summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bergli Heier <snakebite@jvnv.net>2013-08-30 23:21:42 +0200
committerJon Bergli Heier <snakebite@jvnv.net>2013-08-30 23:30:52 +0200
commit143f638a4b1bafdc4a2a811a9f4c97a5392a0acc (patch)
treea3f5e445ef7c597c25890b57b1b224504746405d
parent3c42d5d76da5eda2ce6bedd954269161259b2289 (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.py7
-rwxr-xr-xfbin.py122
-rw-r--r--templates/base.tmpl25
-rw-r--r--templates/changepass.tmpl2
-rw-r--r--templates/delete.tmpl4
-rw-r--r--templates/help.tmpl2
-rw-r--r--templates/images.tmpl2
-rw-r--r--templates/login.tmpl2
-rw-r--r--templates/register.tmpl2
-rw-r--r--templates/upload.tmpl2
10 files changed, 113 insertions, 57 deletions
diff --git a/db.py b/db.py
index 8458704..f571809 100644
--- a/db.py
+++ b/db.py
@@ -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'
diff --git a/fbin.py b/fbin.py
index 51d3203..1d70712 100755
--- a/fbin.py
+++ b/fbin.py
@@ -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>