diff --git a/.gitignore b/.gitignore index bf258de9bbca2e8337040fe8803d97e875c4fae5..2886e55f4ebd761f44add8cccf47fa9ea63f9f85 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,11 @@ config.py __pycache__ *.sqlite +files files/* static/500.html +nginx.err.log +nginx.log +nginx.pid +nginx.conf +uwsgi.sock diff --git a/README.md b/README.md index f9d509d0e6054ff9e7bda070e3da0dde70915dc6..f4de3884bc1a08d2b00597d85b8165ca4727a2ac 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,31 @@ Hinweis: diese Variante startet eine lokale Testversion der Website, es sind nic 1. Repo Clonen 2. Verzeichnis betreten 3. (optional) config.py.example anpassen und als config.py neu speichern -(Achtung: per default werden keine Beispieldaten initialisiert und auch kein useraccount -SQLITE_INIT_DATA sollte also wenigstens beim ersten Durchlauf True sein) -4. Schauen ob alle Dependencies erfüllt sind (siehe weiter unten) +4. Schauen, ob alle Dependencies erfüllt sind (siehe weiter unten) 5. `./run.py` ausführen -6. unter [http://localhost:5000](http://localhost:5000) ist die Website verfügbar +6. Unter [http://localhost:5000](http://localhost:5000) ist die Website verfügbar 7. Moderatorlogin mit user: `videoag` Passwort: `videoag` +Alternativ, insbesondere zum Testen der Zugriffsbeschränkungen: Siehe `nginx.example.conf`. + ### Zum Mitmachen: -1. Repo zum User clonen, dafür den "Clone-Button auf der Website verwenden -2. Weiter mit 'Zum Testen' -3. Änderungen machen -4. ins eigene Repo pushen -5. Pull-Request an uns, dazu unter "Merge-Requests" einmal auf "New Merge Request" und das Private Repo auswählen. +1. Repo für den eigenen User forken, dafür den "Fork-Button" auf der Website verwenden +2. Sicherstellen, dass der Upstream richtig konfiguriert ist: +[Link](https://help.github.com/articles/configuring-a-remote-for-a-fork/) +Origin stellt hier euren User da, Upstream das Original der Gruppe videoagwebsite +3. Erstellt euch eine eigene Branch, diese könnt ihr nennen wie ihr wollt, entweder nach der Änderung oder eurem Namen (git branch username), danach switched ihr in diese Branch (git checkout username) +3. Die Initialisierung ist unter "Zum Testen" bereits erklärt worden +3. Änderungen machen, committen, upstream mergen (git fetch upstream; git merge upstream/master) +4. Ins eigene Repo pushen (git push) +5. Pull-Request an uns, dazu unter "Merge-Requests" einmal auf "New Merge Request" und das Private Repo auswählen; oder ihr geht auf euer privates repo, da taucht dann eine Benachrichtigung über einen möglichen Merge-Request auf 6. Warten 7. Wir mergen die Änderungen ### Abhängigkeiten Notwendig: -* python3 -* flask +* python (Version 3) * sqlite +* python-flask * python-requests (wird vom L2P und vom Kalenderimport verwendet, kann nicht optional eingebunden werden) Optional (wird für einzelne Features benötigt): @@ -37,4 +41,4 @@ Optional (wird für einzelne Features benötigt): * python-mysql-connector (wenn MySQL als Datenbank verwendet werden soll) Kurzform unter Ubuntu: -sudo apt install python3 python3-pip sqlite python3-requests python3-lxml python-ldap python3-icalendar; pip3 install --upgrade pip; pip3 install Flask python-mysql-connector +sudo apt install python3 python3-flask sqlite python3-requests python3-lxml python3-ldap3 python3-icalendar python3-mysql.connector diff --git a/config.py.example b/config.py.example index 40679cb0688684d2fc32a6bb904487c9fedae15b..e6fbed4dbcd23f4e19569a5bc04a0ac9d04c603a 100644 --- a/config.py.example +++ b/config.py.example @@ -1,5 +1,6 @@ # Defaults for development ,do not use in production! DEBUG = False +SERVER_IP = 'localhost' VIDEOPREFIX = 'https://videoag.fsmpi.rwth-aachen.de' VIDEOMOUNT = [{'mountpoint': 'files/protected/', 'prefix':'protected/'},{'mountpoint':'files/pub/','prefix':'pub/' }, {'mountpoint':'files/vpnonline/','prefix':'vpnonline/' }] #SECRET_KEY = 'something random' @@ -16,7 +17,7 @@ DB_DATA = 'db_example.sql' DB_ENGINE = 'sqlite' SQLITE_DB = 'db.sqlite' SQLITE_INIT_SCHEMA = True -SQLITE_INIT_DATA = False +SQLITE_INIT_DATA = True #JOBS_API_KEY = 'something random' diff --git a/nginx.conf.example b/nginx.conf.example new file mode 100644 index 0000000000000000000000000000000000000000..144cb7c29b30d7abd0e0afb06a2adc534339aba9 --- /dev/null +++ b/nginx.conf.example @@ -0,0 +1,52 @@ +# Debug-only nginx config for this website +# +# Requires system-wide installation of nginx (for /etc/nginx/*) +# +# 1. Start nginx +# nginx -c nginx.conf.example -p . +# 2. Start uwsgi +# uwsgi -s uwsgi.sock --manage-script-name --mount /=server:app --plugin python --enable-threads +# 3. Visit http://localhost:5000/ + +pid nginx.pid; +error_log nginx.err.log; + +events { + worker_connections 768; +} + +http { + access_log nginx.log; + client_body_in_file_only off; + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server { + #listen 5000; + #listen [::]:5000; + listen localhost:5000; + error_page 502 /static/500.html; + location /static/ { + root .; + } + location /files/ { + auth_request /auth; + # For use with sshfs (recommended) + #alias /mnt/videoag/srv/videoag/released/; + # For use without sshfs + proxy_pass https://videoag.fsmpi.rwth-aachen.de/; + proxy_set_header Host "videoag.fsmpi.rwth-aachen.de"; + } + location / { + include /etc/nginx/uwsgi_params; + uwsgi_param REQUEST_URI $uri; + uwsgi_param HTTP_X_ORIGINAL_URI $request_uri; + uwsgi_param HTTP_X_REAL_IP $remote_addr; + uwsgi_pass unix:uwsgi.sock; + } + } +} diff --git a/run.py b/run.py index 6c64338353d281eebed0fa5114d0d288807f4fbb..0a55cedff8789f9d055a05952cb023df8934fbc8 100755 --- a/run.py +++ b/run.py @@ -2,4 +2,4 @@ from server import * if __name__ == '__main__': - app.run(threaded=True) + app.run(threaded=True, host=config['SERVER_IP']) diff --git a/server.py b/server.py index 182f3cc43ae584cc358309def78835474ee9718d..d9726542406f850d3b3ac2b58aecf162e44c9666 100644 --- a/server.py +++ b/server.py @@ -71,7 +71,10 @@ def mod_required(func): return func(*args, **kwargs) return decorator +csrf_endpoints = [] + def csrf_protect(func): + csrf_endpoints.append(func.__name__) @wraps(func) def decorator(*args, **kwargs): if '_csrf_token' in request.values: @@ -79,13 +82,19 @@ def csrf_protect(func): elif request.get_json() and ('_csrf_token' in request.get_json()): token = request.get_json()['_csrf_token'] else: - token = none + token = None if not ('_csrf_token' in session) or (session['_csrf_token'] != token ) or not token: return 'csrf test failed', 403 else: return func(*args, **kwargs) return decorator +@app.url_defaults +def csrf_inject(endpoint, values): + if endpoint not in csrf_endpoints or not session['_csrf_token']: + return + values['_csrf_token'] = session['_csrf_token'] + def evalperm(perms): cperms = [] lperms = [] @@ -107,6 +116,8 @@ def evalperm(perms): @app.template_filter() def checkperm(perms, username=None, password=None): + if ismod(): + return True perms = evalperm(perms) for perm in perms: if perm['type'] == 'public': @@ -492,7 +503,7 @@ def auth(): # For use with nginx auth_request return 'Internal Server Error', 500 url = request.headers['X-Original-Uri'].lstrip(config['VIDEOPREFIX']) ip = request.headers.get('X-Real-IP', '') - if url.endswith('jpg'): + if url.endswith('jpg') or ismod(): return "OK", 200 perms = query('''SELECT videos.path, videos.id AS vid, perm.* FROM videos @@ -500,10 +511,9 @@ def auth(): # For use with nginx auth_request 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) WHERE videos.path = ? - AND (? OR (courses.visible AND lectures.visible AND videos.visible)) + AND (courses.visible AND lectures.visible AND videos.visible) ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''', - url, ismod()) - + url) if not perms: return "Not allowed", 403 auth = request.authorization diff --git a/templates/500.html b/templates/500.html index d47abf8fcc7729d41feb14ea71800d971b356f54..0bbc3b2c648b23784d9522d35834dde8eaa81b91 100644 --- a/templates/500.html +++ b/templates/500.html @@ -6,7 +6,10 @@ </div> <div class="row panel-body"> <div class="col-xs-12"> - Es ist ein Fehler aufgetreten. + Es ist ein interner Fehler aufgetreten. + Eventuell tritt dieser nur vorübergehend auf, versuche es doch einfach in ein paar Minuten noch einmal. + Sollte das Problem länger bestehen, schreib uns bitte eine Mail an <a href="mailto:video@fsmpi.rwth-aachen.de">video@fsmpi.rwth-aachen.de</a>. + Wir werden uns dann so schnellst möglich darum kümmern. </div> </div> </div> diff --git a/templates/base.html b/templates/base.html index 85e78c13236af4f030ec73ace821866e62b2a36e..5aee94d6c03d69f2231570cd0d323eab29e0acc8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -84,7 +84,7 @@ { html:true, title:'Login für Moderatoren', - content:'<form method="post" action="{{url_for('login', ref=request.url)}}"><input autofocus placeholder="User" name="user" type="text"><br><input placeholder="Password" name="password" type="password"><br><input type="submit" value="Login"></form>' + content:'<form method="post" action="{{url_for('login', ref=request.values.get('ref', request.url))}}"><input autofocus placeholder="User" name="user" type="text"><br><input placeholder="Password" name="password" type="password"><br><input type="submit" value="Login"></form>' } ) </script> diff --git a/templates/course.html b/templates/course.html index 25553daecab39902cf380b5c4deab33adc8a3490..3c114e1d830f938a54aa116016893d6132a5ab65 100644 --- a/templates/course.html +++ b/templates/course.html @@ -51,7 +51,7 @@ </div> <div class="panel panel-default"> <div class="panel-heading"> - <h1 class="panel-title">Videos{% if ismod() %} <a class="btn btn-default" style="margin-right: 5px;" href="{{ url_for('create', table='lectures', time=datetime.now(), title='Noch kein Titel', visible='0', course_id=course.id, ref=request.url, _csrf_token=session['_csrf_token']) }}">Neuer Termin</a><a class="btn btn-default" style="margin-right: 5px;" href="{{url_for('import_from', id=course['id'])}}">Campus Import</a>{% endif %} <a class="fa fa-rss-square pull-right" aria-hidden="true" href="{{url_for('feed', handle=course.handle)}}" style="text-decoration: none"></a> </h1> + <h1 class="panel-title">Videos{% if ismod() %} <a class="btn btn-default" style="margin-right: 5px;" href="{{ url_for('create', table='lectures', time=datetime.now(), title='Noch kein Titel', visible='0', course_id=course.id, ref=request.url) }}">Neuer Termin</a><a class="btn btn-default" style="margin-right: 5px;" href="{{url_for('import_from', id=course['id'])}}">Campus Import</a>{% endif %} <a class="fa fa-rss-square pull-right" aria-hidden="true" href="{{url_for('feed', handle=course.handle)}}" style="text-decoration: none"></a> </h1> </div> <ul class="list-group lectureslist"> {% for l in lectures %} diff --git a/templates/courses.html b/templates/courses.html index cbdb4c800cd491bd8cc4b197bfd080bccc53ef27..d1104436d2d6535d486c2faa8484c33d999d7cde 100644 --- a/templates/courses.html +++ b/templates/courses.html @@ -9,7 +9,8 @@ </li> {% if ismod() %} <li> - <a class="btn btn-default" href="{{ url_for('create', table='courses', handle='new'+(randint(0,1000)|string), title='Neue Veranstaltung', responsible=session.user.givenName, ref=request.url, _csrf_token=session['_csrf_token']) }}">Neue Veranstaltung</a> + {% set newhandle = 'new'+(randint(0,1000)|string) %} + <a class="btn btn-default" href="{{ url_for('create', table='courses', handle=newhandle, title='Neue Veranstaltung', responsible=session.user.givenName, ref=url_for('course', handle=newhandle)) }}">Neue Veranstaltung</a> </li> {% endif %} <li class="dropdown" style="padding-right: 0px"> diff --git a/templates/index.html b/templates/index.html index 6807ef804d3a10ed7fc6dd53a8dcef46e32dd8dd..22e62d09bcbce72aedd29073fc538aba00a7aeca 100644 --- a/templates/index.html +++ b/templates/index.html @@ -54,7 +54,7 @@ <div class="col-xs-12"> <ul class="list-inline pull-right"> <li style="padding-right: 0px;"> - <a class="btn btn-default" href="{{ url_for('create', table='announcements', text='Neue Ankündigung', time_publish=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0), time_expire=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)+timedelta(days=7), ref=request.url, _csrf_token=session['_csrf_token']) }}">Neue Ankündigung</a> + <a class="btn btn-default" href="{{ url_for('create', table='announcements', text='Neue Ankündigung', time_publish=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0), time_expire=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)+timedelta(days=7), ref=request.url) }}">Neue Ankündigung</a> </li> </ul> </div> diff --git a/templates/macros.html b/templates/macros.html index 8b4a7ea990ed1fc56f733f87ae356a136b93b83c..82774d13d46f39378d4725ceeec875ffd940de29 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -1,43 +1,45 @@ {% macro preview(lecture) %} <li class="list-group-item"> - <a class="hidden-xs" href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> - <div class="row"> - <img class="col-xs-4" style="max-height: 100px; width: auto;" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> - <div class="col-xs-4"> - <span><strong>{{ lecture.course.short }}</strong>{% if ismod() %} <i>({{lecture.course_id}})</i>{% endif %}</span><br> - <span>{% if ismod() %}ID: {{lecture.id}}{% endif %}</span><br> - <span>{{ lecture['time'] }}</span> + <a href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> + <div class="hidden-xs"> + <div class="row"> + <img class="col-xs-4" style="max-height: 100px; width: auto;" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> + <div class="col-xs-4"> + <span><strong>{{ lecture.course.short }}</strong>{% if ismod() %} <i>({{lecture.course_id}})</i>{% endif %}</span><br> + <span>{% if ismod() %}ID: {{lecture.id}}{% endif %}</span><br> + <span>{{ lecture['time'] }}</span> + {% if lecture['speaker'] %} + <div class="small">Gehalten von {{ lecture['speaker']|safe }} </div> + {% endif %} + </div> + <div class="col-xs-4"> + <div>{{ lecture['title']|fixnl|safe }}</div> + <p style="font-style: italic; color: #777;">{{ lecture['comment']|fixnl|safe }}</p> + </div> + </div> + </div> + <div class="visible-xs"> + <ul class="list-unstyled"> + <li> + <img style="width: 100%;" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> + </li> + <li> + <strong>{{ lecture.course.short }}</strong> {{ lecture['time'] }} + </li> {% if lecture['speaker'] %} - <div class="small">Gehalten von {{ lecture['speaker'] }} </div> + <li> + <span class="small">Gehalten von {{ lecture['speaker']|safe }} </span> + </li> {% endif %} - </div> - <div class="col-xs-4"> - <div>{{ lecture['title']|fixnl|safe }}</div> - <p style="font-style: italic; color: #777;">{{ lecture['comment']|fixnl|safe }}</p> - </div> + <li> + {{ lecture['title']|fixnl|safe }} + </li> + <li> + <p style="font-style: italic; color: #777;">{{ lecture['comment']|fixnl|safe }}</p> + </li> + </ul> </div> </a> - <a class="visible-xs" href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> - <ul class="list-unstyled"> - <li> - <img style="width: 100%;" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> - </li> - <li> - <strong>{{ lecture.course.short }}</strong> {{ lecture['time'] }} - </li> - {% if lecture['speaker'] %} - <li> - <span class="small">Gehalten von {{ lecture['speaker']|safe }} </span> - </li> - {% endif %} - <li> - {{ lecture['title']|fixnl|safe }} - </li> - <li> - <p style="font-style: italic; color: #777;">{{ lecture['comment']|fixnl|safe }}</p> - </li> - </ul> - </a> </li> {% endmacro %} @@ -60,7 +62,7 @@ $('#videoplayer').css("width"); <div class="row"> {% if show_semester %} <span class="col-xs-2 col-md-1"> - {{ course.semester }} + {{ course.semester|semester }} </span> <span class="col-xs-10 col-md-6"> {% else %}