You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

184 lines
7.0 KiB

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: ',
'LOG_TEST - Test record, safe to ignore',
]
@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'}
try:
res = requests.post(PROTOSPACE_LOGIN_PAGE, post_data, allow_redirects=False, timeout=2)
except:
return Response({'error': 'Problem reaching my.protospace.ca'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
if res.status_code == requests.codes.ok:
return Response({'error': 'Invalid username / password'}, 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)