parent
ddf72a1cd1
commit
a66b8c3422
6 changed files with 207 additions and 177 deletions
@ -0,0 +1,179 @@ |
||||
import base64 |
||||
import json |
||||
import requests |
||||
import struct |
||||
import time |
||||
|
||||
from django.contrib.auth.models import User |
||||
from django.http import HttpResponse |
||||
from django.shortcuts import get_object_or_404, get_list_or_404 |
||||
|
||||
from rest_framework import mixins, permissions, status, viewsets |
||||
from rest_framework.authtoken.models import Token |
||||
from rest_framework.decorators import api_view, permission_classes |
||||
from rest_framework.response import Response |
||||
|
||||
from . import models, serializers, views |
||||
from authserver.settings import PROTOSPACE_LOGIN_PAGE, FIRMWARE_VERSION_MAGIC |
||||
|
||||
LOG_DIRECTORY = '/var/log/pslockout' |
||||
VALID_TIME = 1000000000 |
||||
|
||||
EVENTS = [ |
||||
'LOG_BOOT_UP - =========== Booted up, version: ', |
||||
'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: ', |
||||
'LOG_UPDATE_FAILED - Firmware update failed, code: ', |
||||
] |
||||
|
||||
@api_view(['POST']) |
||||
def login(request): |
||||
username = request.data.get('username').lower() |
||||
password = request.data.get('password') |
||||
if username is None or password is None: |
||||
return Response({'error': 'Please provide both username and password'}, |
||||
status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
post_data = {'user_name': username, 'web_pw': password, 'SubmitButton': 'Login'} |
||||
res = requests.post(PROTOSPACE_LOGIN_PAGE, post_data, allow_redirects=False) |
||||
if res.status_code == requests.codes.ok: |
||||
return Response({'error': 'Invalid Credentials'}, status=status.HTTP_404_NOT_FOUND) |
||||
|
||||
lockout_username = username.replace('.', '') |
||||
|
||||
user, created = User.objects.get_or_create(username=lockout_username) |
||||
user.set_password(password) # not validated |
||||
user.save() |
||||
|
||||
if created: |
||||
models.Profile.objects.create(user=user) |
||||
|
||||
token, _ = Token.objects.get_or_create(user=user) |
||||
|
||||
return Response({'token': token.key}, status=status.HTTP_200_OK) |
||||
|
||||
@api_view(['GET']) |
||||
def cards(request, mac): |
||||
tool = get_object_or_404(models.Tool, mac=mac) |
||||
cards = models.Card.objects.all().filter(profile__courses__tools=tool) |
||||
card_numbers = [card.number for card in cards] |
||||
|
||||
return Response(','.join(card_numbers), status=status.HTTP_200_OK) |
||||
|
||||
@api_view(['PUT']) |
||||
@permission_classes((views.IsLockoutAdmin,)) |
||||
def update_cards(request): |
||||
data = request.data |
||||
updated_count = 0 |
||||
|
||||
if not data: |
||||
return Response({'error': 'Please provide card data in the form username=cardnumber,cardnumber,cardnumber'}, |
||||
status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
for username, card_numbers in data.items(): |
||||
try: |
||||
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) |
||||
|
||||
@api_view(['POST']) |
||||
def infolog(request, mac): |
||||
entries_processed = 0 |
||||
oldest_valid_log_time = time.time() |
||||
|
||||
tool = get_object_or_404(models.Tool, mac=mac) |
||||
|
||||
encoded_log = request.data.get('log') |
||||
if encoded_log: |
||||
decoded_log = base64.b64decode(encoded_log) |
||||
unpacked_log = list(struct.iter_unpack('<IB10s', decoded_log)) |
||||
|
||||
for entry in unpacked_log: |
||||
if entry[0] < oldest_valid_log_time and entry[0] > 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') |
||||
|
||||
version = str(get_object_or_404(models.Firmware, tools=tool)) |
||||
version_string = '{} {} {}'.format(FIRMWARE_VERSION_MAGIC, version, FIRMWARE_VERSION_MAGIC) |
||||
|
||||
response_object = { |
||||
'processed': entries_processed, |
||||
'unixTime': int(time.time()), |
||||
'version': version_string, |
||||
} |
||||
|
||||
return Response(response_object, status=status.HTTP_200_OK) |
||||
|
||||
@api_view(['GET']) |
||||
def update(request, mac): |
||||
tool = get_object_or_404(models.Tool, mac=mac) |
||||
firmware = get_object_or_404(models.Firmware, tools=tool) |
||||
|
||||
response = HttpResponse(firmware.binary, content_type='text/plain') |
||||
response['Content-Disposition'] = 'attachment; filename=firmware_{}.bin'.format(firmware.version) |
||||
return response |
||||
|
||||
@api_view(['PUT']) |
||||
@permission_classes((permissions.IsAuthenticated,)) |
||||
def select_courses(request): |
||||
if 'courses' not in request.data: |
||||
return Response({'error': 'Please provide a list of course slugs'}, |
||||
status=status.HTTP_400_BAD_REQUEST) |
||||
courses = request.data.get('courses') |
||||
|
||||
profile = get_object_or_404(models.Profile, user=request.user) |
||||
|
||||
if profile.courses.count() or profile.selected_courses: |
||||
return Response({'error': 'Already selected courses'}, |
||||
status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
if len(courses): |
||||
course_objects = get_list_or_404(models.Course, slug__in=courses) |
||||
profile.courses.set(course_objects) |
||||
profile.selected_courses = True |
||||
profile.save() |
||||
|
||||
return Response({'updated': len(courses)}, status=status.HTTP_200_OK) |
Loading…
Reference in new issue