diff options
author | Jon Bergli Heier <snakebite@jvnv.net> | 2013-08-30 23:21:42 +0200 |
---|---|---|
committer | Jon Bergli Heier <snakebite@jvnv.net> | 2013-08-30 23:30:52 +0200 |
commit | 143f638a4b1bafdc4a2a811a9f4c97a5392a0acc (patch) | |
tree | a3f5e445ef7c597c25890b57b1b224504746405d | |
parent | 3c42d5d76da5eda2ce6bedd954269161259b2289 (diff) |
Added settings for registrations and uploads.
* create_active_users:
Controls whether new user accounts are created as 'active'; accounts
that are not marked as active will not be able to log in, and will be
automatically logged out if they're already logged in.
* allow_registration:
Allow or disallow creation of new user accounts. Any attempts to
access the registration page will result in an error message.
* allow_anonymous_uploads:
Allow or disallow uploading of files by anonymous (not logged in)
users. Combined with either allow_registration or create_active_users,
this will effectively create a private filebin where the admin must
explicitly create or activate new users.
-rw-r--r-- | db.py | 7 | ||||
-rwxr-xr-x | fbin.py | 122 | ||||
-rw-r--r-- | templates/base.tmpl | 25 | ||||
-rw-r--r-- | templates/changepass.tmpl | 2 | ||||
-rw-r--r-- | templates/delete.tmpl | 4 | ||||
-rw-r--r-- | templates/help.tmpl | 2 | ||||
-rw-r--r-- | templates/images.tmpl | 2 | ||||
-rw-r--r-- | templates/login.tmpl | 2 | ||||
-rw-r--r-- | templates/register.tmpl | 2 | ||||
-rw-r--r-- | templates/upload.tmpl | 2 |
10 files changed, 113 insertions, 57 deletions
@@ -1,4 +1,4 @@ -from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, Index, ForeignKey +from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, Index, ForeignKey, Boolean from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, relation, backref from sqlalchemy.orm.exc import NoResultFound @@ -16,11 +16,14 @@ class User(Base): id = Column(Integer, primary_key = True) username = Column(String, unique = True, index = True) password = Column(String) + last_login = Column(DateTime) + active = Column(Boolean, nullable = False) files = relation('File', backref = 'user', order_by = 'File.date.desc()') - def __init__(self, username, password): + def __init__(self, username, password, active): self.username = username self.password = password + self.active = active class File(Base): __tablename__ = 'files' @@ -22,6 +22,10 @@ except OSError: else: has_mogrify = True +class BinError(Exception): pass +class InvalidCookieError(BinError): pass +class InactiveLoginError(BinError): pass + class FileUploadFieldStorage(cgi.FieldStorage): def make_file(self, binary = None): # Make a temporary file in the destination directory, which will be renamed on completion. @@ -57,10 +61,10 @@ class Application(object): finally: session.close() - def add_user(self, username, password): + def add_user(self, username, password, active): session = db.Session() try: - user = db.User(username, password) + user = db.User(username, password, active) session.add(user) session.commit() except db.IntegrityError: @@ -81,6 +85,18 @@ 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: @@ -137,14 +153,17 @@ class Application(object): if not user: return None digest = hashlib.sha1(user.username + user.password).hexdigest() - return user if (digest == identifier) else None - - user = self.get_user_by_id(cookie['uid'].value) - if not user: - return None + else: + user = self.get_user_by_id(cookie['uid'].value) + if not user: + return None + digest = hashlib.sha1(str(user.id) + user.password).hexdigest() - digest = hashlib.sha1(str(user.id) + user.password).hexdigest() - return user if (digest == identifier) else None + if digest != identifier: + raise InvalidCookieError(user.username) + if not user.active: + raise InactiveLoginError(user.username) + return user def get_file_by_file_hash(self, file_hash): session = db.Session() @@ -166,6 +185,10 @@ class Application(object): finally: session.close() + def redirect(self, environ, start_response, dest): + start_response('302 Found', [('Location', settings.virtual_root + dest)]) + return [] + def not_modified(self, environ, date): if not 'HTTP_IF_MODIFIED_SINCE' in environ: return False @@ -246,8 +269,16 @@ class Application(object): if environ['REQUEST_METHOD'] != 'POST' or not 'file' in form or not 'filename' in form: if 'file' in form: form['file'].file.delete = True - start_response('200 OK', [('Content-Type', 'text/html')]) - return str(templates.upload(searchList = {'root': settings.virtual_root, 'user': user})) + if settings.allow_anonymous_uploads: + start_response('200 OK', [('Content-Type', 'text/html')]) + return str(templates.upload(searchList = {'settings': settings, 'user': user})) + else: + return self.redirect(environ, start_response, 'l') + + if not user and not settings.allow_anonymous_uploads: + form['file'].file.delete = True + start_response('403 Forbidden', [('Content-Type', 'text/plain')]) + return ['Anonymous uploads are disabled by the administrator.'] filename = form.getvalue('filename') temp = form['file'].file @@ -295,7 +326,7 @@ class Application(object): else: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.uploaded(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'hash': hash, 'filename': filename, @@ -312,7 +343,7 @@ class Application(object): if environ['REQUEST_METHOD'] != 'POST' or not 'username' in form or not 'password' in form: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.login(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'error': None, 'next': next, @@ -323,15 +354,24 @@ class Application(object): user = self.get_user(username, password) + error = None if user == None: + error = 'Login failed' + elif not user.active: + user = None + error = 'User account is not active' + + if error: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.login(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, - 'error': 'Login failed', + 'error': error, 'next': next, })) + self.update_user_login(user) + c = Cookie.SimpleCookie() c['uid'] = user.id c['identifier'] = hashlib.sha1(str(user.id) + password).hexdigest() @@ -348,13 +388,17 @@ class Application(object): return [] def register(self, environ, start_response, path): + 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) 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')]) return str(templates.register(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'error': None, })) @@ -365,22 +409,21 @@ class Application(object): if password != password2: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.register(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'error': 'Passwords doesn\'t match', })) - user = self.add_user(username, hashlib.sha1(password).hexdigest()) + user = self.add_user(username, hashlib.sha1(password).hexdigest(), settings.create_active_users) if not user: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.register(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': None, 'error': 'Username already taken.', })) - start_response('302 Found', [('Location', settings.virtual_root + 'l')]) - return [] + return self.redirect(environ, start_response, 'l') def logout(self, environ, start_response, path): c = Cookie.SimpleCookie() @@ -398,11 +441,13 @@ class Application(object): 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 = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'error': None, })) @@ -414,7 +459,7 @@ class Application(object): if oldpass != user.password: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.changepass(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'error': 'Invalid password.', })) @@ -422,7 +467,7 @@ class Application(object): if password != password2: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.changepass(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'error': 'Passwords doesn\'t match.', })) @@ -460,7 +505,7 @@ class Application(object): user = self.validate_cookie(c) start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.help(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'scheme': environ['wsgi.url_scheme'], 'host': environ['HTTP_HOST'], @@ -475,7 +520,7 @@ class Application(object): files = self.get_files(user) start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.my(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'files': files, 'total_size': db.File.pretty_size(sum([f.get_size() for f in files])), @@ -490,7 +535,7 @@ class Application(object): files = [f for f in self.get_files(user) if f.is_image()] start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.images(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'files': files, 'total_size': db.File.pretty_size(sum([f.get_size() for f in files])), @@ -539,7 +584,7 @@ class Application(object): else: start_response('200 OK', [('Content-Type', 'text/html')]) return str(templates.delete(searchList = { - 'root': settings.virtual_root, + 'settings': settings, 'user': user, 'hash': hash, 'filename': file.filename, @@ -586,15 +631,18 @@ class Application(object): a = api def __call__(self, environ, start_response): - path = environ['PATH_INFO'].split('/')[1:] - module = path[0] if len(path) else '' - if len(module) and module in 'fulshmitorcda': - return getattr(self, module)(environ, start_response, path) - elif path == ['favicon.ico']: - return self.static(environ, start_response, ['s'] + path) - else: - start_response('302 Found', [('Location', settings.virtual_root + 'u')]) - return [] + try: + path = environ['PATH_INFO'].split('/')[1:] + module = path[0] if len(path) else '' + if len(module) and module in 'fulshmitorcda': + return getattr(self, module)(environ, start_response, path) + elif path == ['favicon.ico']: + return self.static(environ, start_response, ['s'] + path) + else: + start_response('302 Found', [('Location', settings.virtual_root + 'u')]) + return [] + except (InactiveLoginError, InvalidCookieError): + return self.logout(environ, start_response, path) if __name__ == '__main__': import sys diff --git a/templates/base.tmpl b/templates/base.tmpl index fe9120b..5c1f894 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -4,33 +4,38 @@ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>$title</title> - <link rel="StyleSheet" href="${root}s/style.css" type="text/css" /> + <link rel="StyleSheet" href="${settings.virtual_root}s/style.css" type="text/css" /> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> #block head #end block </head> <body> + $settings <div id="page"> <div id="page-header"> - <h1><a href="$root">$header</a></h1> + <h1><a href="$settings.virtual_root">$header</a></h1> <div id="page-menu"> <ul> - <li><a href="${root}u">upload</a></li> +#if $user or $settings.allow_anonymous_uploads + <li><a href="${settings.virtual_root}u">upload</a></li> +#end if #if $user - <li><a href="${root}o">logout</a></li> - <li><a href="${root}m">myfiles</a></li> - <li><a href="${root}i">images</a></li> + <li><a href="${settings.virtual_root}o">logout</a></li> + <li><a href="${settings.virtual_root}m">myfiles</a></li> + <li><a href="${settings.virtual_root}i">images</a></li> #else - <li><a href="${root}l">login</a></li> - <li><a href="${root}r">register</a></li> + <li><a href="${settings.virtual_root}l">login</a></li> +#if $settings.allow_registration + <li><a href="${settings.virtual_root}r">register</a></li> +#end if #end if - <li><a href="${root}h">help</a></li> + <li><a href="${settings.virtual_root}h">help</a></li> </ul> </div> </div> <p class="login">#slurp #if $user -Logged in as $user.username. <a href="${root}c">Change password</a>#slurp +Logged in as $user.username. <a href="${settings.virtual_root}c">Change password</a>#slurp #else Not logged in.#slurp #end if diff --git a/templates/changepass.tmpl b/templates/changepass.tmpl index 4171f36..a66c7f3 100644 --- a/templates/changepass.tmpl +++ b/templates/changepass.tmpl @@ -4,7 +4,7 @@ #def content #set error = $error or '' <div class="error">$error</div> - <form method="post" action="${root}c"> + <form method="post" action="${settings.virtual_root}c"> <p>current password</p> <p><input type="password" id="oldpass" name="oldpass" /></p> <p>new password</p> diff --git a/templates/delete.tmpl b/templates/delete.tmpl index 62216d5..a3ed7be 100644 --- a/templates/delete.tmpl +++ b/templates/delete.tmpl @@ -2,8 +2,8 @@ #def header: delete #extends templates.base #def content - <form method="post" action="${root}d/$hash"> + <form method="post" action="${settings.virtual_root}d/$hash"> <p>Are you sure you want to delete the file $filename?</p> - <p><input type="submit" value="Yes" /> <input type="button" value="No" onclick="document.location = '${root}u'" /></p> + <p><input type="submit" value="Yes" /> <input type="button" value="No" onclick="document.location = '${settings.virtual_root}u'" /></p> </form> #end def diff --git a/templates/help.tmpl b/templates/help.tmpl index bb59424..6451b25 100644 --- a/templates/help.tmpl +++ b/templates/help.tmpl @@ -2,7 +2,7 @@ #def header: help #extends templates.base #def content - <p>Usage: POST to <a href="$scheme://${host}${root}u">$scheme://${host}${root}u</a> with filedata given to "file" and original filename to "filename". + <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> diff --git a/templates/images.tmpl b/templates/images.tmpl index c284a1c..a54bef2 100644 --- a/templates/images.tmpl +++ b/templates/images.tmpl @@ -4,7 +4,7 @@ #def head <script type="text/javascript"> function thumb_onerror(img) { - var no_thumb = '${root}s/no-thumbnail.png'; + var no_thumb = '${settings.virtual_root}s/no-thumbnail.png'; if(img.src != no_thumb) img.src = no_thumb; } diff --git a/templates/login.tmpl b/templates/login.tmpl index 7638c07..e55600d 100644 --- a/templates/login.tmpl +++ b/templates/login.tmpl @@ -4,7 +4,7 @@ #def content #set error = $error or '' <div class="error">$error</div> - <form method="post" action="${root}l"> + <form method="post" action="${settings.virtual_root}l"> #if next <input type="hidden" name="next" value="$next" /> #end if diff --git a/templates/register.tmpl b/templates/register.tmpl index cb371a7..4d2bc2a 100644 --- a/templates/register.tmpl +++ b/templates/register.tmpl @@ -4,7 +4,7 @@ #def content #set error = $error or '' <div class="error">$error</div> - <form method="post" action="${root}r"> + <form method="post" action="${settings.virtual_root}r"> <p>username</p> <p><input type="text" id="username" name="username" /></p> <p>password</p> diff --git a/templates/upload.tmpl b/templates/upload.tmpl index ab0bddb..87f50ea 100644 --- a/templates/upload.tmpl +++ b/templates/upload.tmpl @@ -15,7 +15,7 @@ </script> #end def #def content - <form method="post" action="${root}u" enctype="multipart/form-data"> + <form method="post" action="${settings.virtual_root}u" enctype="multipart/form-data"> <input type="hidden" id="filename" name="filename" /> <p><input type="file" id="file" name="file" onchange="file_changed()" /></p> <p><input type="submit" value="Upload" /></p> |