diff --git a/core/authentication/api_v2.py b/core/authentication/api_v2.py index f87e143..b9a67cb 100644 --- a/core/authentication/api_v2.py +++ b/core/authentication/api_v2.py @@ -1,57 +1,85 @@ -from rest_framework import routers, viewsets, serializers +from rest_framework import routers, viewsets, serializers, permissions from rest_framework.decorators import api_view, permission_classes, authentication_classes from rest_framework.response import Response -from rest_framework.authentication import BasicAuthentication -from django.contrib.auth.models import User +from django.contrib.auth import login from django.urls import path +from django.dispatch import receiver +from django.db.models.signals import post_save +from knox.models import AuthToken +from knox.views import LoginView as KnoxLoginView +from rest_framework.authtoken.serializers import AuthTokenSerializer + +from authentication.models import ExtendedUser class UserSerializer(serializers.ModelSerializer): class Meta: - model = User + model = ExtendedUser fields = ('id', 'username', 'email', 'first_name', 'last_name') -class RegisterUserSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ('username', 'password', 'email') - extra_kwargs = { - 'password': {'write_only': True}, - } +@receiver(post_save, sender=ExtendedUser) +def create_auth_token(sender, instance=None, created=False, **kwargs): + if created: + AuthToken.objects.create(user=instance) class UserViewSet(viewsets.ModelViewSet): - queryset = User.objects.all() + queryset = ExtendedUser.objects.all() serializer_class = UserSerializer - authentication_classes = [BasicAuthentication] - permission_classes = [] -@api_view(['GET']) -@permission_classes([]) -@authentication_classes([BasicAuthentication]) -def token(request): - return Response({ - 'token': request.user.auth_token.key - }) +@api_view(['POST']) +def selfUser(request): + serializer = UserSerializer(request.user) + return Response(serializer.data, status=200) @api_view(['POST']) @permission_classes([]) @authentication_classes([]) def registerUser(request): - serializer = RegisterUserSerializer(data=request.data) - if serializer.is_valid(): - user = serializer.save() + try: + username = request.data.get('username') + password = request.data.get('password') + email = request.data.get('email') + + errors = {} + if not username: + errors['username'] = 'Username is required' + if not password: + errors['password'] = 'Password is required' + if not email: + errors['email'] = 'Email is required' + if ExtendedUser.objects.filter(email=email).exists(): + errors['email'] = 'Email already exists' + if ExtendedUser.objects.filter(username=username).exists(): + errors['username'] = 'Username already exists' + if errors: + return Response({'errors': errors}, status=400) + user = ExtendedUser.objects.create_user(username, email, password) return Response({'username': user.username, 'email': user.email}, status=201) - return Response(serializer.errors, status=400) + except Exception as e: + return Response({'errors': str(e)}, status=400) + + +class LoginView(KnoxLoginView): + permission_classes = (permissions.AllowAny,) + authentication_classes = () + + def post(self, request, format=None): + serializer = AuthTokenSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data['user'] + login(request, user) + return super(LoginView, self).post(request, format=None) router = routers.SimpleRouter() router.register(r'users', UserViewSet, basename='users') urlpatterns = router.urls + [ - path('token/', token), + path('self/', selfUser), + path('login/', LoginView.as_view()), path('register/', registerUser), ] diff --git a/core/authentication/migrations/0002_groups.py b/core/authentication/migrations/0002_groups.py deleted file mode 100644 index f7d9f34..0000000 --- a/core/authentication/migrations/0002_groups.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-26 00:16 - -from django.conf import settings -from django.db import migrations -from django.contrib.auth.models import Permission, Group - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('authentication', '0001_initial'), - ('inventory', '0003_alter_item_options'), - ('tickets', '0003_alter_issuethread_options'), - ] - - def create_groups(apps, schema_editor): - admins = Group.objects.create(name='Admin') - orga = Group.objects.create(name='Orga') - team = Group.objects.create(name='Team') - users = Group.objects.create(name='User') - admins.permissions.add(*Permission.objects.all()) - users.permissions.add(*Permission.objects.filter(codename__in= - ['view_item', 'add_item', 'change_item', 'match_item'])) - team.permissions.add(*Permission.objects.filter(codename__in= - ['delete_item', 'view_issuethread', 'add_issuethread', - 'change_issuethread', 'delete_issuethread', 'send_mail']), - *users.permissions.all()) - orga.permissions.add(*Permission.objects.filter(codename__in=['add_event']), - *team.permissions.all()) - - operations = [ - migrations.RunPython(create_groups), - ] diff --git a/core/authentication/tests/v2/test_permissions.py b/core/authentication/tests/v2/test_permissions.py new file mode 100644 index 0000000..0cab3e2 --- /dev/null +++ b/core/authentication/tests/v2/test_permissions.py @@ -0,0 +1,34 @@ +from django.test import TestCase +from django.contrib.auth.models import Permission + +from authentication.models import EventPermission, ExtendedUser +from inventory.models import Event + + +class PermissionsTestCase(TestCase): + def setUp(self): + super().setUp() + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + event1 = Event.objects.create(slug='testevent1', name='testevent1') + event2 = Event.objects.create(slug='testevent2', name='testevent2') + permission1 = Permission.objects.get(codename='view_event') + EventPermission.objects.create(user=self.user, permission=permission1, event=event1) + EventPermission.objects.create(user=self.user, permission=permission1, event=event2) + + def test_user_permissions(self): + """ + Test that a user can only access their own data. + """ + self.client.force_login(self.user) + response = self.client.get('/api/2/users/') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()), 2) + self.assertEqual(response.json()[0]['username'], 'testuser') + self.assertEqual(response.json()[0]['email'], 'test') + self.assertEqual(response.json()[0]['first_name'], '') + self.assertEqual(response.json()[0]['last_name'], '') + self.assertEqual(response.json()[0]['id'], 1) + self.assertEqual(response.json()[1]['username'], 'testuser') + self.assertEqual(response.json()[1]['email'], 'test') + self.assertEqual(response.json()[1]['first_name'], '') + self.assertEqual(response.json()[1]['last_name'], '') diff --git a/core/authentication/tests/v2/test_users.py b/core/authentication/tests/v2/test_users.py index dddfc51..6accb50 100644 --- a/core/authentication/tests/v2/test_users.py +++ b/core/authentication/tests/v2/test_users.py @@ -1,18 +1,93 @@ from django.test import TestCase, Client +from knox.models import AuthToken + +from authentication.models import ExtendedUser from core import settings -client = Client() +class UserApiTest(TestCase): -class IssueApiTest(TestCase): + def setUp(self): + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + self.user.save() + self.token = AuthToken.objects.create(user=self.user) + self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) - def test_issues(self): - response = client.get('/api/2/users/') + def test_users(self): + response = self.client.get('/api/2/users/') self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.json()), 1) + self.assertEqual(len(response.json()), 2) self.assertEqual(response.json()[0]['username'], settings.LEGACY_USER_NAME) self.assertEqual(response.json()[0]['email'], 'mail@' + settings.MAIL_DOMAIN) self.assertEqual(response.json()[0]['first_name'], '') self.assertEqual(response.json()[0]['last_name'], '') self.assertEqual(response.json()[0]['id'], 1) + self.assertEqual(response.json()[1]['username'], 'testuser') + self.assertEqual(response.json()[1]['email'], 'test') + self.assertEqual(response.json()[1]['first_name'], '') + self.assertEqual(response.json()[1]['last_name'], '') + + def test_self_user(self): + response = self.client.post('/api/2/self/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['username'], 'testuser') + self.assertEqual(response.json()['email'], 'test') + self.assertEqual(response.json()['first_name'], '') + self.assertEqual(response.json()['last_name'], '') + + def test_register_user(self): + anonymous = Client() + response = anonymous.post('/api/2/register/', {'username': 'testuser2', 'password': 'test', 'email': 'test2'}, + content_type='application/json') + self.assertEqual(response.status_code, 201) + self.assertEqual(response.json()['username'], 'testuser2') + self.assertEqual(response.json()['email'], 'test2') + self.assertEqual(len(ExtendedUser.objects.all()), 3) + self.assertEqual(ExtendedUser.objects.get(username='testuser2').email, 'test2') + self.assertTrue(ExtendedUser.objects.get(username='testuser2').check_password('test')) + + def test_register_user_duplicate(self): + anonymous = Client() + response = anonymous.post('/api/2/register/', {'username': 'testuser', 'password': 'test', 'email': 'test2'}, + content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()['errors']['username'], 'Username already exists') + self.assertEqual(len(ExtendedUser.objects.all()), 2) + + def test_register_user_no_username(self): + anonymous = Client() + response = anonymous.post('/api/2/register/', {'password': 'test', 'email': 'test2'}, + content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()['errors']['username'], 'Username is required') + self.assertEqual(len(ExtendedUser.objects.all()), 2) + + def test_register_user_no_password(self): + anonymous = Client() + response = anonymous.post('/api/2/register/', {'username': 'testuser2', 'email': 'test2'}, + content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()['errors']['password'], 'Password is required') + self.assertEqual(len(ExtendedUser.objects.all()), 2) + + def test_register_user_no_email(self): + anonymous = Client() + response = anonymous.post('/api/2/register/', {'username': 'testuser2', 'password': 'test'}, + content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()['errors']['email'], 'Email is required') + self.assertEqual(len(ExtendedUser.objects.all()), 2) + + def test_register_user_duplicate_email(self): + anonymous = Client() + response = anonymous.post('/api/2/register/', {'username': 'testuser2', 'password': 'test', 'email': 'test'}, + content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()['errors']['email'], 'Email already exists') + self.assertEqual(len(ExtendedUser.objects.all()), 2) + + def test_get_token(self): + anonymous = Client() + response = anonymous.post('/api/2/login/', {'username': 'testuser', 'password': 'test'}, + content_type='application/json') diff --git a/core/core/settings.py b/core/core/settings.py index a15fbb0..c753624 100644 --- a/core/core/settings.py +++ b/core/core/settings.py @@ -51,7 +51,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'django_extensions', 'rest_framework', - 'rest_framework.authtoken', + 'knox', 'drf_yasg', 'channels', 'authentication', @@ -63,9 +63,13 @@ INSTALLED_APPS = [ ] REST_FRAMEWORK = { - 'TEST_REQUEST_DEFAULT_FORMAT': 'json' + 'TEST_REQUEST_DEFAULT_FORMAT': 'json', + 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',), + 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',), } +AUTH_USER_MODEL = 'authentication.ExtendedUser' + SWAGGER_SETTINGS = { 'SECURITY_DEFINITIONS': { 'api_key': { diff --git a/core/files/api_v1.py b/core/files/api_v1.py index b0386da..ce9730f 100644 --- a/core/files/api_v1.py +++ b/core/files/api_v1.py @@ -16,6 +16,8 @@ class FileViewSet(viewsets.ModelViewSet): serializer_class = FileSerializer queryset = File.objects.all() lookup_field = 'hash' + permission_classes = [] + authentication_classes = [] router = routers.SimpleRouter(trailing_slash=False) diff --git a/core/files/migrations/0001_initial.py b/core/files/migrations/0001_initial.py index fa20ba8..2c15fff 100644 --- a/core/files/migrations/0001_initial.py +++ b/core/files/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2023-11-18 11:28 +# Generated by Django 4.2.7 on 2023-12-09 02:13 from django.db import migrations, models import django.db.models.deletion diff --git a/core/files/tests/v2/test_files.py b/core/files/tests/v2/test_files.py index 81265f2..1a8a02c 100644 --- a/core/files/tests/v2/test_files.py +++ b/core/files/tests/v2/test_files.py @@ -1,22 +1,26 @@ from django.test import TestCase, Client +from authentication.models import ExtendedUser from files.models import File from inventory.models import Event, Container, Item - -client = Client() +from knox.models import AuthToken class FileTestCase(TestCase): def setUp(self): super().setUp() + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + self.user.save() + self.token = AuthToken.objects.create(user=self.user) + self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) self.event = Event.objects.create(slug='EVENT', name='Event') self.box = Container.objects.create(name='BOX') def test_list_files(self): import base64 item = File.objects.create(data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) - response = client.get('/api/2/files/') + response = self.client.get('/api/2/files/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()[0]['hash'], item.hash) self.assertEqual(len(response.json()[0]['hash']), 64) @@ -24,7 +28,7 @@ class FileTestCase(TestCase): def test_one_file(self): import base64 item = File.objects.create(data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) - response = client.get(f'/api/2/files/{item.hash}/') + response = self.client.get(f'/api/2/files/{item.hash}/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['hash'], item.hash) self.assertEqual(len(response.json()['hash']), 64) @@ -33,7 +37,7 @@ class FileTestCase(TestCase): import base64 Item.objects.create(container=self.box, event=self.event, description='1') item = Item.objects.create(container=self.box, event=self.event, description='2') - response = client.post('/api/2/files/', + response = self.client.post('/api/2/files/', {'data': "data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')}, content_type='application/json') self.assertEqual(response.status_code, 201) @@ -45,5 +49,5 @@ class FileTestCase(TestCase): File.objects.create(item=item, data="data:text/plain;base64," + base64.b64encode(b"foo").decode('utf-8')) file = File.objects.create(item=item, data="data:text/plain;base64," + base64.b64encode(b"bar").decode('utf-8')) self.assertEqual(len(File.objects.all()), 2) - response = client.delete(f'/api/2/files/{file.hash}/') + response = self.client.delete(f'/api/2/files/{file.hash}/') self.assertEqual(response.status_code, 204) diff --git a/core/inventory/api_v2.py b/core/inventory/api_v2.py index 0b4feea..3e66966 100644 --- a/core/inventory/api_v2.py +++ b/core/inventory/api_v2.py @@ -20,7 +20,6 @@ class EventViewSet(viewsets.ModelViewSet): serializer_class = EventSerializer queryset = Event.objects.all() permission_classes = [] - authentication_classes = [] class ContainerSerializer(serializers.ModelSerializer): @@ -39,7 +38,6 @@ class ContainerViewSet(viewsets.ModelViewSet): serializer_class = ContainerSerializer queryset = Container.objects.all() permission_classes = [] - authentication_classes = [] class ItemSerializer(serializers.ModelSerializer): @@ -93,8 +91,6 @@ class ItemSerializer(serializers.ModelSerializer): @api_view(['GET']) -@permission_classes([]) -@authentication_classes([]) def search_items(request, event_slug, query): try: event = Event.objects.get(slug=event_slug) @@ -109,8 +105,6 @@ def search_items(request, event_slug, query): @api_view(['GET', 'POST']) -@permission_classes([]) -@authentication_classes([]) def item(request, event_slug): try: event = Event.objects.get(slug=event_slug) @@ -126,8 +120,6 @@ def item(request, event_slug): @api_view(['GET', 'PUT', 'DELETE']) -@permission_classes([]) -@authentication_classes([]) def item_by_id(request, event_slug, id): try: event = Event.objects.get(slug=event_slug) diff --git a/core/inventory/migrations/0001_initial.py b/core/inventory/migrations/0001_initial.py index bd08ef3..ea60f75 100644 --- a/core/inventory/migrations/0001_initial.py +++ b/core/inventory/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2023-11-18 11:28 +# Generated by Django 4.2.7 on 2023-12-09 02:13 from django.db import migrations, models import django.db.models.deletion @@ -15,11 +15,16 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Container', fields=[ + ('is_deleted', models.BooleanField(default=False)), + ('deleted_at', models.DateTimeField(blank=True, null=True)), ('cid', models.AutoField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=255)), ('created_at', models.DateTimeField(blank=True, null=True)), ('updated_at', models.DateTimeField(blank=True, null=True)), ], + options={ + 'abstract': False, + }, ), migrations.CreateModel( name='Event', @@ -38,6 +43,8 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Item', fields=[ + ('is_deleted', models.BooleanField(default=False)), + ('deleted_at', models.DateTimeField(blank=True, null=True)), ('iid', models.AutoField(primary_key=True, serialize=False)), ('uid', models.IntegerField()), ('description', models.TextField()), @@ -48,6 +55,7 @@ class Migration(migrations.Migration): ('event', models.ForeignKey(db_column='eid', on_delete=django.db.models.deletion.CASCADE, to='inventory.event')), ], options={ + 'permissions': [('match_item', 'Can match item')], 'unique_together': {('uid', 'event')}, }, ), diff --git a/core/inventory/migrations/0002_container_deleted_at_container_is_deleted_and_more.py b/core/inventory/migrations/0002_container_deleted_at_container_is_deleted_and_more.py deleted file mode 100644 index 28523a7..0000000 --- a/core/inventory/migrations/0002_container_deleted_at_container_is_deleted_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-20 11:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='container', - name='deleted_at', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='container', - name='is_deleted', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='item', - name='deleted_at', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='item', - name='is_deleted', - field=models.BooleanField(default=False), - ), - ] diff --git a/core/inventory/migrations/0003_alter_item_options.py b/core/inventory/migrations/0003_alter_item_options.py deleted file mode 100644 index 0efebee..0000000 --- a/core/inventory/migrations/0003_alter_item_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-06 13:45 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0002_container_deleted_at_container_is_deleted_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='item', - options={'permissions': [('match_item', 'Can match item')]}, - ), - ] diff --git a/core/mail/api_v2.py b/core/mail/api_v2.py index 3de1d3c..5227096 100644 --- a/core/mail/api_v2.py +++ b/core/mail/api_v2.py @@ -12,8 +12,6 @@ class EmailSerializer(serializers.ModelSerializer): class EmailViewSet(viewsets.ModelViewSet): serializer_class = EmailSerializer queryset = Email.objects.all() - permission_classes = [] - authentication_classes = [] router = routers.SimpleRouter() diff --git a/core/mail/migrations/0001_initial.py b/core/mail/migrations/0001_initial.py index 9c792fb..71c54e7 100644 --- a/core/mail/migrations/0001_initial.py +++ b/core/mail/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2023-12-06 02:52 +# Generated by Django 4.2.7 on 2023-12-09 02:13 from django.db import migrations, models import django.db.models.deletion @@ -9,8 +9,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('inventory', '0001_initial'), ('tickets', '0001_initial'), - ('inventory', '0002_container_deleted_at_container_is_deleted_and_more'), ] operations = [ diff --git a/core/mail/tests/v2/test_mails.py b/core/mail/tests/v2/test_mails.py index 8200c34..7e9fec5 100644 --- a/core/mail/tests/v2/test_mails.py +++ b/core/mail/tests/v2/test_mails.py @@ -1,16 +1,16 @@ import inspect from unittest import mock +from knox.models import AuthToken from django.test import TestCase, Client +from authentication.models import ExtendedUser from core.settings import MAIL_DOMAIN from inventory.models import Event from mail.models import Email from mail.protocol import LMTPHandler from tickets.models import IssueThread -client = Client() - def make_mocked_coro(return_value=mock.sentinel, raise_exception=mock.sentinel): async def mock_coro(*args, **kwargs): @@ -25,6 +25,13 @@ def make_mocked_coro(return_value=mock.sentinel, raise_exception=mock.sentinel): class EmailsApiTest(TestCase): + def setUp(self): + super().setUp() + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + self.user.save() + self.token = AuthToken.objects.create(user=self.user) + self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) + def test_mails(self): Event.objects.get_or_create( name="Test event", @@ -36,7 +43,7 @@ class EmailsApiTest(TestCase): sender='test', recipient='test', ) - response = client.get('/api/2/mails/') + response = self.client.get('/api/2/mails/') self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual(response.json()[0]['subject'], 'test') @@ -45,13 +52,20 @@ class EmailsApiTest(TestCase): self.assertEqual(response.json()[0]['recipient'], 'test') def test_mails_empty(self): - response = client.get('/api/2/mails/') + response = self.client.get('/api/2/mails/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), []) class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test + def setUp(self): + super().setUp() + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + self.user.save() + self.token = AuthToken.objects.create(user=self.user) + self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) + def test_handle_client(self): from aiosmtpd.smtp import Envelope from asgiref.sync import async_to_sync @@ -156,8 +170,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test ) import aiosmtplib aiosmtplib.send = make_mocked_coro() - client = Client() - response = client.post(f'/api/2/tickets/{issue_thread.id}/reply/', { + response = self.client.post(f'/api/2/tickets/{issue_thread.id}/reply/', { 'message': 'test' }) self.assertEqual(response.status_code, 201) diff --git a/core/notify_sessions/api_v2.py b/core/notify_sessions/api_v2.py index 2e8c2de..30ff737 100644 --- a/core/notify_sessions/api_v2.py +++ b/core/notify_sessions/api_v2.py @@ -13,8 +13,6 @@ class SystemEventSerializer(serializers.ModelSerializer): class SystemEventViewSet(viewsets.ModelViewSet): serializer_class = SystemEventSerializer queryset = SystemEvent.objects.all() - permission_classes = [] - authentication_classes = [] router = routers.SimpleRouter() diff --git a/core/notify_sessions/migrations/0001_initial.py b/core/notify_sessions/migrations/0001_initial.py index 0889058..c116acf 100644 --- a/core/notify_sessions/migrations/0001_initial.py +++ b/core/notify_sessions/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2023-12-01 18:19 +# Generated by Django 4.2.7 on 2023-12-09 02:13 from django.conf import settings from django.db import migrations, models diff --git a/core/notify_sessions/models.py b/core/notify_sessions/models.py index d3f248a..095f1b5 100644 --- a/core/notify_sessions/models.py +++ b/core/notify_sessions/models.py @@ -1,10 +1,11 @@ import logging from django.db import models -from django.contrib.auth.models import User from asgiref.sync import sync_to_async from channels.layers import get_channel_layer +from authentication.models import ExtendedUser + class SystemEvent(models.Model): TYPE_CHOICES = [('ticket_created', 'ticket_created'), @@ -19,7 +20,7 @@ class SystemEvent(models.Model): ('event_deleted', 'event_deleted'), ] id = models.AutoField(primary_key=True) timestamp = models.DateTimeField(auto_now_add=True) - user = models.ForeignKey(User, models.SET_NULL, null=True) + user = models.ForeignKey(ExtendedUser, models.SET_NULL, null=True) type = models.CharField(max_length=255, choices=TYPE_CHOICES) reference = models.IntegerField(blank=True, null=True) diff --git a/core/tickets/api_v2.py b/core/tickets/api_v2.py index b1c202a..2aebbe4 100644 --- a/core/tickets/api_v2.py +++ b/core/tickets/api_v2.py @@ -53,13 +53,9 @@ class IssueSerializer(serializers.ModelSerializer): class IssueViewSet(viewsets.ModelViewSet): serializer_class = IssueSerializer queryset = IssueThread.objects.all() - permission_classes = [] - authentication_classes = [] @api_view(['POST']) -@permission_classes([]) -@authentication_classes([]) def reply(request, pk): issue = IssueThread.objects.get(pk=pk) # email = issue.reply(request.data['body']) # TODO evaluate if this is a useful abstraction diff --git a/core/tickets/migrations/0001_initial.py b/core/tickets/migrations/0001_initial.py index cdfaadc..475d70c 100644 --- a/core/tickets/migrations/0001_initial.py +++ b/core/tickets/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2023-12-06 02:34 +# Generated by Django 4.2.7 on 2023-12-09 02:13 from django.db import migrations, models import django.db.models.deletion @@ -18,9 +18,13 @@ class Migration(migrations.Migration): ('is_deleted', models.BooleanField(default=False)), ('deleted_at', models.DateTimeField(blank=True, null=True)), ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('state', models.CharField(default='new', max_length=255)), + ('assigned_to', models.CharField(max_length=255, null=True)), + ('last_activity', models.DateTimeField(auto_now=True)), ], options={ - 'abstract': False, + 'permissions': [('send_mail', 'Can send mail')], }, ), migrations.CreateModel( @@ -29,7 +33,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(primary_key=True, serialize=False)), ('state', models.CharField(max_length=255)), ('timestamp', models.DateTimeField(auto_now_add=True)), - ('issue_thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.issuethread')), + ('issue_thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='state_changes', to='tickets.issuethread')), ], ), migrations.CreateModel( @@ -38,7 +42,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(primary_key=True, serialize=False)), ('comment', models.TextField()), ('timestamp', models.DateTimeField(auto_now_add=True)), - ('issue_thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.issuethread')), + ('issue_thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tickets.issuethread')), ], ), ] diff --git a/core/tickets/migrations/0002_issuethread_assigned_to_issuethread_last_activity_and_more.py b/core/tickets/migrations/0002_issuethread_assigned_to_issuethread_last_activity_and_more.py deleted file mode 100644 index fc30eb0..0000000 --- a/core/tickets/migrations/0002_issuethread_assigned_to_issuethread_last_activity_and_more.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-06 03:53 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='issuethread', - name='assigned_to', - field=models.CharField(max_length=255, null=True), - ), - migrations.AddField( - model_name='issuethread', - name='last_activity', - field=models.DateTimeField(auto_now=True), - ), - migrations.AddField( - model_name='issuethread', - name='name', - field=models.CharField(default='unnamed issue', max_length=255), - preserve_default=False, - ), - migrations.AddField( - model_name='issuethread', - name='state', - field=models.CharField(default='new', max_length=255), - ), - migrations.AlterField( - model_name='comment', - name='issue_thread', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tickets.issuethread'), - ), - migrations.AlterField( - model_name='statechange', - name='issue_thread', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='state_changes', to='tickets.issuethread'), - ), - ] diff --git a/core/tickets/migrations/0003_alter_issuethread_options.py b/core/tickets/migrations/0003_alter_issuethread_options.py deleted file mode 100644 index 4d53778..0000000 --- a/core/tickets/migrations/0003_alter_issuethread_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-06 13:47 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0002_issuethread_assigned_to_issuethread_last_activity_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='issuethread', - options={'permissions': [('send_mail', 'Can send mail')]}, - ), - ] diff --git a/core/tickets/tests/v2/test_tickets.py b/core/tickets/tests/v2/test_tickets.py index 963119a..753c0b6 100644 --- a/core/tickets/tests/v2/test_tickets.py +++ b/core/tickets/tests/v2/test_tickets.py @@ -2,16 +2,24 @@ from datetime import datetime, timedelta from django.test import TestCase, Client +from authentication.models import ExtendedUser from mail.models import Email from tickets.models import IssueThread, StateChange, Comment - -client = Client() +from django.contrib.auth.models import User +from knox.models import AuthToken class IssueApiTest(TestCase): + def setUp(self): + super().setUp() + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + self.user.save() + self.token = AuthToken.objects.create(user=self.user) + self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) + def test_issues_empty(self): - response = client.get('/api/2/tickets/') + response = self.client.get('/api/2/tickets/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), []) @@ -48,7 +56,7 @@ class IssueApiTest(TestCase): timestamp=now + timedelta(seconds=3), ) - response = client.get('/api/2/tickets/') + response = self.client.get('/api/2/tickets/') self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual(response.json()[0]['id'], issue.id) diff --git a/web/src/store/index.js b/web/src/store/index.js index 2a82130..d8eeeec 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -138,17 +138,22 @@ const store = new Vuex.Store({ actions: { async login({commit, dispatch, state}, {username, password, remember}) { commit('setRemember', remember); - const data = await fetch('/api/2/auth/token/', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({username: username, password: password}), - credentials: 'omit' - }).then(r => r.json()) - if (data.token && data.key) { - commit('setToken', data.token); - commit('setUser', username); - return true; - } else { + try{ + const data = await fetch('/api/2/token/', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({username: username, password: password}), + credentials: 'omit' + }).then(r => r.json()) + if (data.token && data.key) { + commit('setToken', data.token); + commit('setUser', username); + return true; + } else { + return false; + } + } catch (e) { + console.error(e); return false; } }, diff --git a/web/src/views/Register.vue b/web/src/views/Register.vue index c028302..11ed693 100644 --- a/web/src/views/Register.vue +++ b/web/src/views/Register.vue @@ -31,6 +31,19 @@ +
+ +
+ +
+
+ {{ errors.email }} +
+
+ +