server.py 20.5 KB
Newer Older
1
from flask import Flask, g, request, url_for, redirect, session, render_template, flash, Response, make_response
2
from werkzeug.routing import Rule
3
from functools import wraps
Julian Rother's avatar
Julian Rother committed
4
from datetime import date, timedelta, datetime, time, MINYEAR
5
import os
6
import sys
Julian Rother's avatar
Julian Rother committed
7
import hashlib
8
import random
9
import traceback
10
import string
11
from socket import gethostname
Julian Rother's avatar
Julian Rother committed
12
from ipaddress import ip_address, ip_network
13
import math
Julian Rother's avatar
Julian Rother committed
14
import locale
15
import base64
16
import json
Julian Rother's avatar
Julian Rother committed
17
18

locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
19

20
app = Flask(__name__)
21

22
config = app.config
23
config.from_pyfile('config.py.example', silent=True)
24
25
26
if sys.argv[0].endswith('run.py'): 
	config['SQLITE_INIT_DATA'] = True
	config['DEBUG'] = True
27
config.from_pyfile('config.py', silent=True)
Andreas Valder's avatar
Andreas Valder committed
28
29
30
31
32
33
34
35
36
37
if sys.argv[0].endswith('tests.py'):
	print('running in test mode')
	import tempfile
	# ensure we always use a clean sqlite db for tests
	config['DB_ENGINE'] = 'sqlite'
	config['SQLITE_DB'] = tempfile.mktemp(prefix='flasktestingtmp')
	print('DB File: {}'.format(config['SQLITE_DB']))
	config['SQLITE_INIT_DATA'] = True
	config['SQLITE_INIT_SCHEMA'] = True
	config['DEBUG'] = True
38
	config['DISABLE_SCHEDULER'] = True
39
	config['JOBS_API_KEY'] = '1'
Andreas Valder's avatar
Andreas Valder committed
40
41
if config['DEBUG']:
	app.jinja_env.auto_reload = True
Andreas Valder's avatar
Andreas Valder committed
42

43
44
if not config.get('SECRET_KEY', None):
	config['SECRET_KEY'] = os.urandom(24)
Julian Rother's avatar
Julian Rother committed
45

46
mod_endpoints = []
Julian Rother's avatar
Julian Rother committed
47

48
def mod_required(func):
49
	mod_endpoints.append(func.__name__)
50
51
	@wraps(func)
	def decorator(*args, **kwargs):
52
		if not ismod():
53
54
55
56
57
58
			flash('Diese Funktion ist nur für Moderatoren verfügbar!')
			return redirect(url_for('login', ref=request.url))
		else:
			return func(*args, **kwargs)
	return decorator

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def evalperm(perms):
	cperms = []
	lperms = []
	vperms = []
	for perm in perms:
		if perm['course_id']:
			cperms.append(perm)
		elif perm['lecture_id']:
			lperms.append(perm)
		elif perm['video_id']:
			vperms.append(perm)
	if vperms:
		return vperms
	elif lperms:
	 	return lperms
	elif cperms:
		return cperms
	return [{'type': 'public'}]
77

78

Julian Rother's avatar
Julian Rother committed
79
from db import query, modify, show, searchquery
80
from template_helper import *
Julian Rother's avatar
Julian Rother committed
81
82
83
84
85
from mail import notify_mods, notify_admins
from ldap import ldapauth
from legacy import legacy_index
from scheduler import sched_func

Julian Rother's avatar
Cleanup    
Julian Rother committed
86
87
88
89
def render_endpoint(endpoint, flashtext=None, **kargs):
	if flashtext:
		flash(flashtext)
	# request.endpoint is used for navbar highlighting
90
	request.url_rule = Rule(request.path, endpoint=endpoint)
Julian Rother's avatar
Cleanup    
Julian Rother committed
91
92
	return app.view_functions[endpoint](**kargs)

93
94
95
96
97
98
99
def handle_errors(endpoint, text, code, *errors, **epargs):
	def wrapper(func):
		@wraps(func)
		def decorator(*args, **kwargs):
			try:
				return func(*args, **kwargs)
			except errors:
Julian Rother's avatar
Julian Rother committed
100
				if endpoint:
101
					return make_response(render_endpoint(endpoint, text, **epargs), code)
Julian Rother's avatar
Julian Rother committed
102
103
				else:
					return text, code
104
105
106
		return decorator
	return wrapper

Julian Rother's avatar
Cleanup    
Julian Rother committed
107
@app.errorhandler(404)
108
@app.route('/invalidpath')
Julian Rother's avatar
Julian Rother committed
109
def handle_not_found(e=None):
110
	return render_endpoint('index', 'Diese Seite existiert nicht!'), 404
Julian Rother's avatar
Cleanup    
Julian Rother committed
111

112
113
114
115
@app.errorhandler(500)
@app.errorhandler(Exception)
def handle_internal_error(e):
	traceback.print_exc()
116
	notify_admins('endpoint_exception', traceback=traceback.format_exc())
117
	return render_template('500.html', online=True), 500
118

119
120
121
122
123
124
125
126
127
128
@sched_func(5*60, firstdelay=0)
def dump_error_page():
	if 'ERROR_PAGE' not in config:
		return
	request.url_rule = Rule(request.path, endpoint='handle_internal_error')
	text = render_template('500.html')
	f = open(config['ERROR_PAGE'], 'w')
	f.write(text)
	f.close()

129
def genlive(streams):
130
131
132
133
134
135
	for stream in streams:
		stream['visible'] = True
		stream['downloadable'] = False
		stream['path'] = 'pub/hls/%s.m3u8'%stream['livehandle']
		stream['file_size'] = 0
	return streams
136
137


138
@app.route('/')
139
@register_navbar('Home', icon='home')
140
def index():
141
	# handle legacy urls...
142
143
144
	result = legacy_index()
	if result:
		return result
145

146
	start = date.today()
147
	end = start + timedelta(days=7)
148
	upcomming = query('''
149
		SELECT lectures.*, streams.active AS nowlive, "course" AS sep, courses.*
Andreas Valder's avatar
Andreas Valder committed
150
151
		FROM lectures
		JOIN courses ON (lectures.course_id = courses.id)
152
		LEFT JOIN streams ON lectures.id = streams.lecture_id
153
154
		WHERE (time > ?) AND (time < ?) AND (? OR (lectures.visible AND courses.visible AND courses.listed)) AND NOT lectures.norecording
		ORDER BY time ASC LIMIT 30''', start, end, ismod())
Andreas Valder's avatar
Andreas Valder committed
155
156
157
	for i in upcomming:
		i['date'] = i['time'].date()
	latestvideos=query('''
158
		SELECT lectures.*, "course" AS sep, courses.*
Andreas Valder's avatar
Andreas Valder committed
159
160
161
162
163
		FROM lectures
		LEFT JOIN videos ON (videos.lecture_id = lectures.id)
		LEFT JOIN courses on (courses.id = lectures.course_id)
		WHERE (? OR (courses.visible AND courses.listed AND lectures.visible AND videos.visible))
		GROUP BY videos.lecture_id
164
		ORDER BY MAX(videos.time_created) DESC
Andreas Valder's avatar
Andreas Valder committed
165
		LIMIT 6	''',ismod())
166
	livestreams = query('''SELECT streams.handle AS livehandle, lectures.*, "course" AS sep, courses.*
167
168
169
170
171
		FROM streams
		JOIN lectures ON lectures.id = streams.lecture_id
		JOIN courses ON courses.id = lectures.course_id
		WHERE streams.active AND (? OR (streams.visible AND courses.visible AND courses.listed AND lectures.visible))
		''', ismod())
Julian Rother's avatar
Julian Rother committed
172
	featured = query('SELECT * FROM featured WHERE (? OR visible) ORDER BY `order`', ismod())
173
	featured = list(filter(lambda x: not x['deleted'], featured))
Julian Rother's avatar
Julian Rother committed
174
175
176
177
	for item in featured:
		if item['type'] == 'courses':
			if item['param'] not in ['title', 'semester', 'organizer', 'subject']:
				continue
178
			item['courses'] = query('SELECT * FROM courses WHERE (visible AND listed) AND `%s` = ? ORDER BY `%s`'%(item['param'], item['param']), item['param2'])
Julian Rother's avatar
Julian Rother committed
179
180
		elif item['type'] == 'video':
			item['lecture'] = {'id': item['param']}
181
			streams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
Julian Rother's avatar
Julian Rother committed
182
183
184
185
186
187
188
189
190
191
192
					FROM streams
					JOIN lectures ON lectures.id = streams.lecture_id
					JOIN formats ON formats.keywords = "hls"
					WHERE streams.active AND streams.visible AND lectures.id = ?
					''', item['param'])
			item['videos'] = query('''
					SELECT videos.*, "formats" AS sep, formats.*
					FROM videos
					JOIN formats ON (videos.video_format = formats.id)
					WHERE videos.lecture_id = ? AND videos.visible
					ORDER BY formats.prio DESC
193
					''', item['param'])+genlive(streams)
194
	return render_template('index.html', latestvideos=livestreams+latestvideos, upcomming=upcomming, featured=featured)
195

196
@app.route('/courses')
197
@register_navbar('Videos', icon='film')
198
def courses():
199
	courses = query('SELECT * FROM courses WHERE (? OR (visible AND listed)) ORDER BY lower(semester), lower(title)', ismod())
200
	chapters = {}
201
	for i in query('SELECT lectures.course_id AS id, COUNT(chapters.id) AS c FROM chapters JOIN lectures ON chapters.lecture_id = lectures.id WHERE NOT chapters.visible AND NOT chapters.deleted GROUP BY lectures.course_id'):
202
		chapters[i['id']] = i['c']
203
	for course in courses:
204
		course['chapter_count'] = chapters.get(course['id'], 0)
205
206
		if course['semester'] == '':
			course['semester'] = 'zeitlos'
Andreas Valder's avatar
Andreas Valder committed
207
	groupedby = request.args.get('groupedby')
Julian Rother's avatar
Julian Rother committed
208
	if groupedby not in ['title', 'semester', 'organizer', 'subject']:
Andreas Valder's avatar
Andreas Valder committed
209
		groupedby = 'semester'
210
	return render_template('courses.html', courses=courses, groupedby=groupedby)
Andreas Valder's avatar
Andreas Valder committed
211

212
213
@app.route('/<handle>')
@app.route('/<int:id>')
214
@handle_errors('courses', 'Diese Veranstaltung existiert nicht!', 404, IndexError)
215
216
def course(id=None, handle=None):
	if id:
217
		course = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', id, ismod())[0]
218
	else:
219
		course = query('SELECT * FROM courses WHERE handle = ? AND (? OR visible)', handle, ismod())[0]
220
221
	course['perm'] = query('SELECT * FROM perm WHERE (NOT perm.deleted) AND course_id = ? ORDER BY type', course['id'])
	perms = query('SELECT perm.* FROM perm JOIN lectures ON (perm.lecture_id = lectures.id) WHERE (NOT perm.deleted) AND lectures.course_id = ? ORDER BY perm.type', course['id'])
222
	chapters = {}
Andreas Valder's avatar
Andreas Valder committed
223
	for i in query('SELECT lectures.id AS id, COUNT(chapters.id) AS c FROM chapters JOIN lectures ON chapters.lecture_id = lectures.id WHERE lectures.course_id = ? AND NOT chapters.visible AND NOT chapters.deleted GROUP BY chapters.lecture_id;', course['id']):
224
		chapters[i['id']] = i['c']
225
	lectures = query('SELECT * FROM lectures WHERE course_id = ? AND (? OR visible) ORDER BY time, duration DESC', course['id'], ismod())
226
	for lecture in lectures:
227
		lecture['perm'] = []
228
		lecture['perm'] += course['perm']
229
		lecture['course'] = course
230
		lecture['chapter_count'] = chapters.get(lecture['id'], 0)
231
232
233
		for perm in perms:
			if perm['lecture_id'] == lecture['id']:
				lecture['perm'].append(perm)
Andreas Valder's avatar
Andreas Valder committed
234
	videos = query('''
235
			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
Andreas Valder's avatar
Andreas Valder committed
236
237
238
239
240
241
			FROM videos
			JOIN lectures ON (videos.lecture_id = lectures.id)
			JOIN formats ON (videos.video_format = formats.id)
			JOIN courses ON (lectures.course_id = courses.id)
			WHERE lectures.course_id= ? AND (? OR videos.visible)
			ORDER BY lectures.time, formats.prio DESC
242
			''', course['id'], ismod())
243
	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
244
245
246
247
248
			FROM streams
			JOIN lectures ON lectures.id = streams.lecture_id
			JOIN formats ON formats.keywords = "hls"
			WHERE streams.active AND (? OR streams.visible) AND lectures.course_id = ?
			''', ismod(), course['id'])
249
250
251
	chapters = []
	if course['coursechapters']:
		chapters = query('SELECT chapters.* FROM chapters JOIN lectures ON lectures.id = chapters.lecture_id WHERE lectures.course_id = ? AND NOT chapters.deleted AND chapters.visible ORDER BY time ASC', course['id'])
252
	videos += genlive(livestreams)
253
254
255
256
257
258
	responsible = query('''SELECT users.*, responsible.course_id AS responsible
			FROM users
			LEFT JOIN responsible ON (responsible.user_id = users.id AND responsible.course_id = ?)
			WHERE users.fsacc != "" AND users.level > 0
			ORDER BY responsible DESC, users.realname ASC''', course['id'])
	return render_template('course.html', course=course, lectures=lectures, videos=videos, chapters=chapters, responsible=responsible)
Andreas Valder's avatar
Andreas Valder committed
259

Andreas Valder's avatar
Andreas Valder committed
260
@app.route('/faq')
261
@register_navbar('FAQ', icon='question-sign')
Andreas Valder's avatar
Andreas Valder committed
262
def faq():
263
	return render_template('faq.html')
Andreas Valder's avatar
Andreas Valder committed
264

265
266
267
268
@app.route('/<course>/<int:id>')
@app.route('/<int:courseid>/<int:id>')
@app.route('/<course>/<int:id>/embed', endpoint='embed')
@app.route('/<int:courseid>/<int:id>/embed', endpoint='embed')
269
@handle_errors('course', 'Diese Vorlesung existiert nicht!', 404, IndexError)
270
def lecture(id, course=None, courseid=None):
Andreas Valder's avatar
Andreas Valder committed
271
272
	lecture = query('SELECT * FROM lectures WHERE id = ? AND (? OR visible)', id, ismod())[0]
	videos = query('''
273
			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
Andreas Valder's avatar
Andreas Valder committed
274
275
			FROM videos
			JOIN formats ON (videos.video_format = formats.id)
276
277
278
279
			JOIN courses ON (courses.id = ?)
			WHERE videos.lecture_id = ? AND (? OR videos.visible)
			ORDER BY formats.prio DESC
			''', lecture['course_id'], lecture['id'], ismod())
280
	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
281
282
283
284
285
286
			FROM streams
			JOIN lectures ON lectures.id = streams.lecture_id
			JOIN formats ON formats.keywords = "hls"
			WHERE streams.active AND (? OR streams.visible) AND lectures.id = ?
			''', ismod(), id)
	videos += genlive(livestreams)
287
	perms = query('SELECT perm.* FROM perm WHERE ((NOT perm.deleted) AND (perm.lecture_id = ? OR perm.course_id = ?))',
288
			lecture['id'], lecture['course_id'])
289
	if not videos:
290
291
292
293
294
295
		if lecture['live'] and lecture['time'] > datetime.now()-timedelta(minutes=30) and lecture['time']-timedelta(hours=20) < datetime.now():
			flash('Der Livestream beginnt um '+human_time(lecture['time'])+' Uhr.')
		elif lecture['time'] > datetime.now():
			flash('Diese Vorlesung hat noch nicht stattgefunden!')
		else:
			flash('Zu dieser Vorlesung wurden noch keine Videos veröffentlicht!')
296
	courses = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', lecture['course_id'], ismod())
297
298
	if not courses:
		return render_endpoint('courses', 'Diese Veranstaltung existiert nicht!'), 404
299
	chapters = query('SELECT * FROM chapters WHERE lecture_id = ? AND NOT deleted AND (? OR visible) ORDER BY time ASC', id, ismod())
300
301
302
303
304
	username = password = None
	if request.authorization:
		username = request.authorization.username
		password = request.authorization.password
	if not checkperm(perms, username=username, password=password):
305
		mode, text = permdescr(perms)
306
		if mode == 'rwth':
307
			flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_rwthauth')+'">Hier authorisieren</a>.', category='player')
308
		elif mode == 'l2p':
309
			if 'l2p_courses' in session:
310
				flash(text+'. Du bist kein Teilnehmer des L2P-Kurses! <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Kurse aktualisieren</a>.', category='player')
311
			else:
312
				flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Hier authorisieren</a>.', category='player')
313
		else:
314
			flash(text+'.', category='player')
315
	return render_template('embed.html' if request.endpoint == 'embed' else 'lecture.html', course=courses[0], lecture=lecture, videos=videos, chapters=chapters, seek=request.args.get('t'))
Andreas Valder's avatar
Andreas Valder committed
316

317
318
319
320
321
322
323

@app.route('/search')
def search():
	if 'q' not in request.args:
		return redirect(url_for('index'))
	q = request.args['q']
	courses = searchquery(q, '*', ['title', 'short', 'organizer', 'subject', 'description'],
324
			'courses', 'WHERE (? OR (visible AND listed)) GROUP BY id ORDER BY _score DESC, semester DESC LIMIT 20', ismod())
325
	lectures = searchquery(q, 'lectures.*, courses.visible AS coursevisible, courses.listed, courses.id AS courses_id, courses.visible AS courses_visible, courses.listed AS courses_listed, courses.title AS courses_title, courses.short AS courses_short, courses.handle AS courses_handle, courses.organizer AS courses_organizer, courses.subject AS courses_subject, courses.credits AS courses_credits, courses.created_by AS courses_created_by, courses.time_created AS courses_time_created, courses.time_updated AS courses_time_updated, courses.semester AS courses_semester, courses.downloadable AS courses_downloadable, courses.embedinvisible AS courses_embedinvisible, courses.description AS courses_description, courses.internal AS courses_internal',
326
327
			['lectures.title', 'lectures.comment', 'lectures.speaker', 'courses.short'],
			'lectures LEFT JOIN courses on (courses.id = lectures.course_id)',
328
			'WHERE (? OR (coursevisible AND listed AND visible)) GROUP BY id ORDER BY _score DESC, time DESC LIMIT 30', ismod())
Julian Rother's avatar
Julian Rother committed
329
330
331
332
333
	for lecture in lectures:
		lecture['course'] = {}
		for key in lecture:
			if key.startswith('courses_'):
				lecture['course'][key[8:]] = lecture[key]
334
	return render_template('search.html', searchtext=request.args['q'], courses=courses, lectures=lectures)
Andreas Valder's avatar
Andreas Valder committed
335

336
def check_mod(user, groups):
337
338
339
340
341
342
	if not user:
		return False
	for group in config['LDAP_GROUPS']:
		if group in groups:
			return True
	return False
343

344
@app.route('/internal/login', methods=['GET', 'POST'])
Julian Rother's avatar
Julian Rother committed
345
def login():
346
347
	if request.method == 'GET':
		return render_template('login.html')
348
349
	userinfo, groups = ldapauth(request.form.get('user'), request.form.get('password'))
	user = userinfo.get('uid')
350
	if not check_mod(user, groups):
351
		flash('Login fehlgeschlagen!')
Andreas Valder's avatar
Andreas Valder committed
352
		return make_response(render_template('login.html'), 403)
353
	session['user'] = userinfo
354
355
	dbuser = query('SELECT * FROM users WHERE name = ?', user)
	if not dbuser:
Julian Rother's avatar
Julian Rother committed
356
		modify('INSERT INTO users (name, realname, fsacc, level, calendar_key, rfc6238) VALUES (?, ?, ?, 1, "", "")', user, session['user']['givenName'], user)
357
358
		dbuser = query('SELECT * FROM users WHERE name = ?', user)
	session['user']['dbid'] = dbuser[0]['id']
359
	session['_csrf_token'] = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(64))
Andreas Valder's avatar
Andreas Valder committed
360
	session.permanent = True
Julian Rother's avatar
Julian Rother committed
361
	return redirect(request.values.get('ref', url_for('index')))
Julian Rother's avatar
Julian Rother committed
362

363
@app.route('/internal/logout', methods=['GET', 'POST'])
Julian Rother's avatar
Julian Rother committed
364
def logout():
365
	session.pop('user', None)
Julian Rother's avatar
Julian Rother committed
366
	return redirect(request.values.get('ref', url_for('index')))
Julian Rother's avatar
Julian Rother committed
367

368
@app.route('/internal/auth')
369
370
371
372
def auth(): # For use with nginx auth_request
	if 'X-Original-Uri' not in request.headers:
		return 'Internal Server Error', 500
	url = request.headers['X-Original-Uri'].lstrip(config['VIDEOPREFIX'])
Julian Rother's avatar
Julian Rother committed
373
374
	if request.cookies.get('tracking', '') and request.cookies['tracking'].isdigit():
		cookie = int(request.cookies['tracking'])
375
	else:
Julian Rother's avatar
Julian Rother committed
376
		cookie = random.getrandbits(8*8-1)
377
	if url.endswith('jpg') or ismod():
378
		return "OK", 200
379
380
	if url.startswith('pub/hls/'):
		handle = url[len('pub/hls/'):].split('_')[0].split('.')[0]
381
		perms = query('''SELECT lectures.id AS lecture, perm.*
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
				FROM streams
				JOIN lectures ON (streams.lecture_id = lectures.id)
				JOIN courses ON (lectures.course_id = courses.id)
				LEFT JOIN perm ON ((lectures.id = perm.lecture_id OR courses.id = perm.course_id) AND NOT perm.deleted)
				WHERE streams.handle = ?
				AND (courses.visible AND lectures.visible AND streams.visible)
				ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''', handle)
	else:
		perms = query('''SELECT videos.path, videos.id AS vid, perm.*
				FROM videos
				JOIN lectures ON (videos.lecture_id = lectures.id)
				JOIN courses ON (lectures.course_id = courses.id)
				LEFT JOIN perm ON ((videos.id = perm.video_id OR lectures.id = perm.lecture_id OR courses.id = perm.course_id) AND NOT perm.deleted)
				WHERE videos.path = ?
				AND (courses.visible AND lectures.visible AND videos.visible)
				ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''',
				url)
399
	if not perms:
400
		return "Not found", 404
401
	auth = request.authorization
402
403
404
405
	username = password = None
	if auth:
		username = auth.username
		password = auth.password
406
	if checkperm(perms, username=username, password=password):
407
		try:
408
409
			if not url.startswith('pub/hls/'):
				modify('INSERT INTO log (id, `time`, `date`, video, source) VALUES (?, ?, ?, ?, 1)', cookie, datetime.now(), datetime.combine(date.today(), time()), perms[0]['vid'])
410
411
412
413
			elif url.endswith('.ts'):
				fmt = url.split('_')[-1].split('-')[0]
				seg = url.split('.')[0].split('-')[-1]
				modify('INSERT INTO hlslog (id, `time`, segment, lecture, handle, format) VALUES (?, ?, ?, ?, ?, ?)', cookie, datetime.now(), seg, perms[0]['lecture'], handle, fmt)
414
415
416
		except:
			pass
		r = make_response('OK', 200)
Julian Rother's avatar
Julian Rother committed
417
		r.set_cookie('tracking', str(cookie), max_age=2147483647) # Many many years
418
		return r
419
	password_auth = False
420
421
	for perm in perms:
		if perm['type'] == 'password':
422
423
424
			password_auth = True
			break
	if password_auth:
425
426
		return Response("Login required", 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
	return "Not allowed", 403
Andreas Valder's avatar
Andreas Valder committed
427

Julian Rother's avatar
Julian Rother committed
428
429
430
431
@app.route('/files/<filename>')
def files(filename):
	return redirect(config['VIDEOPREFIX']+'/'+filename)

Andreas Valder's avatar
Andreas Valder committed
432
@app.route('/sitemap.xml')
Andreas Valder's avatar
Andreas Valder committed
433
434
435
436
437
438
439
440
441
442
def sitemap():
	pages=[]
	# static pages
	for rule in app.url_map.iter_rules():
		if 'GET' in rule.methods and len(rule.arguments)==0:
			if rule.endpoint not in mod_endpoints:
				pages.append([rule.rule])
	for i in query('select * from courses where visible and listed'):
		pages.append([url_for('course',handle=i['handle'])])
		for j in query('select * from lectures where (course_id = ? and visible)',i['id']):
443
			pages.append([url_for('lecture',course=i['handle'],id=j['id'])])
Andreas Valder's avatar
Andreas Valder committed
444
445
446
447


	return Response(render_template('sitemap.xml', pages=pages), 200, {'Content-Type': 'application/atom+xml'} )

Julian Rother's avatar
Julian Rother committed
448
@app.route('/internal/dbstatus')
449
@register_navbar('DB-Status', icon='ok', group='weitere')
Julian Rother's avatar
Julian Rother committed
450
451
452
453
454
455
456
@mod_required
def dbstatus():
	hosts = set()
	clusters = {}
	status = {}
	variables = {}
	for host in config.get('MYSQL_DBSTATUS_HOSTS', [])+[config.get('MYSQL_HOST', None)]:
Andreas Valder's avatar
Andreas Valder committed
457
458
459
460
461
		try:
			for _host in show('SHOW VARIABLES LIKE "wsrep_cluster_address"', host=host)['wsrep_cluster_address'][len('gcomm://'):].split(','):
				hosts.add(_host)
		except:
			pass
Julian Rother's avatar
Julian Rother committed
462
	for host in sorted(list(hosts)):
Julian Rother's avatar
Julian Rother committed
463
464
465
466
467
468
469
470
471
472
473
474
		try:
			status[host] = show('SHOW GLOBAL STATUS LIKE "wsrep%"', host=host)
			variables[host] = show('SHOW GLOBAL VARIABLES LIKE "wsrep%"', host=host)
		except:
			status[host] = {'wsrep_cluster_state_uuid': '', 'wsrep_local_state_comment': 'Not reachable', 'wsrep_cluster_conf_id': '0', 'wsrep_cluster_status': 'Unknown'}
			variables[host] = {'wsrep_node_name': host, 'wsrep_cluster_name': 'unknown'}
		cluster = variables[host]['wsrep_cluster_name']+'-'+status[host]['wsrep_cluster_conf_id']
		if cluster not in clusters:
			clusters[cluster] = []
		clusters[cluster].append(host)
	return render_template('dbstatus.html', clusters=clusters, statuses=status, vars=variables), 200

475
476
477
def date_json_handler(obj):
	return obj.isoformat() if hasattr(obj, 'isoformat') else obj

Julian Rother's avatar
Julian Rother committed
478
from jobs import job_handler, schedule_job, cancel_job, restart_job
479
from edit import edit_handler
Julian Rother's avatar
Julian Rother committed
480
import feeds
481
import importer
482
import stats
483
484
if 'ICAL_URL' in config:
	import meetings
485
import l2pauth
486
from encoding import schedule_remux
Julian Rother's avatar
Julian Rother committed
487
import sorter
Andreas Valder's avatar
Andreas Valder committed
488
import timetable
Andreas Valder's avatar
Andreas Valder committed
489
import chapters
490
import icalexport
491
import livestreams
492
import cutprogress