diff options
author | Jon Bergli Heier <snakebite@jvnv.net> | 2014-02-07 21:11:31 +0100 |
---|---|---|
committer | Jon Bergli Heier <snakebite@jvnv.net> | 2014-02-07 21:11:31 +0100 |
commit | 9fab02fc11b9fbf559f989a4c01721307073f66f (patch) | |
tree | fcab0b686367b0ed1a5130d65186a5ac3220f680 | |
parent | d6d22f2c1f10cb19b3976a76d78c20349bb95f76 (diff) |
Added support for authentication via jab.
-rwxr-xr-x | fbin.py | 182 | ||||
-rw-r--r-- | templates/base.tmpl | 1 | ||||
-rw-r--r-- | templates/help.tmpl | 15 |
3 files changed, 92 insertions, 106 deletions
@@ -3,6 +3,7 @@ import templates import settings, db, os, random, datetime, mimetypes, cgi, tempfile, hashlib, Cookie, urllib, subprocess, json from PIL import Image +import jab.client base62_alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' rfc1123_format = '%a, %d %b %Y %H:%M:%S +0000' @@ -67,6 +68,8 @@ class Application(object): user = db.User(username, password, active) session.add(user) session.commit() + # Refresh so we can fetch the id. + session.refresh(user) except db.IntegrityError: return None finally: @@ -85,18 +88,6 @@ class Application(object): finally: session.close() - def update_user_login(self, user, login_time = None): - if login_time is None: - login_time = datetime.datetime.utcnow() - session = db.Session() - try: - user.last_login = login_time - session.add(user) - session.commit() - session.refresh(user) - finally: - session.close() - def get_file(self, hash, update_accessed = False): session = db.Session() try: @@ -142,7 +133,20 @@ class Application(object): return files - def validate_cookie(self, cookie): + def validate_cookie(self, environ): + cookie = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) + if not cookie or not 'token' in cookie: + return + + token = cookie['token'].value + try: + user = self.jab.get_user_by_token(token, settings.jab_identifier, environ['REMOTE_ADDR']) + user = self.get_user_by_id(user['token_data']['user_id']) + except jab.client.InvalidCredentialsError: + user = None + return user + + def validate_cookie_old(self, cookie): if not cookie or not 'identifier' in cookie: return None @@ -262,8 +266,7 @@ class Application(object): return open(filename, 'rb') def upload(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) tempfile.tempdir = settings.file_directory form = FileUploadFieldStorage(fp = environ['wsgi.input'], environ = environ) if environ['REQUEST_METHOD'] != 'POST' or not 'file' in form or not 'filename' in form: @@ -336,8 +339,7 @@ class Application(object): })) def login(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) form = cgi.FieldStorage(fp = environ['wsgi.input'], environ = environ) next = form.getvalue('next') if environ['REQUEST_METHOD'] != 'POST' or not 'username' in form or not 'password' in form: @@ -350,16 +352,32 @@ class Application(object): })) username = form.getvalue('username') - password = hashlib.sha1(form.getvalue('password')).hexdigest() - - user = self.get_user(username, password) + password = form.getvalue('password') + # XXX: password_hash and the password field from the local db should probably be removed later. + password_hash = hashlib.sha1(password).hexdigest() error = None - if user == None: - error = 'Login failed' - elif not user.active: - user = None - error = 'User account is not active' + try: + token = self.jab.generate_user_token(username, password, settings.jab_identifier, settings.jab_name) + # If the user exists locally, both username and password must match (for migrating existing users). + # Otherwise, one could create a new user in jab with the same username but different password. + user = self.get_user(username, password_hash) + # Create the user if it exists in jab but not locally. + if not user: + user = self.add_user(username, password_hash, True) + self.jab.set_token_data(token, settings.jab_identifier, {'user_id': user.id}) + except jab.client.InvalidCredentialsError: + # Check wether the user exists in our local db, then add it via jab. + user = self.get_user(username, password_hash) + if user: + try: + self.jab.add_user(username, password, None, True) + token = self.jab.generate_user_token(username, password, settings.jab_identifier, settings.jab_name) + self.jab.set_token_data(token, settings.jab_identifier, {'user_id': user.id}) + except jab.client.InvalidCredentialsError: + error = 'Login failed' + else: + error = 'Login failed' if error: start_response('200 OK', [('Content-Type', 'text/html')]) @@ -370,40 +388,36 @@ class Application(object): 'next': next, })) - self.update_user_login(user) - rememberme = 'rememberme' in form forever = 'forever' in form c = Cookie.SimpleCookie() - c['uid'] = user.id - c['identifier'] = hashlib.sha1(str(user.id) + password).hexdigest() + c['token'] = token dt = datetime.datetime.utcnow() + datetime.timedelta(days = 30) expires = dt.strftime('%a, %d-%b-%y %H:%M:%S GMT') if rememberme: - c['uid']['expires'] = expires - c['identifier']['expires'] = expires + c['token']['expires'] = expires if forever: c['forever'] = 1 c['forever']['expires'] = expires headers = [ ('Location', next if next else (settings.virtual_root + 'u')), - ('Set-Cookie', c['uid'].OutputString()), - ('Set-Cookie', c['identifier'].OutputString())] + ('Set-Cookie', c['token'].OutputString())] if 'forever' in c: headers.append(('Set-Cookie', c['forever'].OutputString())) start_response('302 Found', headers) return [] def register(self, environ, start_response, path): + start_response('302 Found', [('Location', settings.jab_web_url + 'register')]) + return [] if not settings.allow_registration: start_response('403 Forbidden', [('Content-Type', 'text/plain')]) return ['Registrations are disabled by the administrator.'] - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) form = cgi.FieldStorage(fp = environ['wsgi.input'], environ = environ) if environ['REQUEST_METHOD'] != 'POST' or not 'username' in form or not 'password' in form or not 'password2' in form: start_response('200 OK', [('Content-Type', 'text/html')]) @@ -436,6 +450,13 @@ class Application(object): return self.redirect(environ, start_response, 'l') def logout(self, environ, start_response, path): + c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) + if c and 'token' in c: + try: + self.jab.expire_user_token(c['token'].value, settings.jab_identifier) + except: + pass + c = Cookie.SimpleCookie() expires = datetime.datetime.utcfromtimestamp(0).strftime('%a, %d-%b-%y %H:%M:%S GMT') c['uid'] = 0 @@ -444,59 +465,18 @@ class Application(object): c['identifier']['expires'] = expires c['forever'] = 0 c['forever']['expires'] = expires + c['token'] = 0 + c['token']['expires'] = expires start_response('302 Found', [ ('Set-Cookie', c['uid'].OutputString()), ('Set-Cookie', c['identifier'].OutputString()), ('Set-Cookie', c['forever'].OutputString()), + ('Set-Cookie', c['token'].OutputString()), ('Location', settings.virtual_root)]) return [] def changepass(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) - if not user: - return self.redirect(environ, start_response, 'l?' + urllib.urlencode({'next': settings.virtual_root + 'c'})) - form = cgi.FieldStorage(fp = environ['wsgi.input'], environ = environ) - if environ['REQUEST_METHOD'] != 'POST' or not 'oldpass' in form or not 'password' in form or not 'password2' in form: - start_response('200 OK', [('Content-Type', 'text/html')]) - return str(templates.changepass(searchList = { - 'settings': settings, - 'user': user, - 'error': None, - })) - - oldpass = hashlib.sha1(form.getvalue('oldpass')).hexdigest() - password = form.getvalue('password') - password2 = form.getvalue('password2') - - if oldpass != user.password: - start_response('200 OK', [('Content-Type', 'text/html')]) - return str(templates.changepass(searchList = { - 'settings': settings, - 'user': user, - 'error': 'Invalid password.', - })) - - if password != password2: - start_response('200 OK', [('Content-Type', 'text/html')]) - return str(templates.changepass(searchList = { - 'settings': settings, - 'user': user, - 'error': 'Passwords doesn\'t match.', - })) - - password = hashlib.sha1(password).hexdigest() - self.save_user_pass(user, password) - - dt = datetime.datetime.utcnow() + datetime.timedelta(days = 30) - expires = dt.strftime('%a, %d-%b-%y %H:%M:%S GMT') - c['uid']['expires'] = expires - c['identifier'] = hashlib.sha1(str(user.id) + password).hexdigest() - c['identifier']['expires'] = expires - start_response('302 Found', [ - ('Set-Cookie', c['uid'].OutputString()), - ('Set-Cookie', c['identifier'].OutputString()), - ('Location', settings.virtual_root)]) + start_response('302 Found', [('Location', settings.jab_web_url + 'changepass')]) return [] def static(self, environ, start_response, path): @@ -514,8 +494,7 @@ class Application(object): return open(filepath, 'rb') def help(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.help(searchList = { 'settings': settings, @@ -525,8 +504,7 @@ class Application(object): })) def my_files(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) if user == None: start_response('302 Found', [('Location', settings.virtual_root + 'l?' + urllib.urlencode({'next': settings.virtual_root + 'm'}))]) return [] @@ -540,8 +518,7 @@ class Application(object): })) def images(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) if user == None: start_response('302 Found', [('Location', settings.virtual_root + 'l?' + urllib.urlencode({'next': settings.virtual_root + 'i'}))]) return [] @@ -572,8 +549,7 @@ class Application(object): return open(thumbfile, 'rb') def delete(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) + user = self.validate_cookie(environ) if user == None: start_response('200 OK', [('Content-Type', 'text/html')]) return ['Not logged in.'] @@ -604,15 +580,16 @@ class Application(object): })) def api(self, environ, start_response, path): - c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) - user = self.validate_cookie(c) - if user == None: + def error(msg): start_response('200 OK', [('Content-Type', 'application/json')]) - return [json.dumps({'status': False, 'message': 'Not logged in'})] - form = cgi.FieldStorage(environ = environ) + return [json.dumps({'status': False, 'message': msg})] + user = self.validate_cookie(environ) + form = cgi.FieldStorage(fp = environ['wsgi.input'], environ = environ) method = form.getvalue('method') data = {'status': False, 'method': method, 'message': None} if method in ('list', 'images'): + if user == None: + return error('Not logged in') files = self.get_files(user) data['files'] = [ { @@ -624,6 +601,22 @@ class Application(object): for f in files if method == 'list' or (method == 'images' and f.is_image()) ] data['status'] = True + elif method == 'get_token': + user = self.get_user(form['username'].value, hashlib.sha1(form['password'].value).hexdigest()) + if not user: + return error('Invalid credentials') + try: + token = self.jab.generate_user_token(form['username'].value, form['password'].value, settings.jab_identifier, '%s (API)' % settings.jab_name, {'user_id': user.id}) + except: + return error('Invalid credentials') + data['token'] = token + data['status'] = True + elif method == 'expire_token': + try: + self.jab.expire_token(form['token'].value, settings.jab_identifier) + except: + pass + data['status'] = True else: data['message'] = 'Unknown method "%s"' start_response('200 OK', [('Content-Type', 'application/json')]) @@ -643,6 +636,9 @@ class Application(object): d = delete a = api + def __init__(self): + self.jab = jab.client.JabClient(settings.jab_socket) + def __call__(self, environ, start_response): def never_forget_wrapper(status, headers, exc_info = None): c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None) diff --git a/templates/base.tmpl b/templates/base.tmpl index 5c1f894..76d9c99 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -10,7 +10,6 @@ #end block </head> <body> - $settings <div id="page"> <div id="page-header"> <h1><a href="$settings.virtual_root">$header</a></h1> diff --git a/templates/help.tmpl b/templates/help.tmpl index 6451b25..afba84a 100644 --- a/templates/help.tmpl +++ b/templates/help.tmpl @@ -2,19 +2,10 @@ #def header: help #extends templates.base #def content - <p>Usage: POST to <a href="$scheme://${host}${settings.virtual_root}u">$scheme://${host}${settings.virtual_root}u</a> with filedata given to "file" and original filename to "filename". - Login is sent by cookies with either: - <ul> - <li>user id in "uid" and an identifier which is sha1(uid+sha1(password))</li> - </ul> - or - <ul> - <li>username in "username" and an identifier which is sha1(username+sha1(password))</li> - </ul> - </p> + <p>Usage: POST to <a href="$scheme://${host}${settings.virtual_root}u">$scheme://${host}${settings.virtual_root}u</a> with filedata given to "file" and original filename to "filename". Login is done by generating a login token and sending it as the cookie "token".</p> + <p>TODO: Add info on how to generate a login token.</p> <p>cURL example: - <blockquote><code>curl -b 'uid=42; identifier=360b411581c3d516c8d55c290f039b144cea79ad' -F 'file=@image.png' -F 'filename=image.png' -F 'api=1' http://myhost/u</code></blockquote> - Here user id is 42 and the password is "foobar". + <blockquote><code>curl -b 'token=foo' -F 'file=@image.png' -F 'filename=image.png' -F 'api=1' http://myhost/u</code></blockquote> If you get HTTP 417 responses, try adding:<code>-H 'Expect:'</code>.</p> <p>By adding the key-value pair "api=1" you will get machine-readable responses in the form: <code>response result</code> where <code>response</code> is either <code>ERROR</code> or <code>OK</code>, and <code>result</code> is the file hash in the case of <code>OK</code>, or an error message in the case of <code>ERROR</code>. |