diff --git a/.gitignore b/.gitignore index 3894248..1e42565 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ *.swo *.json *.db +config.py diff --git a/buckler_flask.py b/buckler_flask.py new file mode 100644 index 0000000..0ed5429 --- /dev/null +++ b/buckler_flask.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Session interface middlewares to integrate the flask app with Buckler. +""" +import json +import urllib.parse +import urllib.request +from datetime import datetime + +from flask.sessions import SessionInterface, SessionMixin +from flask import session, redirect, request + +import config + +class BucklerSessionInterface(SessionInterface): + """ + Queries the Buckler server for session data to the current user and + application. + """ + + def __init__(self): + self.url = config.buckler['url'] + self.app_id = config.buckler['app_id'] + self.app_key = config.buckler['app_key'] + + def open_session(self, app, request): + """Called when a request is initiated.""" + user_id = request.cookies.get('userid') + user_sid = request.cookies.get('session') + + params = { + 'app_id': self.app_id, + 'app_key': self.app_key, + 'userid': user_id, + 'session': user_sid + } + params = urllib.parse.urlencode(params) + req = urllib.request.Request(self.url + f"/get_session?{params}") + res = urllib.request.urlopen(req) + data = json.loads(res.read()) + if data.get('error'): + return None + session = BucklerSession() + session.update(data['session_data']) + session.meta = data['meta'] + session.cookies = request.cookies + return session + + def save_session(self, app, session, response): + """Called at the end of a request.""" + if not session.modified: + return + user_id = session.meta.get('user_id') + user_sid = session.meta.get('user_sid') + + params = { + 'app_id': self.app_id, + 'app_key': self.app_key, + 'userid': user_id, + 'session': user_sid + } + params = urllib.parse.urlencode(params) + data = json.dumps(session) + req = urllib.request.Request( + self.url + f"/set_session?{params}", + data=data.encode('utf8'), + method='POST') + res = urllib.request.urlopen(req) + + last_used = datetime.fromisoformat(session.meta['last_used']) + now = datetime.now(last_used.tzinfo) + delta = now - last_used + if delta.seconds > 600: + response.set_cookie( + 'userid', + session.cookies['userid'], + max_age=30*24*60*60, + secure=True, + httponly=True) + response.set_cookie( + 'session', + session.cookies['session'], + max_age=30*24*60*60, + secure=True, + httponly=True) + + +class BucklerSession(dict, SessionMixin): + """A server side session class based on the Buckler security shield.""" + def __init__(self): + super().__init__() + self.modified = False + + def __setitem__(self, key, value): + super().__setitem__(key, value) + self.modified = True + + +def require_auth(): + """ + Requires the user to be properly authenticated with Buckler before + accessing any views on the application. + """ + if not hasattr(session, 'meta'): + resp = redirect(config.buckler['login_url']) + resp.set_cookie('redirect', request.url) + return resp diff --git a/config.py.template b/config.py.template new file mode 100755 index 0000000..8a80454 --- /dev/null +++ b/config.py.template @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +""" +Configuration settings for Musik. +`buckler` specifies settings pertaining to the Buckler server. +""" +buckler = { + 'url': "http://127.0.0.1:5400/buckler", + 'app_id': 1, + 'app_key': """password""", +} diff --git a/musik.py b/musik.py index 2b0d329..6b23047 100755 --- a/musik.py +++ b/musik.py @@ -11,6 +11,7 @@ from urllib import parse from flask import Flask, Response, render_template, send_file, url_for, request, abort import database +import buckler_flask FFMPEG_CMD = [ 'ffmpeg', '-y', @@ -23,6 +24,8 @@ FFMPEG_CMD = [ app = Flask(__name__) +app.session_interface = buckler_flask.BucklerSessionInterface() +app.before_request(buckler_flask.require_auth) database.init_database() @app.route('/')