Open slinnarsson opened 8 years ago
Implemented the third option above. The server serves .loom
files from a datasets
directory (given as an argument at startup). Under this directory, there are one or more projects directories, each of which contains the .loom
files. The server now accepts Basic Auth username and password headers. It checks each project directory and:
auth.txt
file, then the project is public to all, with or without Basic Auth Headers in the requestauth.txt
file, then the project is only visible to requests that (a) provide a basic Auth header with username and password, and (b) the auth.txt
file contains a line username,password
that matches the provided credentials. Otherwise, the request returns 404 Not Found.Thus, a project is public and visible if it does not contain an auth.txt
file. If it does contain such a file, the project is visible and accessible only to users who authenticate with credentials listed in the file.
Example of a datasets
directory:
datasets/
public_project/
somefile.loom
anotherfile.loom
private_project/
auth.txt
visible_only_to_some.loom
Example of a auth.txt
file:
sten.linnarsson@ki.se,secretpassword01
timSnowman,timsPassword
The file is comma-delimited, with exactly two fields per line, no extra whitespace. If the file violates these rules, then the project will be inaccessible to all users.
It's probably time to look into this again...
This is not intended to scale for big, complex authorisation situations with many users on a big system. If this viewer would ever be adapted for that purpose, this security needs to be changed.
For the current and likely-future use-cases, a dumb, simple, but effective solution would using HTTPS + Basic Auth.
It seems like we need to implement the following steps for this:
https://github.com/kennethreitz/flask-sslify
This is a simple Flask extension that configures your Flask application to redirect all incoming requests to HTTPS.
Combining with basic auth is probably enough for our use-case:
Security consideration using basic auth
When using basic auth, it is important that the redirect occurs before the user is prompted for credentials. Flask-SSLify registers a
before_request
handler, to make sure this handler gets executed before credentials are entered it is advisable to not prompt for any authentication inside abefore_request
handler.The example found at http://flask.pocoo.org/snippets/8/ works nicely, as the view function's decorator will never have an effect before the
before_request
hooks are executed.
As suggested to combine with Flask-SSLify
For very simple applications HTTP Basic Auth is probably good enough. Flask makes this very easy. The following decorator applied around a function that is only available for certain users does exactly that:
from functools import wraps from flask import request, Response def check_auth(username, password): """This function is called to check if a username / password combination is valid. """ return username == 'admin' and password == 'secret' def authenticate(): """Sends a 401 response that enables basic auth""" return Response( 'Could not verify your access level for that URL.\n' 'You have to login with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) return decorated
To use this decorator, just wrap a view function:
@app.route('/secret-page') @requires_auth def secret_page(): return render_template('secret_page.html')
If you are using basic auth with mod_wsgi you will have to enable auth forwarding, otherwise apache consumes the required headers and does not send it to your application: WSGIPassAuthorization.
http://flask.pocoo.org/snippets/8/
Depending on whether the serve requires basic authentication or not, the first page should be a login page or directly show the dataset list.
The login page is really just storing the user/password in a javascript object (and yes, I know that is insecure! But I doubt we have to worry about cross-site scripting attacks targeting people known to have access to the data any time soon...)
This user:password
string is then used in fetch
calls to let the server play nice with our authorisation.
fetch
calls to include authorisationExample fetch with authorization header:
fetch('URL_GOES_HERE', { method: 'post', headers: { 'Authorization': 'Basic '+btoa('username:password'), 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'A=1&B=2' });
If we do have to use session-based security (which I hope to avoid because we really don't need a database...), the following set-up is taken from this blog, and probably the simplest way of using a session-based authorisation in a Single Page App:
POST /api/session |
This API endpoint is responsible for creating a session. It accepts a username/password combination, and returns either a session object with an access token or an error that the credentials are incorrect. |
Schema |
We should then look into Flask-Security.
We will eventually want to have some simple system of authentication and authorization, so that we can make the Loom web site public and still keep private datasets there. Some options: