New JWT token based RFID view
This commit is contained in:
parent
fb661f32d1
commit
cb29f28a78
|
@ -620,3 +620,5 @@ ST_UPLOAD_FILE_ENABLED = True
|
||||||
ST_TESTS_RATELIMIT_NEVER_EXPIRE = False
|
ST_TESTS_RATELIMIT_NEVER_EXPIRE = False
|
||||||
ST_BASE_DIR = os.path.dirname(__file__)
|
ST_BASE_DIR = os.path.dirname(__file__)
|
||||||
# ST is Spirit forum software config
|
# ST is Spirit forum software config
|
||||||
|
|
||||||
|
RFID_SECRET = env("RFID_SECRET")
|
|
@ -38,7 +38,7 @@ router.register(r'categories', CategoryViewSet, base_name='categories')
|
||||||
router.register(r'feeds', FeedViewSet, 'feeds')
|
router.register(r'feeds', FeedViewSet, 'feeds')
|
||||||
router.register(r'articles', ArticleViewSet, base_name='articles')
|
router.register(r'articles', ArticleViewSet, base_name='articles')
|
||||||
router.register(r'rfid', DeviceViewSet, base_name='rfid_device')
|
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 = {
|
sitemaps = {
|
||||||
|
|
|
@ -14,7 +14,7 @@ from mhackspace.base.models import BannerImage
|
||||||
from mhackspace.feeds.models import Article, Feed
|
from mhackspace.feeds.models import Article, Feed
|
||||||
from mhackspace.users.models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES
|
from mhackspace.users.models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES
|
||||||
from mhackspace.blog.models import Category, Post
|
from mhackspace.blog.models import Category, Post
|
||||||
from mhackspace.rfid.models import Device, DeviceAuth
|
from mhackspace.rfid.models import Device
|
||||||
|
|
||||||
|
|
||||||
class ImageFixture(AutoFixture):
|
class ImageFixture(AutoFixture):
|
||||||
|
@ -80,7 +80,6 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
Rfid.objects.all().delete()
|
Rfid.objects.all().delete()
|
||||||
Device.objects.all().delete()
|
Device.objects.all().delete()
|
||||||
DeviceAuth.objects.all().delete()
|
|
||||||
|
|
||||||
rfid = AutoFixture(
|
rfid = AutoFixture(
|
||||||
Rfid,
|
Rfid,
|
||||||
|
@ -94,9 +93,6 @@ class Command(BaseCommand):
|
||||||
})
|
})
|
||||||
device.create(5)
|
device.create(5)
|
||||||
|
|
||||||
deviceauth = AutoFixture(DeviceAuth)
|
|
||||||
deviceauth.create(5)
|
|
||||||
|
|
||||||
feed = AutoFixture(Feed)
|
feed = AutoFixture(Feed)
|
||||||
feed.create(10)
|
feed.create(10)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin import ModelAdmin
|
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)
|
@admin.register(Device)
|
||||||
|
@ -11,12 +10,7 @@ class DeviceAdmin(ModelAdmin):
|
||||||
list_display = ('name', 'identifier')
|
list_display = ('name', 'identifier')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DeviceAuth)
|
@admin.register(AccessLog)
|
||||||
class DeviceAuthAdmin(ModelAdmin):
|
class AccessLogAdmin(ModelAdmin):
|
||||||
list_display = ('device', 'rfid_code', 'rfid_user', 'device_id')
|
list_display = ('rfid', 'device', 'success')
|
||||||
|
list_filter = ('success',)
|
||||||
def rfid_code(self, x):
|
|
||||||
return x.rfid.code
|
|
||||||
|
|
||||||
def rfid_user(self, x):
|
|
||||||
return x.rfid.user
|
|
||||||
|
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,31 +8,23 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mhackspace.users.models import Rfid
|
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):
|
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)
|
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)
|
description = models.CharField(_('Short description of what the device does'), blank=True, max_length=255)
|
||||||
added_date = models.DateTimeField(default=timezone.now, editable=False)
|
added_date = models.DateTimeField(default=timezone.now, editable=False)
|
||||||
members = models.ManyToManyField(Rfid, through='DeviceAuth')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
# http://stackoverflow.com/questions/4443190/djangos-manytomany-relationship-with-additional-fields
|
class AccessLog(models.Model):
|
||||||
# many to many, lookup user from rfid model then get there user_id and the device to check if auth allowed
|
rfid = models.ForeignKey(Rfid, on_delete=models.CASCADE)
|
||||||
class DeviceAuth(models.Model):
|
device = models.ForeignKey(Device, on_delete=models.CASCADE)
|
||||||
rfid = models.ForeignKey(
|
success = models.BooleanField()
|
||||||
Rfid,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
|
|
||||||
device = models.ForeignKey(
|
def __str__(self):
|
||||||
Device,
|
return f"{self.rfid.user} {self.device}"
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
|
|
|
@ -19,6 +19,5 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||||
class AuthSerializer(serializers.Serializer):
|
class AuthSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField(max_length=255)
|
name = serializers.CharField(max_length=255)
|
||||||
rfid = serializers.CharField(max_length=255)
|
rfid = serializers.CharField(max_length=255)
|
||||||
# device = serializers.UUIDField(format='hex_verbose')
|
|
||||||
device = serializers.CharField(max_length=255)
|
device = serializers.CharField(max_length=255)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import sys
|
import sys
|
||||||
import requests
|
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from test_plus.test import TestCase
|
from test_plus.test import TestCase
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.test import RequestsClient
|
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
|
from mhackspace.users.models import User, Rfid
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +22,6 @@ class MigrationTestCase(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class ApiTests(TestCase):
|
class ApiTests(TestCase):
|
||||||
maxDiff = None
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User(name='User01')
|
self.user = User(name='User01')
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
@ -34,8 +31,6 @@ class ApiTests(TestCase):
|
||||||
self.device.save()
|
self.device.save()
|
||||||
self.rfid = Rfid(code='1', user=self.user)
|
self.rfid = Rfid(code='1', user=self.user)
|
||||||
self.rfid.save()
|
self.rfid.save()
|
||||||
self.auth = DeviceAuth(rfid=self.rfid, device=self.device)
|
|
||||||
self.auth.save()
|
|
||||||
|
|
||||||
def testAuth(self):
|
def testAuth(self):
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import logging
|
import logging
|
||||||
|
import jwt
|
||||||
|
from jwt import ExpiredSignatureError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from django.views.generic import ListView, DeleteView, CreateView
|
from django.views.generic import ListView, DeleteView, CreateView
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
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.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 mhackspace.rfid.serializers import DeviceSerializer, AuthSerializer
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -31,18 +35,31 @@ class AuthUserWithDeviceViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
try:
|
try:
|
||||||
rfid = Rfid.objects.get(code=request.data.get('rfid'))
|
data = jwt.decode(request.data["data"], settings.RFID_SECRET, algorithms=['HS256'])
|
||||||
device = Device.objects.get(identifier=request.data.get('device'))
|
except ExpiredSignatureError:
|
||||||
deviceAuth = DeviceAuth.objects.get(device=device.identifier, rfid=rfid.id)
|
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:
|
except ObjectDoesNotExist:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
except ValidationError as e:
|
|
||||||
# except:
|
try:
|
||||||
# logger.exception("An error occurred")
|
member = device.users.get(pk=rfid.user_id)
|
||||||
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
matrix_message.delay(
|
||||||
serializer = AuthSerializer(
|
f"{member.username} has just entered {device.name}"
|
||||||
instance={'name': device.name, 'rfid': rfid.code, 'device': device.identifier})
|
)
|
||||||
return Response(serializer.data, status=200)
|
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):
|
class RfidCardsListView(LoginRequiredMixin, ListView):
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -125,7 +125,7 @@ class Membership(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Rfid(models.Model):
|
class Rfid(models.Model):
|
||||||
code = models.CharField(max_length=7)
|
code = models.CharField(max_length=7, unique=True)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
_("Short rfid description"), blank=True, max_length=255
|
_("Short rfid description"), blank=True, max_length=255
|
||||||
)
|
)
|
||||||
|
|
|
@ -100,3 +100,4 @@ ldap3==2.5.1
|
||||||
bcrypt==3.1.4
|
bcrypt==3.1.4
|
||||||
python-twitter==3.4.2
|
python-twitter==3.4.2
|
||||||
feedparser==5.2.1
|
feedparser==5.2.1
|
||||||
|
PyJWT==1.6.4
|
Loading…
Reference in New Issue