diff --git a/apps/chatter/consumers.py b/apps/chatter/consumers.py index 35277df..d53a709 100644 --- a/apps/chatter/consumers.py +++ b/apps/chatter/consumers.py @@ -4,6 +4,7 @@ from channels.generic.websocket import AsyncWebsocketConsumer from rest_framework.authtoken.models import Token from django.contrib.auth.models import AnonymousUser +from django.contrib.auth.hashers import check_password from channels.db import database_sync_to_async from .models import Entry @@ -28,6 +29,16 @@ def create_entry(author, text, room_id): # def get_latest_messages(room_id): # return sync_to_async(Entry.last_50_messages, thread_sensitive=True)(room_id=room_id) +@database_sync_to_async +def check_room_password(room_id, password): + hashed_password = Room.objects.get(id=room_id).password + print('GOT HASHED PASSWORD ', hashed_password) + print('CHECKING PASSWORD ', password) + if not hashed_password: + return True + else: + return check_password(password, hashed_password) + # WEBSOCKETS - The consumer is like the view for websockets. # It is registered in the app routing.py class ChatConsumer(AsyncWebsocketConsumer): @@ -36,6 +47,7 @@ class ChatConsumer(AsyncWebsocketConsumer): NEW_ENTRY = 'NEW_ENTRY' ENTRIES = 'ENTRIES' ERROR = 'ERROR' + INIT_RESPONSE = 'INIT_RESPONSE' async def connect(self): print('CONNECTION RECEIVED') @@ -55,12 +67,13 @@ async def disconnect(self, close_code): self.channel_name ) - async def init_chat(self, data): - user = await get_user(data['token']) + async def init_chat(self, isAuthorized): + # user = await get_user(data['token']) message = { - 'command': self.INIT_CHAT, - 'success': '{0} has joined the room'.format(str(user)) + 'command': self.INIT_RESPONSE, + 'authorized': isAuthorized } + print('INIT_CHAT RECEIVED, RESPONDING WITH ', message) await self.send_message(message) async def fetch_entries(self, data): @@ -94,12 +107,6 @@ async def error_reponse(self, error): } await self.send_message(content) - commands = { - INIT_CHAT: init_chat, - FETCH_ENTRIES: fetch_entries, - NEW_ENTRY: new_entry - } - async def receive(self, text_data): print('IN RECEIVE WITH MESSAGE STRING ' + text_data) message = json.loads(text_data) @@ -108,22 +115,30 @@ async def receive(self, text_data): if message_serializer.is_valid(): print('VALID SERIALIZER: ', message_serializer.validated_data) validMessage = message_serializer.validated_data + if 'password' in validMessage: + password = validMessage['password'] + else: + password = '' + isAuthorized = await check_room_password(self.room_id, password) if validMessage['command'] == ChatConsumer.INIT_CHAT: - await self.init_chat(validMessage) - elif validMessage['command'] == ChatConsumer.FETCH_ENTRIES: - await self.fetch_entries(validMessage) - elif validMessage['command'] == ChatConsumer.NEW_ENTRY: - await self.new_entry(validMessage) + await self.init_chat(isAuthorized) + elif isAuthorized: + if validMessage['command'] == ChatConsumer.FETCH_ENTRIES: + await self.fetch_entries(validMessage) + elif validMessage['command'] == ChatConsumer.NEW_ENTRY: + await self.new_entry(validMessage) + else: + await self.error_reponse('No procedure is available for command \"{0}\"' + .format(validMessage['command'])) else: - await self.error_reponse('No procedure is available for command \"{0}\"' - .format(validMessage['command'])) + await self.error_reponse('The password was incorrect') else: await self.error_reponse('The data sent could not be recognized') print('INVALID SERIALIZER: ', message_serializer.errors) async def send_message(self, message): - print('Sending message: ' + json.dumps(message)) + # print('Sending message: ' + json.dumps(message)) await self.send(text_data=json.dumps(message)) async def send_chat_message(self, message): diff --git a/apps/chatter/migrations/0005_room_password_hash.py b/apps/chatter/migrations/0005_room_password_hash.py new file mode 100644 index 0000000..c23becc --- /dev/null +++ b/apps/chatter/migrations/0005_room_password_hash.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-20 17:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('chatter', '0004_auto_20200519_1651'), + ] + + operations = [ + migrations.AddField( + model_name='room', + name='password_hash', + field=models.CharField(blank=True, default='', max_length=255), + ), + ] diff --git a/apps/chatter/migrations/0006_auto_20200520_1724.py b/apps/chatter/migrations/0006_auto_20200520_1724.py new file mode 100644 index 0000000..430b49d --- /dev/null +++ b/apps/chatter/migrations/0006_auto_20200520_1724.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-20 17:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('chatter', '0005_room_password_hash'), + ] + + operations = [ + migrations.RenameField( + model_name='room', + old_name='password_hash', + new_name='password', + ), + ] diff --git a/apps/chatter/migrations/0007_auto_20200520_2148.py b/apps/chatter/migrations/0007_auto_20200520_2148.py new file mode 100644 index 0000000..1cf950c --- /dev/null +++ b/apps/chatter/migrations/0007_auto_20200520_2148.py @@ -0,0 +1,26 @@ +# Generated by Django 3.0.6 on 2020-05-20 21:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('chatter', '0006_auto_20200520_1724'), + ] + + operations = [ + migrations.AlterField( + model_name='entry', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='entry', + name='room', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chatter.Room'), + ), + ] diff --git a/apps/chatter/models.py b/apps/chatter/models.py index f8eb930..81bd84e 100644 --- a/apps/chatter/models.py +++ b/apps/chatter/models.py @@ -4,21 +4,21 @@ class Room(models.Model): name = models.CharField(max_length=80) - password_hash = models.CharField(max_length=255, required=False) + password = models.CharField(max_length=255, blank=True, default='') def __str__(self): return self.name class Entry(models.Model): text = models.CharField(max_length=255) - timestamp = models.DateTimeField(auto_now_add=True, blank=True) + timestamp = models.DateTimeField(auto_now_add=True) author = models.ForeignKey( settings.AUTH_USER_MODEL, - on_delete=models.DO_NOTHING + on_delete=models.CASCADE ) room = models.ForeignKey( Room, - on_delete=models.DO_NOTHING + on_delete=models.CASCADE ) def was_published_recently(self): diff --git a/apps/chatter/routing.py b/apps/chatter/routing.py index 9d3661f..89937c8 100644 --- a/apps/chatter/routing.py +++ b/apps/chatter/routing.py @@ -1,5 +1,6 @@ # chatter/routing.py from django.urls import re_path + from . import consumers # WEBSOCKETS - The routing is kind of like the urls.py but for a different protocol diff --git a/apps/chatter/serializers.py b/apps/chatter/serializers.py index c7f1c90..b3c11d3 100644 --- a/apps/chatter/serializers.py +++ b/apps/chatter/serializers.py @@ -1,23 +1,45 @@ from rest_framework import serializers from rest_framework.renderers import JSONRenderer +from django.contrib.auth import hashers from .models import Room, Entry +# from .constants import INIT_CHAT, FETCH_ENTRIES, NEW_ENTRY, ENTRIES, ERROR, INIT_RESPONSE class RoomSerializer(serializers.HyperlinkedModelSerializer): + has_password = serializers.SerializerMethodField() class Meta: model = Room - fields = ['id', 'name'] + fields = ['id', 'name', 'password', 'has_password'] + extra_kwargs = { + 'password': {'write_only': True}, + 'id': {'read_only': True} + } + + def create(self, validated_data): + if 'password' in validated_data: + password_hash = hashers.make_password(validated_data['password']) + validated_data['password'] = password_hash + else: + validated_data['password'] = '' + return Room.objects.create(**validated_data) + + def get_has_password(self, obj): + return obj.password is not '' class EntrySerializer(serializers.ModelSerializer): class Meta: model = Entry - fields = ('id', 'timestamp', 'author', 'text') - extra_kwargs = {'author': {'read_only': True}} + fields = ('timestamp', 'author', 'text') + extra_kwargs = { + 'author': {'read_only': True}, + 'timestamp': {'read_only': True} + } class MessageSerializer(serializers.Serializer): - command = serializers.ChoiceField(choices=['INIT_CHAT', 'FETCH_ENTRIES', 'NEW_ENTRY', 'ENTRIES']) + command = serializers.CharField(max_length=32) token = serializers.CharField(required=False, min_length=40, max_length=40) text = serializers.CharField(required=False, max_length=255) + password = serializers.CharField(required=False, max_length=255, allow_blank=True) # Not necessary at this end of the API? # entries = EntrySerializer(required=False, many=True) success = serializers.CharField(required=False, max_length=100) diff --git a/apps/chatter/urls.py b/apps/chatter/urls.py index 97f5ec5..262a8f6 100644 --- a/apps/chatter/urls.py +++ b/apps/chatter/urls.py @@ -1,6 +1,7 @@ # chatter/urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter + from . import views router = DefaultRouter()