diff --git a/TODO.md b/TODO.md index 68452cf..a0eb090 100644 --- a/TODO.md +++ b/TODO.md @@ -71,30 +71,8 @@ * [ ] concept: purge old tickets * [ ] concept: purge old items * [ ] concept: auto email stale after x days -* [ ] frontend: reply email field multiline -* [ ] concept: customisable autoreply (message, title) + ticket id -* [ ] frontend: show from address in issueThread or internal user -* [ ] manual ticket creation -* [ ] hide citation for incoming mails -* [ ] frontend: different icons for send and received mails -* [ ] frontend: change ticket status - * new mail -> new, - * op reply -> - * waiting for detail - * waiting for address / shipment information - * customer reply -> open - * needs physical confirmation - * needs to be shipped - * dhl mail -> closed: shipped - * closed: not found - * closed: not our problem - * closed: timeout (no reply) - * closed: duplicate - * closed: spam -* [ ] concept: split ticket for multiple items -* [ ] mail signature -* [ ] guru api integration ## Priority: TODO -* manual ticket creation +* send mails from web frontend +* login / user management diff --git a/core/authentication/api_v2.py b/core/authentication/api_v2.py index 514a697..019a0fd 100644 --- a/core/authentication/api_v2.py +++ b/core/authentication/api_v2.py @@ -7,7 +7,6 @@ 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 django.contrib.auth.models import Group from knox.models import AuthToken from knox.views import LoginView as KnoxLoginView @@ -15,16 +14,9 @@ from authentication.models import ExtendedUser class UserSerializer(serializers.ModelSerializer): - permissions = serializers.SerializerMethodField() - groups = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name') - class Meta: model = ExtendedUser - fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions', 'groups') - read_only_fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions', 'groups') - - def get_permissions(self, obj): - return list(set(obj.get_permissions())) + fields = ('id', 'username', 'email', 'first_name', 'last_name') @receiver(post_save, sender=ExtendedUser) @@ -38,27 +30,7 @@ class UserViewSet(viewsets.ModelViewSet): serializer_class = UserSerializer -class GroupSerializer(serializers.ModelSerializer): - permissions = serializers.SerializerMethodField() - members = serializers.SerializerMethodField() - - class Meta: - model = Group - fields = ('id', 'name', 'permissions', 'members') - - def get_permissions(self, obj): - return ["*:" + p.codename for p in obj.permissions.all()] - - def get_members(self, obj): - return [u.username for u in obj.user_set.all()] - - -class GroupViewSet(viewsets.ModelViewSet): - queryset = Group.objects.all() - serializer_class = GroupSerializer - - -@api_view(['GET']) +@api_view(['POST']) @permission_classes([IsAuthenticated]) def selfUser(request): serializer = UserSerializer(request.user) @@ -107,7 +79,6 @@ class LoginView(KnoxLoginView): router = routers.SimpleRouter() router.register(r'users', UserViewSet, basename='users') -router.register(r'groups', GroupViewSet, basename='groups') urlpatterns = router.urls + [ path('self/', selfUser), diff --git a/core/authentication/migrations/0005_alter_eventpermission_event.py b/core/authentication/migrations/0005_alter_eventpermission_event.py new file mode 100644 index 0000000..486f94d --- /dev/null +++ b/core/authentication/migrations/0005_alter_eventpermission_event.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.7 on 2023-12-13 02:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0001_initial'), + ('authentication', '0004_legacy_user'), + ] + + operations = [ + migrations.AlterField( + model_name='eventpermission', + name='event', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='inventory.event'), + ), + ] diff --git a/core/authentication/tests/v2/test_permissions.py b/core/authentication/tests/v2/test_permissions.py index 0a00fcd..bb184cf 100644 --- a/core/authentication/tests/v2/test_permissions.py +++ b/core/authentication/tests/v2/test_permissions.py @@ -65,6 +65,7 @@ class PermissionsTestCase(TestCase): user.event_permissions.create(permission=Permission.objects.get(codename='view_item'), event=Event.objects.get(slug='testevent2')) user.event_permissions.create(permission=Permission.objects.get(codename='add_item'), event=Event.objects.get(slug='testevent1')) user.save() + print(user.get_all_permissions()) #self.assertTrue(user.has_perm('inventory.view_event', Event.objects.get(slug='testevent1'))) #self.assertTrue(user.has_perm('inventory.view_event', Event.objects.get(slug='testevent2'))) #self.assertFalse(user.has_perm('inventory.add_event', Event.objects.get(slug='testevent1'))) diff --git a/core/authentication/tests/v2/test_users.py b/core/authentication/tests/v2/test_users.py index 125be9b..6838e64 100644 --- a/core/authentication/tests/v2/test_users.py +++ b/core/authentication/tests/v2/test_users.py @@ -1,30 +1,17 @@ from django.test import TestCase, Client -from django.contrib.auth.models import Permission, Group +from django.contrib.auth.models import Permission from knox.models import AuthToken -from authentication.models import ExtendedUser, EventPermission +from authentication.models import ExtendedUser from core import settings -from inventory.models import Event class UserApiTest(TestCase): def setUp(self): - self.event = Event.objects.create(name='testevent', slug='testevent') - self.group1 = Group.objects.create(name='testgroup1') - self.group2 = Group.objects.create(name='testgroup2') - self.group1.permissions.add(Permission.objects.get(codename='add_item')) - self.group1.permissions.add(Permission.objects.get(codename='view_item')) - self.group2.permissions.add(Permission.objects.get(codename='view_event')) - self.group2.permissions.add(Permission.objects.get(codename='view_item')) self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') - self.user.user_permissions.add(Permission.objects.get(codename='add_event')) - self.user.groups.add(self.group1) - self.user.groups.add(self.group2) - self.user.save() - EventPermission.objects.create(event=self.event, user=self.user, - permission=Permission.objects.get(codename='delete_item')) + self.user.user_permissions.add(*Permission.objects.all()) self.user.save() self.token = AuthToken.objects.create(user=self.user) self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) @@ -38,28 +25,18 @@ class UserApiTest(TestCase): 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()[0]['groups'], []) 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'], '') - self.assertEqual(response.json()[1]['id'], 2) - self.assertEqual(response.json()[1]['groups'], ['testgroup1', 'testgroup2']) def test_self_user(self): - response = self.client.get('/api/2/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'], '') - permissions = response.json()['permissions'] - self.assertEqual(len(permissions), 5) - self.assertTrue('*:add_item' in permissions) - self.assertTrue('*:view_item' in permissions) - self.assertTrue('*:view_event' in permissions) - self.assertTrue('testevent:delete_item' in permissions) - self.assertTrue('*:add_event' in permissions) def test_register_user(self): anonymous = Client() @@ -135,49 +112,3 @@ class UserApiTest(TestCase): content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue('token' in response.json()) - - -class GroupApiTest(TestCase): - - def setUp(self): - self.event = Event.objects.create(name='testevent', slug='testevent') - # Admin, Orga, Team, User are created by default - self.group1 = Group.objects.create(name='testgroup1') - self.group2 = Group.objects.create(name='testgroup2') - self.group1.permissions.add(Permission.objects.get(codename='add_item')) - self.group1.permissions.add(Permission.objects.get(codename='view_item')) - self.group2.permissions.add(Permission.objects.get(codename='view_event')) - self.group2.permissions.add(Permission.objects.get(codename='view_item')) - self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') - self.user.user_permissions.add(Permission.objects.get(codename='add_event')) - self.user.groups.add(self.group1) - self.user.groups.add(self.group2) - self.user.save() - EventPermission.objects.create(event=self.event, user=self.user, - permission=Permission.objects.get(codename='delete_item')) - self.user.save() - self.token = AuthToken.objects.create(user=self.user) - self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) - - def test_groups(self): - response = self.client.get('/api/2/groups/') - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.json()), 6) - self.assertEqual(response.json()[0]['name'], 'Admin') - self.assertEqual(response.json()[1]['name'], 'Orga') - self.assertEqual(response.json()[2]['name'], 'Team') - self.assertEqual(response.json()[3]['name'], 'User') - self.assertEqual(response.json()[4]['name'], 'testgroup1') - self.assertEqual(response.json()[5]['name'], 'testgroup2') - - def test_group(self): - response = self.client.get('/api/2/groups/5/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()['name'], 'testgroup1') - permissions = response.json()['permissions'] - self.assertEqual(len(permissions), 2) - self.assertTrue('*:add_item' in permissions) - self.assertTrue('*:view_item' in permissions) - members = response.json()['members'] - self.assertEqual(len(members), 1) - self.assertEqual(members[0], 'testuser') diff --git a/core/core/settings.py b/core/core/settings.py index 6fe4b9c..f8d7ebb 100644 --- a/core/core/settings.py +++ b/core/core/settings.py @@ -29,7 +29,7 @@ SECRET_KEY = 'django-insecure-tm*$w_14iqbiy-!7(8#ba7j+_@(7@rf2&a^!=shs&$03b%2*rv # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [os.getenv('HTTP_HOST', 'localhost'), 'c3lf.de'] +ALLOWED_HOSTS = [os.getenv('HTTP_HOST', 'localhost')] MAIL_DOMAIN = os.getenv('MAIL_DOMAIN', 'localhost') @@ -66,7 +66,7 @@ REST_FRAMEWORK = { 'TEST_REQUEST_DEFAULT_FORMAT': 'json', 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',), 'DEFAULT_PERMISSION_CLASSES': ( - 'rest_framework.permissions.IsAuthenticated', 'rest_framework.permissions.DjangoModelPermissions'), + 'rest_framework.permissions.IsAuthenticated', 'rest_framework.permissions.DjangoModelPermissions'), } AUTH_USER_MODEL = 'authentication.ExtendedUser' diff --git a/core/inventory/api_v2.py b/core/inventory/api_v2.py index f7306da..79e25f2 100644 --- a/core/inventory/api_v2.py +++ b/core/inventory/api_v2.py @@ -97,7 +97,7 @@ class ItemSerializer(serializers.ModelSerializer): @api_view(['GET']) @permission_classes([IsAuthenticated]) -@permission_required('view_item', raise_exception=True) +@permission_required('inventory.view_item', raise_exception=True) def search_items(request, event_slug, query): try: event = Event.objects.get(slug=event_slug) @@ -117,11 +117,11 @@ def item(request, event_slug): try: event = Event.objects.get(slug=event_slug) if request.method == 'GET': - if not request.user.has_event_perm(event, 'view_item'): + if not request.user.has_event_perm(event, 'inventory.view_item'): return Response(status=403) return Response(ItemSerializer(Item.objects.filter(event=event), many=True).data) elif request.method == 'POST': - if not request.user.has_event_perm(event, 'add_item'): + if not request.user.has_event_perm(event, 'inventory.add_item'): return Response(status=403) validated_data = ItemSerializer(data=request.data) if validated_data.is_valid(): @@ -131,34 +131,25 @@ def item(request, event_slug): return Response(status=404) -@api_view(['GET', 'PUT', 'DELETE', 'PATCH']) +@api_view(['GET', 'PUT', 'DELETE']) @permission_classes([IsAuthenticated]) def item_by_id(request, event_slug, id): try: event = Event.objects.get(slug=event_slug) item = Item.objects.get(event=event, uid=id) if request.method == 'GET': - if not request.user.has_event_perm(event, 'view_item'): + if not request.user.has_event_perm(event, 'inventory.view_item'): return Response(status=403) return Response(ItemSerializer(item).data) elif request.method == 'PUT': - if not request.user.has_event_perm(event, 'change_item'): + if not request.user.has_event_perm(event, 'inventory.change_item'): return Response(status=403) validated_data = ItemSerializer(item, data=request.data) if validated_data.is_valid(): validated_data.save() return Response(validated_data.data) - return Response(validated_data.errors, status=400) - elif request.method == 'PATCH': - if not request.user.has_event_perm(event, 'change_item'): - return Response(status=403) - validated_data = ItemSerializer(item, data=request.data, partial=True) - if validated_data.is_valid(): - validated_data.save() - return Response(validated_data.data) - return Response(validated_data.errors, status=400) elif request.method == 'DELETE': - if not request.user.has_event_perm(event, 'delete_item'): + if not request.user.has_event_perm(event, 'inventory.delete_item'): return Response(status=403) item.delete() return Response(status=204) diff --git a/core/tickets/api_v2.py b/core/tickets/api_v2.py index 695aa7f..c8eb103 100644 --- a/core/tickets/api_v2.py +++ b/core/tickets/api_v2.py @@ -1,18 +1,16 @@ import logging -from django.urls import re_path +from django.urls import path from django.contrib.auth.decorators import permission_required from rest_framework import routers, viewsets, serializers, status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from asgiref.sync import async_to_sync -from channels.layers import get_channel_layer from core.settings import MAIL_DOMAIN from mail.models import Email from mail.protocol import send_smtp, make_reply, collect_references -from notify_sessions.models import SystemEvent from tickets.models import IssueThread @@ -82,44 +80,9 @@ def reply(request, pk): return Response({'status': 'ok'}, status=status.HTTP_201_CREATED) -@api_view(['POST']) -@permission_classes([IsAuthenticated]) -@permission_required('tickets.add_issuethread_manual', raise_exception=True) -def manual_ticket(request): - if 'name' not in request.data: - return Response({'status': 'error', 'message': 'missing name'}, status=status.HTTP_400_BAD_REQUEST) - if 'sender' not in request.data: - return Response({'status': 'error', 'message': 'missing sender'}, status=status.HTTP_400_BAD_REQUEST) - if 'recipient' not in request.data: - return Response({'status': 'error', 'message': 'missing recipient'}, status=status.HTTP_400_BAD_REQUEST) - if 'body' not in request.data: - return Response({'status': 'error', 'message': 'missing body'}, status=status.HTTP_400_BAD_REQUEST) - - issue = IssueThread.objects.create( - name=request.data['name'], - manually_created=True, - ) - email = Email.objects.create( - issue_thread=issue, - sender=request.data['sender'], - recipient=request.data['recipient'], - subject=request.data['name'], - body=request.data['body'], - ) - systemevent = SystemEvent.objects.create(type='email received', reference=email.id) - channel_layer = get_channel_layer() - async_to_sync(channel_layer.group_send)( - 'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id, - "message": "email received"} - ) - - return Response(IssueSerializer(issue).data, status=status.HTTP_201_CREATED) - - router = routers.SimpleRouter() router.register(r'tickets', IssueViewSet, basename='issues') -urlpatterns = ([ - re_path(r'^tickets/(?P\d+)/reply/$', reply, name='reply'), - re_path(r'^tickets/manual/$', manual_ticket, name='manual_ticket'), - ] + router.urls) +urlpatterns = router.urls + [ + path('tickets//reply/', reply, name='reply'), +] diff --git a/core/tickets/migrations/0002_alter_issuethread_options_and_more.py b/core/tickets/migrations/0002_alter_issuethread_options_and_more.py deleted file mode 100644 index 3c1e4a7..0000000 --- a/core/tickets/migrations/0002_alter_issuethread_options_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-22 20:57 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='issuethread', - options={'permissions': [('send_mail', 'Can send mail'), ('add_issuethread_manual', 'Can add issue thread manually')]}, - ), - migrations.AddField( - model_name='issuethread', - name='manually_created', - field=models.BooleanField(default=False), - ), - ] diff --git a/core/tickets/models.py b/core/tickets/models.py index 7130fe5..44da336 100644 --- a/core/tickets/models.py +++ b/core/tickets/models.py @@ -10,12 +10,10 @@ class IssueThread(SoftDeleteModel): state = models.CharField(max_length=255, default='new') assigned_to = models.CharField(max_length=255, null=True) last_activity = models.DateTimeField(auto_now=True) - manually_created = models.BooleanField(default=False) class Meta: permissions = [ ('send_mail', 'Can send mail'), - ('add_issuethread_manual', 'Can add issue thread manually'), ] diff --git a/core/tickets/tests/v2/test_tickets.py b/core/tickets/tests/v2/test_tickets.py index 1f29963..753c0b6 100644 --- a/core/tickets/tests/v2/test_tickets.py +++ b/core/tickets/tests/v2/test_tickets.py @@ -5,7 +5,7 @@ from django.test import TestCase, Client from authentication.models import ExtendedUser from mail.models import Email from tickets.models import IssueThread, StateChange, Comment -from django.contrib.auth.models import Permission +from django.contrib.auth.models import User from knox.models import AuthToken @@ -14,7 +14,6 @@ class IssueApiTest(TestCase): def setUp(self): super().setUp() self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') - self.user.user_permissions.add(*Permission.objects.all()) self.user.save() self.token = AuthToken.objects.create(user=self.user) self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) @@ -92,19 +91,3 @@ class IssueApiTest(TestCase): self.assertEqual(response.json()[0]['timeline'][3]['comment'], 'test') self.assertEqual(response.json()[0]['timeline'][3]['timestamp'], comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ')) - - def test_manual_creation(self): - response = self.client.post('/api/2/tickets/manual/', {'name': 'test issue', 'sender': 'test', - 'recipient': 'test', 'body': 'test'}) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.json()['state'], 'new') - self.assertEqual(response.json()['name'], 'test issue') - self.assertEqual(response.json()['assigned_to'], None) - timeline = response.json()['timeline'] - self.assertEqual(len(timeline), 1) - self.assertEqual(timeline[0]['type'], 'mail') - self.assertEqual(timeline[0]['sender'], 'test') - self.assertEqual(timeline[0]['recipient'], 'test') - self.assertEqual(timeline[0]['subject'], 'test issue') - self.assertEqual(timeline[0]['body'], 'test') - diff --git a/web/src/App.vue b/web/src/App.vue index d741a99..068e7ef 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,13 +1,12 @@ @@ -109,21 +99,14 @@ export default { ] }), computed: { - ...mapState(['events', 'layout']), - ...mapGetters(['getEventSlug', 'getActiveView', "checkPermission", "hasPermissions"]), + ...mapState(['events', 'activeEvent', 'layout']), + ...mapGetters(['getEventSlug', 'getActiveView', "checkRole"]), }, methods: { ...mapActions(['changeEvent', 'changeView', 'searchEventItems']), - ...mapMutations(['setLayout', 'logout']), + ...mapMutations(['setLayout']), navigateTo(link) { - if (this.$router.currentRoute.path !== link) - this.$router.push(link); - }, - isItemView() { - return this.getActiveView === 'items' || this.getActiveView === 'item'; - }, - isTicketView() { - return this.getActiveView === 'tickets' || this.getActiveView === 'ticket'; + this.$router.push(link); } } }; diff --git a/web/src/components/Timeline.vue b/web/src/components/Timeline.vue index d56ae3f..71b8bd9 100644 --- a/web/src/components/Timeline.vue +++ b/web/src/components/Timeline.vue @@ -25,8 +25,8 @@
- -
diff --git a/web/src/components/TimelineMail.vue b/web/src/components/TimelineMail.vue index df780d3..8a92a8c 100644 --- a/web/src/components/TimelineMail.vue +++ b/web/src/components/TimelineMail.vue @@ -13,13 +13,13 @@

- +
- + diff --git a/web/src/main.js b/web/src/main.js index 597802a..d23f011 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -34,17 +34,14 @@ import { faComment, faEnvelope, faUser, - faComments, - faArchive, - faMinus, + faComments } from '@fortawesome/free-solid-svg-icons'; import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'; import vueDebounce from 'vue-debounce'; library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList, - faWindowClose, faCamera, faStop, faPen, faCheck, faTimes, faSave, faEye, faComment, faUser, faComments, faEnvelope, - faArchive, faMinus); + faWindowClose, faCamera, faStop, faPen, faCheck, faTimes, faSave, faEye, faComment, faUser, faComments, faEnvelope); Vue.component('font-awesome-icon', FontAwesomeIcon); //import VueQRCodeComponent from 'vue-qrcode-component' diff --git a/web/src/router.js b/web/src/router.js index 08ac822..f612b09 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -1,6 +1,7 @@ import Items from './views/Items'; import Boxes from './views/Boxes'; import Files from './views/Files'; +import Events from './views/Events'; import Error from './views/Error'; import HowTo from './views/HowTo'; import VueRouter from 'vue-router'; @@ -12,51 +13,25 @@ import Tickets from "@/views/Tickets.vue"; import Ticket from "@/views/Ticket.vue"; import Admin from "@/views/admin/Admin.vue"; import store from "@/store"; -import Empty from "@/views/Empty.vue"; -import Events from "@/views/admin/Events.vue"; -import AccessControl from "@/views/admin/AccessControl.vue"; Vue.use(VueRouter); const routes = [ {path: '/', redirect: '/Camp23/items', meta: {requiresAuth: false}}, - {path: '/login/', name: 'login', component: Login, meta: {requiresAuth: false}}, - {path: '/register/', name: 'register', component: Register, meta: {requiresAuth: false}}, - {path: '/howto/', name: 'howto', component: HowTo, meta: {requiresAuth: true}}, - {path: '/:event/items/', name: 'items', component: Items, meta: - {requiresAuth: true, requiresPermission: 'view_item'}}, - {path: '/:event/item/:uid/', name: 'item', component: Items, meta: - {requiresAuth: true, requiresPermission: 'view_item'}}, - {path: '/:event/boxes/', name: 'boxes', component: Boxes, meta: - {requiresAuth: true, requiresPermission: 'view_container'}}, - {path: '/:event/box/:uid/', name: 'box', component: Boxes, meta: - {requiresAuth: true, requiresPermission: 'view_container'}}, - {path: '/:event/tickets/', name: 'tickets', component: Tickets, meta: - {requiresAuth: true, requiresPermission: 'view_issuethread'}}, - {path: '/:event/ticket/:id/', name: 'ticket', component: Ticket, meta: - {requiresAuth: true, requiresPermission: 'view_issuethread'}}, - {path: '/admin/', component: Admin, meta: - {requiresAuth: true, requiresPermission: 'delete_event'}, - children: [ - { - path: 'files/', name: 'files', component: Files, meta: - {requiresAuth: true, requiresPermission: 'delete_event'} - }, - { - path: 'events/', name: 'events', component: Events, meta: - {requiresAuth: true, requiresPermission: 'delete_event'} - }, - { - path: '', name: 'admin', component: Debug, meta: - {requiresAuth: true, requiresPermission: 'delete_event'} - }, - { - path: 'users/', name: 'users', component: AccessControl, meta: - {requiresAuth: true, requiresPermission: 'delete_event'} - }, - ] - }, - {path: '/user', name: 'user', component: Empty, meta: {requiresAuth: true}}, + {path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}}, + {path: '/register', name: 'register', component: Register, meta: {requiresAuth: false}}, + {path: '/howto', name: 'howto', component: HowTo, meta: {requiresAuth: true}}, + {path: '/:event/boxes', name: 'boxes', component: Boxes, meta: {requiresAuth: true}}, + {path: '/:event/items', name: 'items', component: Items, meta: {requiresAuth: true}}, + {path: '/:event/box/:uid', name: 'box', component: Boxes, meta: {requiresAuth: true}}, + {path: '/:event/item/:uid', name: 'item', component: Items, meta: {requiresAuth: true}}, + {path: '/:event/tickets', name: 'tickets', component: Tickets, meta: {requiresAuth: true}}, + {path: '/:event/ticket/:id', name: 'ticket', component: Ticket, meta: {requiresAuth: true}}, + {path: '/admin', name: 'admin', component: Admin, meta: {requiresAuth: true}}, + {path: '/admin/files', name: 'files', component: Files, meta: {requiresAuth: true}}, + {path: '/admin/events', name: 'events', component: Events, meta: {requiresAuth: true}}, + {path: '/admin/debug', name: 'debug', component: Debug, meta: {requiresAuth: true}}, + {path: '/admin/users', name: 'users', component: Events, meta: {requiresAuth: true}}, {path: '*', component: Error}, ]; @@ -82,16 +57,11 @@ const router = new VueRouter({ router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !store.getters.isLoggedIn) { - console.log("Not logged in, redirecting to login page") + //console.log("Not logged in, redirecting to login page") next({ name: 'login', query: {redirect: to.fullPath}, }) - } else if (to.meta.requiresPermission && !store.getters.checkPermission(to.params.event || "*", to.meta.requiresPermission)) { - console.log("Not enough permissions, redirecting to empty page") - next({ - path: '/user', - }) } else { next() } diff --git a/web/src/store/index.js b/web/src/store/index.js index 4a2cc35..2edd9ff 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -63,18 +63,14 @@ const store = new Vuex.Store({ events: [], layout: 'cards', loadedItems: [], - itemCache: {}, loadedBoxes: [], toasts: [], tickets: [], - users: [], - groups: [], + userRoles: ['admin', 'team', 'orga', 'user'], lastEvent: localStorage.getItem('lf_lastEvent') || '36C3', lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'), remember: false, user: null, - password: null, - userPermissions: [], token: null, token_expiry: null, local_loaded: false, @@ -84,13 +80,11 @@ const store = new Vuex.Store({ getActiveView: state => state.route.name || 'items', getFilters: state => state.route.query, getBoxes: state => state.loadedBoxes, - checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`), - hasPermissions: state => state.userPermissions.length > 0, + checkRole: state => role => state.userRoles.includes(role), isLoggedIn(state) { if (!state.local_loaded) { state.remember = localStorage.getItem('remember') === 'true' state.user = localStorage.getItem('user') - state.userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]') state.token = localStorage.getItem('token') state.token_expiry = localStorage.getItem('token_expiry') state.local_loaded = true @@ -117,9 +111,6 @@ const store = new Vuex.Store({ replaceLoadedItems(state, newItems) { state.loadedItems = newItems; }, - setItemCache(state, {slug, items}) { - state.itemCache[slug] = items; - }, setLayout(state, layout) { state.layout = layout; }, @@ -139,12 +130,6 @@ const store = new Vuex.Store({ replaceTickets(state, tickets) { state.tickets = tickets; }, - replaceUsers(state, users) { - state.users = users; - }, - replaceGroups(state, groups) { - state.groups = groups; - }, updateTicket(state, updatedTicket) { const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0]; Object.assign(ticket, updatedTicket); @@ -167,14 +152,6 @@ const store = new Vuex.Store({ if (user) localStorage.setItem('user', user); }, - setPassword(state, password) { - state.password = password; - }, - setPermissions(state, permissions) { - state.userPermissions = permissions; - if (permissions) - localStorage.setItem('permissions', JSON.stringify(permissions)); - }, setToken(state, {token, expiry}) { state.token = token; state.token_expiry = expiry; @@ -186,11 +163,9 @@ const store = new Vuex.Store({ state.user = null; state.token = null; localStorage.removeItem('user'); - localStorage.removeItem('permissions'); localStorage.removeItem('token'); localStorage.removeItem('token_expiry'); - if (router.currentRoute.name !== 'login') - router.push('/login'); + router.push('/login'); }, }, actions: { @@ -206,7 +181,6 @@ const store = new Vuex.Store({ if (data.token) { commit('setToken', data); commit('setUser', username); - commit('setPassword', password); axios.defaults.headers.common['Authorization'] = `Token ${data.token}`; dispatch('afterLogin'); return true; @@ -220,46 +194,39 @@ const store = new Vuex.Store({ }, async reloadToken({commit, state}) { try { - if (data.password) { - const data = await fetch('/api/2/login/', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({username: state.user, password: state.password}), - credentials: 'omit' - }).then(r => r.json()) - if (data.token) { - commit('setToken', data); - axios.defaults.headers.common['Authorization'] = `Token ${data.token}`; - return true; - } + const data = await fetch('/api/2/login/', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({username: state.user, password: state.token}), + credentials: 'omit' + }).then(r => r.json()) + if (data.token) { + commit('setToken', data); + axios.defaults.headers.common['Authorization'] = `Token ${data.token}`; + return true; } } catch (e) { console.error(e); } //credentials failed, logout store.commit('logout'); + router.push('/login'); }, async afterLogin({dispatch}) { - const boxes = dispatch('loadBoxes'); - const items = dispatch('loadEventItems'); - const tickets = dispatch('loadTickets'); - const user = dispatch('loadUserInfo'); - await Promise.all([boxes, items, tickets, user]); + await dispatch('loadBoxes'); + await dispatch('loadEventItems'); + await dispatch('loadTickets'); }, async fetchImage({state}, url) { return await fetch(url, {headers: {'Authorization': `Token ${state.token}`}}); }, - async loadUserInfo({commit}) { - const {data} = await axios.get('/2/self/'); - commit('setUser', data.username); - commit('setPermissions', data.permissions); - }, async loadEvents({commit}) { const {data} = await axios.get('/2/events/'); commit('replaceEvents', data); }, changeEvent({dispatch, getters, commit}, eventName) { router.push({path: `/${eventName.slug}/${getters.getActiveView}/`}); + commit('replaceLoadedItems', []); dispatch('loadEventItems'); }, changeView({getters}, link) { @@ -268,16 +235,10 @@ const store = new Vuex.Store({ showBoxContent({getters}, box) { router.push({path: `/${getters.getEventSlug}/items/`, query: {box}}); }, - async loadEventItems({commit, getters, state}) { + async loadEventItems({commit, getters}) { try { - commit('replaceLoadedItems', []); - const slug = getters.getEventSlug; - if (slug in state.itemCache) { - commit('replaceLoadedItems', state.itemCache[slug]); - } - const {data} = await axios.get(`/2/${slug}/items/`); + const {data} = await axios.get(`/2/${getters.getEventSlug}/items/`); commit('replaceLoadedItems', data); - commit('setItemCache', {slug, items: data}); } catch (e) { console.error("Error loading items"); } @@ -298,7 +259,7 @@ const store = new Vuex.Store({ commit('updateItem', data); }, async markItemReturned({commit, getters}, item) { - await axios.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}); + await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}); commit('removeItem', item); }, async deleteItem({commit, getters}, item) { @@ -317,19 +278,7 @@ const store = new Vuex.Store({ async sendMail({commit, dispatch}, {id, message}) { const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message}); await dispatch('loadTickets'); - }, - async postManualTicket({commit, dispatch}, {sender, message, title,}) { - const {data} = await axios.post(`/2/tickets/manual/`, {name: title, sender, body: message, recipient: 'mail@c3lf.de'}); - await dispatch('loadTickets'); - }, - async loadUsers({commit}) { - const {data} = await axios.get('/2/users/'); - commit('replaceUsers', data); - }, - async loadGroups({commit}) { - const {data} = await axios.get('/2/groups/'); - commit('replaceGroups', data); - }, + } } }); diff --git a/web/src/views/Empty.vue b/web/src/views/Empty.vue deleted file mode 100644 index 4c7c3eb..0000000 --- a/web/src/views/Empty.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/web/src/views/Error.vue b/web/src/views/Error.vue index 73e63e5..9d8cbad 100644 --- a/web/src/views/Error.vue +++ b/web/src/views/Error.vue @@ -1,18 +1,5 @@ + + \ No newline at end of file diff --git a/web/src/views/admin/AccessControl.vue b/web/src/views/admin/AccessControl.vue deleted file mode 100644 index 339e35a..0000000 --- a/web/src/views/admin/AccessControl.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/views/admin/Admin.vue b/web/src/views/admin/Admin.vue index dd66a5d..0dab6cc 100644 --- a/web/src/views/admin/Admin.vue +++ b/web/src/views/admin/Admin.vue @@ -3,22 +3,21 @@
-
- -
-
- -
+

Admin

+
    +
  • + Debug +
  • +
  • + Boxes +
  • +
  • + Events +
  • +
  • + Users +
  • +
@@ -27,11 +26,9 @@ + + \ No newline at end of file