diff --git a/config/settings/common.py b/config/settings/common.py index e5d6630..fb60be4 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -620,3 +620,5 @@ ST_UPLOAD_FILE_ENABLED = True ST_TESTS_RATELIMIT_NEVER_EXPIRE = False ST_BASE_DIR = os.path.dirname(__file__) # ST is Spirit forum software config + +RFID_SECRET = env("RFID_SECRET") \ No newline at end of file diff --git a/config/urls.py b/config/urls.py index 184df25..58db4e2 100644 --- a/config/urls.py +++ b/config/urls.py @@ -38,7 +38,7 @@ router.register(r'categories', CategoryViewSet, base_name='categories') router.register(r'feeds', FeedViewSet, 'feeds') router.register(r'articles', ArticleViewSet, base_name='articles') router.register(r'rfid', DeviceViewSet, base_name='rfid_device') -router.register(r'rfidAuth', AuthUserWithDeviceViewSet, base_name='device_auth') +router.register(r'rfid_auth', AuthUserWithDeviceViewSet, base_name='device_auth') sitemaps = { diff --git a/mhackspace/base/management/commands/generate_test_data.py b/mhackspace/base/management/commands/generate_test_data.py index 999fb74..4b9e341 100644 --- a/mhackspace/base/management/commands/generate_test_data.py +++ b/mhackspace/base/management/commands/generate_test_data.py @@ -14,7 +14,7 @@ from mhackspace.base.models import BannerImage from mhackspace.feeds.models import Article, Feed from mhackspace.users.models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES from mhackspace.blog.models import Category, Post -from mhackspace.rfid.models import Device, DeviceAuth +from mhackspace.rfid.models import Device class ImageFixture(AutoFixture): @@ -80,7 +80,6 @@ class Command(BaseCommand): Rfid.objects.all().delete() Device.objects.all().delete() - DeviceAuth.objects.all().delete() rfid = AutoFixture( Rfid, @@ -94,9 +93,6 @@ class Command(BaseCommand): }) device.create(5) - deviceauth = AutoFixture(DeviceAuth) - deviceauth.create(5) - feed = AutoFixture(Feed) feed.create(10) diff --git a/mhackspace/rfid/admin.py b/mhackspace/rfid/admin.py index f530735..dec41f5 100644 --- a/mhackspace/rfid/admin.py +++ b/mhackspace/rfid/admin.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- from django.contrib import admin from django.contrib.admin import ModelAdmin -from django.forms.models import ModelChoiceField -from mhackspace.rfid.models import Device, DeviceAuth +from mhackspace.rfid.models import Device, AccessLog @admin.register(Device) @@ -11,12 +10,7 @@ class DeviceAdmin(ModelAdmin): list_display = ('name', 'identifier') -@admin.register(DeviceAuth) -class DeviceAuthAdmin(ModelAdmin): - list_display = ('device', 'rfid_code', 'rfid_user', 'device_id') - - def rfid_code(self, x): - return x.rfid.code - - def rfid_user(self, x): - return x.rfid.user +@admin.register(AccessLog) +class AccessLogAdmin(ModelAdmin): + list_display = ('rfid', 'device', 'success') + list_filter = ('success',) diff --git a/mhackspace/rfid/migrations/0002_rfid_users.py b/mhackspace/rfid/migrations/0002_rfid_users.py new file mode 100644 index 0000000..7851dc5 --- /dev/null +++ b/mhackspace/rfid/migrations/0002_rfid_users.py @@ -0,0 +1,54 @@ +# Generated by Django 2.1.1 on 2018-10-17 17:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0012_rfid_users'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('rfid', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AccessLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('success', models.BooleanField()), + ], + ), + migrations.RemoveField( + model_name='deviceauth', + name='device', + ), + migrations.RemoveField( + model_name='deviceauth', + name='rfid', + ), + migrations.RemoveField( + model_name='device', + name='members', + ), + migrations.AddField( + model_name='device', + name='users', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + migrations.DeleteModel( + name='DeviceAuth', + ), + migrations.AddField( + model_name='accesslog', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rfid.Device'), + ), + migrations.AddField( + model_name='accesslog', + name='rfid', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Rfid'), + ), + ] diff --git a/mhackspace/rfid/models.py b/mhackspace/rfid/models.py index 52b38ed..56f3acf 100644 --- a/mhackspace/rfid/models.py +++ b/mhackspace/rfid/models.py @@ -8,31 +8,23 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from mhackspace.users.models import Rfid -# just brainstorming so we can start playing with this, -# be nice to make this a 3rd party django installable app ? -# description of a device like door, print, laser cutter + class Device(models.Model): - # user = models.ManyToMany(settings.AUTH_USER_MODEL) - name = models.CharField(_('Device name'), max_length=255) identifier = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + users = models.ManyToManyField(settings.AUTH_USER_MODEL) + name = models.CharField(_('Device name'), max_length=255) description = models.CharField(_('Short description of what the device does'), blank=True, max_length=255) added_date = models.DateTimeField(default=timezone.now, editable=False) - members = models.ManyToManyField(Rfid, through='DeviceAuth') def __str__(self): return self.name -# http://stackoverflow.com/questions/4443190/djangos-manytomany-relationship-with-additional-fields -# many to many, lookup user from rfid model then get there user_id and the device to check if auth allowed -class DeviceAuth(models.Model): - rfid = models.ForeignKey( - Rfid, - on_delete=models.CASCADE - ) +class AccessLog(models.Model): + rfid = models.ForeignKey(Rfid, on_delete=models.CASCADE) + device = models.ForeignKey(Device, on_delete=models.CASCADE) + success = models.BooleanField() - device = models.ForeignKey( - Device, - on_delete=models.CASCADE - ) + def __str__(self): + return f"{self.rfid.user} {self.device}" diff --git a/mhackspace/rfid/serializers.py b/mhackspace/rfid/serializers.py index 9f718c6..da5ce98 100644 --- a/mhackspace/rfid/serializers.py +++ b/mhackspace/rfid/serializers.py @@ -19,6 +19,5 @@ class DeviceSerializer(serializers.ModelSerializer): class AuthSerializer(serializers.Serializer): name = serializers.CharField(max_length=255) rfid = serializers.CharField(max_length=255) - # device = serializers.UUIDField(format='hex_verbose') device = serializers.CharField(max_length=255) diff --git a/mhackspace/rfid/tests/tests.py b/mhackspace/rfid/tests/tests.py index 2f5b24b..8f6cf1d 100644 --- a/mhackspace/rfid/tests/tests.py +++ b/mhackspace/rfid/tests/tests.py @@ -1,13 +1,11 @@ import sys -import requests - from io import StringIO from django.core.management import call_command from test_plus.test import TestCase from rest_framework.test import APIRequestFactory from rest_framework.test import RequestsClient -from mhackspace.rfid.models import Device, DeviceAuth +from mhackspace.rfid.models import Device from mhackspace.users.models import User, Rfid @@ -24,7 +22,6 @@ class MigrationTestCase(TestCase): class ApiTests(TestCase): - maxDiff = None def setUp(self): self.user = User(name='User01') self.user.save() @@ -34,8 +31,6 @@ class ApiTests(TestCase): self.device.save() self.rfid = Rfid(code='1', user=self.user) self.rfid.save() - self.auth = DeviceAuth(rfid=self.rfid, device=self.device) - self.auth.save() def testAuth(self): factory = APIRequestFactory() diff --git a/mhackspace/rfid/views.py b/mhackspace/rfid/views.py index 3bb9a05..2920534 100644 --- a/mhackspace/rfid/views.py +++ b/mhackspace/rfid/views.py @@ -1,15 +1,19 @@ import logging +import jwt +from jwt import ExpiredSignatureError from rest_framework.response import Response from rest_framework import viewsets from rest_framework import status from django.views.generic import ListView, DeleteView, CreateView from django.contrib.auth.mixins import LoginRequiredMixin +from django.conf import settings +from mhackspace.base.tasks import matrix_message from mhackspace.users.models import Rfid -from mhackspace.rfid.models import Device, DeviceAuth +from mhackspace.rfid.models import Device, AccessLog from mhackspace.rfid.serializers import DeviceSerializer, AuthSerializer -from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.core.exceptions import ObjectDoesNotExist logger = logging.getLogger(__name__) @@ -31,18 +35,31 @@ class AuthUserWithDeviceViewSet(viewsets.ViewSet): def post(self, request, format=None): try: - rfid = Rfid.objects.get(code=request.data.get('rfid')) - device = Device.objects.get(identifier=request.data.get('device')) - deviceAuth = DeviceAuth.objects.get(device=device.identifier, rfid=rfid.id) + data = jwt.decode(request.data["data"], settings.RFID_SECRET, algorithms=['HS256']) + except ExpiredSignatureError: + data = jwt.decode(request.data["data"], settings.RFID_SECRET, algorithms=['HS256'], verify=False) + logger.warn(f"Signature expired for {data.get('rfid_code')} on device {data.get('device_id')}") + return Response(jwt.encode({"authenticated": False}, settings.RFID_SECRET), status=status.HTTP_403_FORBIDDEN) + + if data.get("rfid_code") is None or data.get("rfid_code") is None: + return Response(status=status.HTTP_400_BAD_REQUEST) + # print(data) + try: + rfid = Rfid.objects.get(code=data["rfid_code"]) + device = Device.objects.get(identifier=data["device_id"]) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) - except ValidationError as e: - # except: - # logger.exception("An error occurred") - return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) - serializer = AuthSerializer( - instance={'name': device.name, 'rfid': rfid.code, 'device': device.identifier}) - return Response(serializer.data, status=200) + + try: + member = device.users.get(pk=rfid.user_id) + matrix_message.delay( + f"{member.username} has just entered {device.name}" + ) + AccessLog.objects.create(rfid=rfid, device=device, success=True) + return Response(jwt.encode({"authenticated": True, "username": member.username}, settings.RFID_SECRET)) + except ObjectDoesNotExist: + AccessLog.objects.create(rfid=rfid, device=device, success=False) + return Response(jwt.encode({"authenticated": False}, settings.RFID_SECRET), status=status.HTTP_403_FORBIDDEN) class RfidCardsListView(LoginRequiredMixin, ListView): diff --git a/mhackspace/users/migrations/0012_rfid_users.py b/mhackspace/users/migrations/0012_rfid_users.py new file mode 100644 index 0000000..094dbe6 --- /dev/null +++ b/mhackspace/users/migrations/0012_rfid_users.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.1 on 2018-10-17 17:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0011_auto_20181011_0752'), + ] + + operations = [ + migrations.AlterField( + model_name='membership', + name='payment_date', + field=models.DateTimeField(default=None), + ), + ] diff --git a/mhackspace/users/models.py b/mhackspace/users/models.py index cbab539..b3a64d2 100644 --- a/mhackspace/users/models.py +++ b/mhackspace/users/models.py @@ -125,7 +125,7 @@ class Membership(models.Model): class Rfid(models.Model): - code = models.CharField(max_length=7) + code = models.CharField(max_length=7, unique=True) description = models.CharField( _("Short rfid description"), blank=True, max_length=255 ) diff --git a/requirements/base.txt b/requirements/base.txt index 56beaab..3090bb3 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -100,3 +100,4 @@ ldap3==2.5.1 bcrypt==3.1.4 python-twitter==3.4.2 feedparser==5.2.1 +PyJWT==1.6.4 \ No newline at end of file