summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bergli Heier <snakebite@jvnv.net>2014-02-07 21:11:31 +0100
committerJon Bergli Heier <snakebite@jvnv.net>2014-02-07 21:11:31 +0100
commit9fab02fc11b9fbf559f989a4c01721307073f66f (patch)
treefcab0b686367b0ed1a5130d65186a5ac3220f680
parentd6d22f2c1f10cb19b3976a76d78c20349bb95f76 (diff)
Added support for authentication via jab.
-rwxr-xr-xfbin.py182
-rw-r--r--templates/base.tmpl1
-rw-r--r--templates/help.tmpl15
3 files changed, 92 insertions, 106 deletions
diff --git a/fbin.py b/fbin.py
index b5dabd2..6826bb4 100755
--- a/fbin.py
+++ b/fbin.py
@@ -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>.