diff --git a/authserver/authserver/api/models.py b/authserver/authserver/api/models.py index bb46088..ef7ed8c 100644 --- a/authserver/authserver/api/models.py +++ b/authserver/authserver/api/models.py @@ -31,8 +31,8 @@ class Profile(models.Model): return self.user.username class Card(models.Model): - profile = models.OneToOneField(Profile, on_delete=models.CASCADE, editable=False) - number = models.CharField(max_length=512) # TODO: normalize + profile = models.ForeignKey(Profile, related_name='cards', on_delete=models.CASCADE) + number = models.CharField(max_length=10) def __str__(self): return self.number diff --git a/authserver/authserver/api/serializers.py b/authserver/authserver/api/serializers.py index 57e1aff..1861f86 100644 --- a/authserver/authserver/api/serializers.py +++ b/authserver/authserver/api/serializers.py @@ -32,11 +32,7 @@ class ToolDataSerializer(serializers.HyperlinkedModelSerializer): class ProfileSerializer(serializers.HyperlinkedModelSerializer): user = serializers.StringRelatedField() - card = serializers.SlugRelatedField( - allow_null=True, - slug_field='number', - queryset=models.Card.objects.all() - ) + cards = serializers.StringRelatedField(many=True, read_only=True) authorized_tools = serializers.SlugRelatedField( many=True, slug_field='slug', diff --git a/authserver/authserver/api/views.py b/authserver/authserver/api/views.py index 6907a7c..b4eb48c 100644 --- a/authserver/authserver/api/views.py +++ b/authserver/authserver/api/views.py @@ -1,5 +1,8 @@ -import requests +import base64 import json +import requests +import struct +import time from django.contrib.auth.models import User @@ -11,6 +14,9 @@ from rest_framework.response import Response from . import models, serializers from authserver.settings import PROTOSPACE_LOGIN_PAGE +LOG_DIRECTORY = '/var/log/pslockout' +VALID_TIME = 1000000000 + class IsLockoutAdmin(permissions.BasePermission): def has_permission(self, request, view): try: @@ -65,7 +71,9 @@ def login(request): if res.status_code == requests.codes.ok: return Response({'error': 'Invalid Credentials'}, status=status.HTTP_404_NOT_FOUND) - user, created = User.objects.get_or_create(username=username) + lockout_username = username.replace('.', '') + + user, created = User.objects.get_or_create(username=lockout_username) user.set_password(password) # not validated user.save() @@ -95,13 +103,78 @@ def update_cards(request): for username, card_numbers in data.items(): try: - profile = models.Profile.objects.get(user__username=username) - card, _ = models.Card.objects.update_or_create( - profile=profile, - defaults={'number': card_numbers} - ) - if card: updated_count += 1 - except: + lockout_username = username.replace('.', '') + profile = models.Profile.objects.get(user__username=lockout_username) + except models.Profile.DoesNotExist: continue + for card_number in card_numbers.split(','): + card, _ = models.Card.objects.get_or_create( + profile=profile, + number=card_number + ) + if card: updated_count += 1 + return Response({'updated': updated_count}, status=status.HTTP_200_OK) + +EVENTS = [ + 'LOG_BOOT_UP - Booted up =============================================', + 'LOG_INIT_COMPLETE - Initialization completed', + 'LOG_WIFI_CONNECTED - Wifi connected', + 'LOG_WIFI_DISCONNECTED - Wifi disconnected', + 'LOG_COMM_LOCK_ARM - Received arm request over web', + 'LOG_COMM_LOCK_DISARM - Received disarm request over web', + 'LOG_COMM_LOCK_FAIL - Lock status communication failed, code: ', + 'LOG_COMM_CARD_FAIL - Card list communication failed, code: ', + 'LOG_COMM_INFO_FAIL - Info log communication failed, code: ', + 'LOG_LOCK_OFF - Lock turned off', + 'LOG_LOCK_ARMED - Lock armed', + 'LOG_LOCK_TIMEOUT - Lock arming timed out', + 'LOG_LOCK_ON - Lock turned on', + 'LOG_LOCK_DISARMED - Lock disarmed', + 'LOG_LOCK_ERROR - Button held while arming lock', + 'LOG_CARD_GOOD_READ - Successful read from card: ', + 'LOG_CARD_ACCEPTED - Accepted card: ', + 'LOG_CARD_DENIED - Denied card: ', +] + + +@api_view(["POST"]) +def infolog(request, mac): + entries_processed = 0 + oldest_valid_log_time = time.time() + + encoded_log = request.data.get('log') + if encoded_log: + decoded_log = base64.b64decode(encoded_log) + unpacked_log = list(struct.iter_unpack(' VALID_TIME: + oldest_valid_log_time = entry[0] + + with open(LOG_DIRECTORY + '/devices/' + mac + '.log', 'a') as log_file: + for entry in unpacked_log: + # if time is obviously wrong, just use oldest valid log time or now + entry_time = entry[0] if entry[0] > VALID_TIME else oldest_valid_log_time + entry_time_string = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(entry_time)) + + entry_event = EVENTS[entry[1]] + entry_data = entry[2].decode('utf-8').strip('\0') + + if entry_data: + user = models.User.objects.filter(profile__cards__number=entry_data) + if user.count(): + entry_data += ' (' + str(user.first()) + ')' + + entry_string = '{} - {} - {}{}'.format(entry_time_string, mac, entry_event, entry_data) + + entries_processed += 1 + log_file.write(entry_string + '\n') + + response_object = { + 'unixTime': int(time.time()), + 'processed': entries_processed, + } + + return Response(response_object, status=status.HTTP_200_OK) diff --git a/authserver/authserver/urls.py b/authserver/authserver/urls.py index 1d67cf7..48ab365 100644 --- a/authserver/authserver/urls.py +++ b/authserver/authserver/urls.py @@ -35,7 +35,8 @@ urlpatterns = [ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), url(r'^login/', views.login), url(r'^cards/(?P.*)/', views.cards), - url(r'^update-cards/', views.update_cards) + url(r'^update-cards/', views.update_cards), + url(r'^infolog/(?P.*)/', views.infolog), ] if settings.DEBUG is True: diff --git a/authserver/parselog_example.py b/authserver/parselog_example.py new file mode 100644 index 0000000..4083748 --- /dev/null +++ b/authserver/parselog_example.py @@ -0,0 +1,51 @@ +import base64 +import struct +import time + +EVENTS = [ + 'LOG_BOOT_UP - Booted up', + 'LOG_INIT_COMPLETE - Initialization completed', + 'LOG_WIFI_CONNECTED - Wifi connected', + 'LOG_WIFI_DISCONNECTED - Wifi disconnected', + 'LOG_COMM_LOCK_FAIL - Lock status communication failed, code: ', + 'LOG_COMM_CARD_FAIL - Card list communication failed, code: ', + 'LOG_COMM_INFO_FAIL - Info log communication failed, code: ', + 'LOG_LOCK_OFF - Lock turned off', + 'LOG_LOCK_ARMED - Lock armed', + 'LOG_LOCK_TIMEOUT - Lock armed timed out', + 'LOG_LOCK_ON - Lock turned on', + 'LOG_LOCK_CANCEL - Lock armed cancelled', + 'LOG_LOCK_ERROR - Lock armed button error', + 'LOG_CARD_GOOD_READ - Successful read from card: ', + 'LOG_CARD_ACCEPTED - Accepted card: ', + 'LOG_CARD_DENIED - Denied card: ', + ] + +encoded = "AAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAABgAAAAIAAAAAAAAAAAAABwAAAAQ1MDMAAAAAAAAA0whEXA0wNzAwQjU2MUMx0whEXA8wNzAwQjU2MUMx1AhEXA0wNzAwQjU2MUMx1AhEXA8wNzAwQjU2MUMx6whEXA0wNzAwQjU2MUMx6whEXA8wNzAwQjU2MUMx7AhEXA0wNzAwQjU2MUMx7AhEXA8wNzAwQjU2MUMx7ghEXA0wNzAwQjU2MUMx7ghEXA8wNzAwQjU2MUMx7whEXA0wNzAwQjU2MUMx7whEXA8wNzAwQjU2MUMx8AhEXA0wNzAwQjU2MUMx8AhEXA8wNzAwQjU2MUMx8QhEXA0wNzAwQjU2MUMx8QhEXA8wNzAwQjU2MUMx8QhEXA0wNzAwQjU2MUMx8QhEXA8wNzAwQjU2MUMx8ghEXA0wNzAwQjU2MUMx8ghEXA8wNzAwQjU2MUMx8whEXA0wNzAwQjU2MUMx8whEXA8wNzAwQjU2MUMx8whEXA0wNzAwQjU2MUMx8whEXA8wNzAwQjU2MUMx9AhEXA0wNzAwQjU2MUMx9AhEXA8wNzAwQjU2MUMx9QhEXA0wNzAwQjU2MUMx9QhEXA8wNzAwQjU2MUMx9QhEXA0wNzAwQjU2MUMx9QhEXA8wNzAwQjU2MUMx9ghEXA0wNzAwQjU2MUMx9ghEXA8wNzAwQjU2MUMx9whEXA0wNzAwQjU2MUMx9whEXA8wNzAwQjU2MUMx9whEXA0wNzAwQjU2MUMx9whEXA8wNzAwQjU2MUMx+AhEXA0wNzAwQjU2MUMx+AhEXA8wNzAwQjU2MUMx+AhEXA0wNzAwQjU2MUMx+AhEXA8wNzAwQjU2MUMx+QhEXA0wNzAwQjU2MUMx+QhEXA8wNzAwQjU2MUMx+whEXA0wNzAwQjU2MUMx+whEXA8wNzAwQjU2MUMx/AhEXA0wNzAwQjU2MUMx/AhEXA8wNzAwQjU2MUMx/QhEXA0wNzAwQjU2MUMx/QhEXA8wNzAwQjU2MUMx/ghEXA0wNzAwQjU2MUMx/ghEXA8wNzAwQjU2MUMx/whEXA0wNzAwQjU2MUMx/whEXA8wNzAwQjU2MUMxAAlEXA0wNzAwQjU2MUMxAAlEXA8wNzAwQjU2MUMxAQlEXA0wNzAwQjU2MUMxAQlEXA8wNzAwQjU2MUMxAglEXA0wNzAwQjU2MUMxAglEXA8wNzAwQjU2MUMxAwlEXA0wNzAwQjU2MUMxAwlEXA8wNzAwQjU2MUMxAwlEXA0wNzAwQjU2MUMxAwlEXA8wNzAwQjU2MUMxBQlEXA0wNzAwQjU2MUMxBQlEXA8wNzAwQjU2MUMxBglEXAY1MDAAAAAAAAAABglEXA0wNzAwQjU2MUMxBglEXA8wNzAwQjU2MUMxBwlEXA0wNzAwQjU2MUMxBwlEXA8wNzAwQjU2MUMxBwlEXA0wNzAwQjU2MUMxBwlEXA8wNzAwQjU2MUMxCAlEXA0wNzAwQjU2MUMxCAlEXA8wNzAwQjU2MUMxCQlEXA0wNzAwQjU2MUMxCQlEXA8wNzAwQjU2MUMxCQlEXA0wNzAwQjU2MUMxCQlEXA8wNzAwQjU2MUMxCQlEXA0wNzAwQjU2MUMxCQlEXA8wNzAwQjU2MUMxCglEXA0wNzAwQjU2MUMxCglEXA8wNzAwQjU2MUMxCwlEXA0wNzAwQjU2MUMxCwlEXA8wNzAwQjU2MUMxDAlEXA0wNzAwQjU2MUMxDAlEXA8wNzAwQjU2MUMxDAlEXA0wNzAwQjU2MUMx" + +while True: + + try: + + decoded = base64.b64decode(encoded) + + unpacked = list(struct.iter_unpack(' 1000000 else int(time.time()) + entry_time_string = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(entry_time)) + entry_event = EVENTS[entry[1]] + entry_data = entry[2].decode('ascii') + + entry_string = entry_time_string + ' - ' + entry_event + entry_data + + print(entry_string) + print(len(unpacked)) + break + + except: + print('bad parse') + encoded = encoded[:-1]