Select Git revision
views.py 15.03 KiB
import collections
import datetime
import json
import os
import socket
import re
import hashlib
from django.conf import settings
from django.db.models import Q
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import SuspiciousOperation
from django.db import transaction
from django.http import HttpResponse, HttpResponseNotAllowed, JsonResponse
from django.http import QueryDict
from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.generic import TemplateView
from .models import Host, Exercise, Request, Room, ExerciseOption
from user.models import Profile
def load_secret():
'''
Communication between hosts and server is protected by a secret to prevent
malicious users from injecting invalid status updates.
'''
secret = cache.get('secret_hosts')
if secret is None:
with open(os.path.join(settings.BASE_DIR, 'secret_hosts'), 'r') as f:
secret = f.read().strip()
cache.set('secret_hosts', secret)
return secret
def remote_cip_hostname(request):
# "faui00x" in "faui00x.cs.fau.de".
try:
hostname = socket.gethostbyaddr(
request.META['REMOTE_ADDR'])[0].split('.')[0]
return re.sub('-win$', '', hostname) #drop -win from windows cip hosts
except (socket.gaierror, socket.herror):
return ""
def _get_rooms(public=True):
if public:
rooms = Room.objects.filter(order__gte=0)
else:
rooms = Room.objects.all()
return rooms
@ensure_csrf_cookie
def index(request, room):
room_filtered = Room.objects.filter(name=room)
if len(room_filtered) == 1:
room = room_filtered[0]
else:
return redirect('index')
return render(request, 'server/index.html', {'room': room, 'rooms': _get_rooms()})
@ensure_csrf_cookie
def optin(request):
return render(request, 'server/optin.html', {'rooms': _get_rooms()})
def faq(request):
return render(request, 'server/faq.html', {'rooms': _get_rooms()})
def privacypolicy(request):
return render(request, 'server/privacypolicy.html', {'rooms': _get_rooms()})
def legalnotice(request):
return render(request, 'server/legalnotice.html', {'rooms': _get_rooms()})
def cipmap_beamer_user(request):
response = redirect('index-room', room='cip2')
response.set_cookie(key='cipmap-beamer-user', value=True, secure=True)
return response
# Disable CSRF-checks to allow automated POST requests to update the state.
@csrf_exempt
def hosts(request):
if request.method == 'GET':
hostname = remote_cip_hostname(request)
result = {
'hostname': hostname,
}
for host in Host.objects.all():
result[host.host_name] = {
'name': host.user_name,
'gecos': host.user_gecos,
'idle': host.user_idle,
'occupied': host.occupied,
'update': int(host.last_update.timestamp()),
}
result['exercises_today'] = _get_lectures_today()
result['temperatures'] = _get_temperatures()
indent = None
if settings.DEBUG:
indent = 4
return JsonResponse(result, json_dumps_params={'indent': indent})
elif request.method == 'POST':
try:
name = request.POST['name']
gecos = request.POST['gecos']
idle = request.POST['idle']
occupied = request.POST['occupied'].lower() == "true"
secret = request.POST['secret']
except KeyError:
raise SuspiciousOperation('missing fields')
# Some hosts are behind NAT and use this header.
hostname = None
try:
hostname = request.POST['host']
except KeyError:
pass
if secret != load_secret():
raise SuspiciousOperation('invalid secret')
if hostname is None:
hostname = remote_cip_hostname(request)
with transaction.atomic():
host = Host.objects.get_or_create(host_name=hostname,
defaults={'occupied': occupied, 'user_name': "", 'user_gecos': ""})[0]
# User no longer logged in, delete all requests for this host.
# This breaks if someone logs out and someone else logs in faster than we get
# the update, but we can't don anything against it.
if not occupied and host.occupied != occupied:
Request.objects.filter(host=hostname).delete()
host.user_name = name
host.user_gecos = gecos
host.user_idle = idle
host.occupied = occupied
host.save()
return HttpResponse()
else:
return HttpResponseNotAllowed(['GET', 'POST'])
def __hash_username(username):
return hashlib.sha512((username + username).encode()).hexdigest()
def __request_type_to_field(request_type_text):
# It's okay (and wanted!) that it raises a KeyError on invalid inputs
text_to_num = {v: k for k, v in Request.REQUEST_CHOICES}
return text_to_num[request_type_text]
def __check_allowance_of_requesttype(request_type, exercise_options):
# raises exception if not allowed, passes otherwise
if request_type == Request.NORMAL_REQUEST:
if not exercise_options.use_requests:
raise SuspiciousOperation('normal requests are not allowed by this exercise')
elif request_type == Request.SUBMISSION_REQUEST:
if not exercise_options.use_submission_list:
raise SuspiciousOperation('submission requests are not allowed by this exercise')
elif request_type == Request.SPECIAL_REQUEST:
if not exercise_options.use_special_list:
raise SuspiciousOperation('special requests are not allowed by this exercise')
else:
raise SuspiciousOperation('unimplemented requesttype')
@csrf_exempt
def request(request):
'''
Create and delete requests.
'''
hostname = remote_cip_hostname(request)
host = get_object_or_404(Host, host_name=hostname)
put = QueryDict(request.body)
try:
exercise_id = put.get('exercise')
exercise = get_object_or_404(Exercise, id=exercise_id)
except KeyError:
raise SuspiciousOperation('missing fields')
# get type of request
try:
req_type = __request_type_to_field(put['request_type'])
except KeyError:
raise SuspiciousOperation('invalid request type')
# there must be a graphically logged in user at this host!
if not host.occupied:
raise SuspiciousOperation('unoccupied host')
# and or delete requests now.
if request.method == 'PUT':
# Check if exercise happens at the moment
now = datetime.datetime.now()
if not (now.weekday() == exercise.weekday and
exercise.start <= now.time() <= exercise.end):
raise SuspiciousOperation('invalid request time')
# Check if Exercise uses requests at all
try:
exercise_options = ExerciseOption.objects.get(exercise_name=exercise.name)
except ObjectDoesNotExist:
raise SuspiciousOperation('requests not allowed by exercise')
# check if this kind of request is allowed by the exercise
__check_allowance_of_requesttype(req_type, exercise_options)
if exercise_options.use_highprior:
# only evaluate username if exercise is highprior
# does only affect normal requests on webinterface
try:
user = request.META['REMOTE_USER']
except KeyError:
user = "WindowsCIP"
priority = exercise.exerciseuser_set.\
filter(name=user).exists()
else:
priority = False
# Check if user is okay with saving username for statistics
allow_statistic = request.COOKIES.get('allow_statistic')
if allow_statistic == "true":
try:
user = request.META['REMOTE_USER']
except KeyError:
user = "WindowsCIP"
hashed_username = __hash_username(user)
else:
hashed_username = "NOT_ALLOWED"
with transaction.atomic():
r, created = Request.objects.get_or_create(
defaults={'priority': False, 'username': 'NOT_ALLOWED', 'request_type': req_type},
host=hostname, exercise=exercise,
)
if created:
r.username=hashed_username # set statisticrequest
r.priority = priority
r.save()
else:
pass #otherwise, request was send twice, just ignore it.
elif request.method == 'DELETE':
Request.objects.filter(
host=hostname,
exercise=exercise).delete()
else:
return HttpResponseNotAllowed(['PUT', 'DELETE'])
return _requests(request, send_hostname=hostname)
def requests(request):
'''
Provide data about current requests.
'''
hostname = remote_cip_hostname(request)
return _requests(request, send_hostname=hostname)
def _requests(request, send_hostname=None):
data = {
'age': int(datetime.datetime.now().timestamp()),
'requests': collections.defaultdict(list),
'exercises': collections.defaultdict(list),
'hostname': send_hostname,
}
for r in Request.objects.\
prefetch_related('exercise', 'exercise__room').all():
exercise = r.exercise
data['requests'][exercise.room.name].append({
'host': r.host,
'time': int(r.time.timestamp()),
'priority': r.priority,
'request_type': r.request_type,
'exercise': exercise.id,
})
data['exercises'] = _get_lectures_now()
data['exercises_today'] = _get_lectures_today()
data['configuration'] = _get_lecture_configuration()
data['temperatures'] = _get_temperatures()
indent = None
if settings.DEBUG:
indent = 4
return JsonResponse(data, json_dumps_params={'indent': indent})
def _get_temperatures():
temperatures = {}
for room in Room.objects.all():
temperatures[room.name] = cache.get('temp_' + room.name)
if temperatures[room.name] is None:
if room.temp_hosts == '':
temperatures[room.name] = 'N/A'
else:
try:
temps = []
for thost in room.temp_hosts.split(','):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((thost + '.cs.fau.de', 16842))
temp_float = float(s.recv(1024).decode().strip())
temps.append(str(round(temp_float, 1)))
temperatures[room.name] = '/'.join(temps) + ' °C'
except Exception as e:
temperatures[room.name] = 'N/A'
cache.set('temp_' + room.name, temperatures[room.name], 10)
return temperatures
def _get_lectures_now():
ex_today = _get_lectures_today()
ex_now = {}
time_now = datetime.datetime.now().time()
for r in ex_today:
ex_now[r] = [k for k in ex_today[r] if k['display_start'] <= time_now < k['display_end']]
return ex_now
def _get_lectures_today():
exercises = collections.defaultdict(list)
for r in _get_rooms(False):
exercises[r.name] = []
# exercises which use at least one of our request-lists
cipmap_lectures = set(ExerciseOption.objects.filter(
Q(use_requests=True) |
Q(use_submission_list=True) |
Q(use_special_list=True)
).values_list('exercise_name', flat=True))
weekday = datetime.datetime.now().weekday()
for e in Exercise.objects.select_related('room').filter(weekday=weekday):
exercises[e.room.name].append({
'id': e.id,
'name': e.name,
'active': e.name in cipmap_lectures,
'request_start': e.start,
'request_end': e.end,
'display_start': e.display_start,
'display_end': e.display_end,
})
for r in exercises:
exercises[r].sort(key=lambda x: (-x['active'], x['name']))
return exercises
def _get_lecture_configuration():
configuration = {}
for l in ExerciseOption.objects.all():
d = {k: getattr(l, k) for k in ['use_requests', 'keep_requests',
'use_submission_list',
'use_special_list']}
if d['use_requests']:
d['use_highprior'] = l.use_highprior
if d['use_special_list']:
d['special_list_name'] = l.special_list_name
d['special_list_button'] = l.special_list_button
configuration[l.exercise_name] = d
return configuration
def map(request):
map_file = open(settings.MAP_PATH).read()
map_json = json.loads(map_file)
map_json['buero'] = {
'computer': [
{
'x': 0,
'y': 0,
'id': 'faui03a',
},
{
'x': 1,
'y': 0,
'id': 'faui03b',
},
{
'x': 2,
'y': 0,
'id': 'faui03c',
},
{
'x': 3,
'y': 0,
'id': 'faui03d',
},
{
'x': 4,
'y': 0,
'id': 'faui03e',
},
{
'x': 5,
'y': 0,
'id': 'faui03f',
},
{
'x': 0,
'y': 1,
'id': 'faui03g',
},
{
'x': 1,
'y': 1,
'id': 'faui03h',
},
{
'x': 2,
'y': 1,
'id': 'faui03l',
},
{
'x': 3,
'y': 1,
'id': 'faui03m',
},
{
'x': 4,
'y': 1,
'id': 'faui03n',
}
],
'door': {
'position': 0,
'offset': -42,
}
}
map_json['fsi'] = {
'computer': [
{ # FSI Inf
'x': 0,
'y': 0,
'id': 'faui03i',
},
{ # FSI CE
'x': 2,
'y': 0,
'id': 'faui03j',
},
{ # FSI MT
'x': 4,
'y': 0,
'id': 'faui03k',
}
],
'door': {
'position': 0,
'offset': -42,
}
}
# Insert room names
for room in _get_rooms(False):
map_json[room.name]['name'] = room.name_long
map_json[room.name]['number'] = room.name_number
indent = None
if settings.DEBUG:
indent = 4
context = {'map_data': json.dumps(map_json, indent=indent)}
return render(request, 'server/map.js', context, 'application/javascript')