# setup django environment, so we can use their template engine
from django.core.management import setup_environ
from account import settings
setup_environ(settings)
from django.template import Context
from django.template.loader import render_to_string
# mod_python session handling and redirects
from mod_python import Session
from mod_python.util import redirect
# import mysql bindings
import MySQLdb
# import other common stuff
from random import choice
import base64, cracklib, sha, string, os
# render mail template and send it using local sendmail binary
def sendmail(to_addr, template, c):
c['from_addr'] = "dev@spline.de"
c['to_addr'] = to_addr
msg = render_to_string(template + "_mail.txt", c)
# open sendmail process for writing
p = os.popen("/usr/sbin/sendmail -t", 'w')
p.write(msg)
# close will return exit status
# TODO: log error to some file
if p.close():
return False
return True
# cleanup database connection after every request
def closedb(req):
req.cursor.close()
req.dbc.close()
# start a new session and context and register the session dictionary in our
# global template context
def session_start(req, anonymous):
try:
req.dbc = MySQLdb.connect(host="localhost", user="apache", passwd="password", db="trac")
except:
redirect(req, "db_error")
req.cursor = req.dbc.cursor()
req.register_cleanup(closedb, req)
s = Session.Session(req)
if not 'login' in s:
s['login'] = None
s.save()
req.c = Context()
req.c['session'] = s
# None means everyone can acccess this URL
# True/False means only anonymous/authenticated users can access this URL
if anonymous != None:
if (anonymous and s['login']) or (not anonymous and not s['login']):
redirect(req, "index")
return (req.c, s, req.cursor)
# generate a htpasswd compatible sha1 digest in base64 encoding
# see: http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
def generate_sha_base64(password):
return "{SHA}" + base64.encodestring(sha.new(password).digest()).rstrip('\n')
# generate a new random password, save it to the database and send it to the user
# WARNING: this function will do an implicit commit/rollback to the database!
def set_random_password(req, user_id, length):
req.c['new_password'] = ''.join([choice(string.letters) for i in range(length)])
pw_hash = generate_sha_base64(req.c['new_password'])
req.cursor.execute("UPDATE user SET password = %s WHERE id = %s", (pw_hash, user_id))
req.cursor.execute("SELECT email FROM user WHERE id = %s", (user_id,))
result = req.cursor.fetchone()
email = result[0]
req.cursor.execute("SELECT login FROM user WHERE id = %s AND password NOT NULL", (user_id,))
result = req.cursor.fetchone()
if not result:
template = 'registration'
else:
req.c['login'] = result[0]
template = 'password'
if not sendmail(email, template, req.c):
req.dbc.rollback()
return "failed to send temporary password. sorry."
# otherwise commit to database and confirm
req.dbc.commit()
return None
# Ditch nonsense email addresses.
# taken from http://commandline.org.uk/python/2008/may/3/email-syntax-check/
GENERIC_DOMAINS = "aero", "asia", "biz", "cat", "com", "coop", \
"edu", "gov", "info", "int", "jobs", "mil", "mobi", "museum", \
"name", "net", "org", "pro", "tel", "travel"
def invalid(emailaddress, domains = GENERIC_DOMAINS):
"""Checks for a syntactically invalid email address."""
# Email address must be 7 characters in total.
if len(emailaddress) < 7:
return True # Address too short.
# Split up email address into parts.
try:
localpart, domainname = emailaddress.rsplit('@', 1)
host, toplevel = domainname.rsplit('.', 1)
except ValueError:
return True # Address does not have enough parts.
# Check for Country code or Generic Domain.
if len(toplevel) != 2 and toplevel not in domains:
return True # Not a domain name.
for i in '-_.%+.':
localpart = localpart.replace(i, "")
for i in '-_.':
host = host.replace(i, "")
if localpart.isalnum() and host.isalnum():
return False # Email address is fine.
else:
return True # Email address has funny characters.
def validate_and_register(req, login, email):
# check for invalid entries
# empty login
if login == "":
return "please enter a username";
# no fu-berlin.de address
if not email.endswith('.fu-berlin.de'):
return "you did not give a *.fu-berlin.de address"
# regex checker for valid email
if invalid(email):
return "please enter a valid email address";
req.cursor.execute("SELECT login FROM user WHERE login = %s", (login, ))
if req.cursor.fetchone():
return "username already taken, please choose another one"
req.cursor.execute("SELECT login FROM user WHERE email = %s", (email, ))
if req.cursor.fetchone():
return 'you already have an account! <a href="password">forgot your password?</a>'
# entries are valid - generate activation code and prepare insert statements
hash = ''.join([choice(string.letters) for i in range(10)])
req.cursor.execute("INSERT INTO user (login, email, date_added) VALUES (%s, %s, NOW())", (login, email))
req.cursor.execute("INSERT INTO activation (hash, user_id) VALUES (%s, LAST_INSERT_ID())", (hash, ))
# send confirmation email to applicant
req.c['activation_link'] = "https://dev.spline.de/account/activate?hash=" + hash
# in case of an error, rollback
if not sendmail(email, 'activation', req.c):
req.dbc.rollback()
return "failed to send confirmation link."
# otherwise commit to database
req.dbc.commit()
return None
###############################
# URL handlers start here #
###############################
# req = request
def index(req):
(c, s, cursor) = session_start(req, None)
return render_to_string("index.html", c)
def activate(req, **formdata):
(c, s, cursor) = session_start(req, True)
# check whether a (correct) hashcode is given in the url
if "hash" in formdata:
cursor.execute("SELECT user_id FROM activation WHERE hash = %s", (formdata['hash'],))
result = cursor.fetchone()
if not result:
c['error_msg'] = "invalid activation code"
else:
user_id = result[0]
cursor.execute("DELETE FROM activation WHERE user_id = %s", (user_id,))
cursor.execute("UPDATE user SET activated = 1 WHERE id = %s", (user_id,))
# send email containing a temporary password
c['error_msg'] = set_random_password(req, user_id, 8)
if not c['error_msg']:
c['info_msg'] = "your account has been activated. you will receive an email with a temporary password."
# no hash value given
else:
redirect(req, "index")
return render_to_string("index.html", c)
def login(req, **formdata):
(c, s, cursor) = session_start(req, True)
if req.method == "POST":
pw_hash = generate_sha_base64(formdata['password'])
cursor.execute("SELECT * FROM user WHERE login = %s AND password = %s", (formdata['login'], pw_hash))
if not cursor.fetchone():
c['error_msg'] = "Login failed. Sorry."
return render_to_string("index.html", c)
else:
s['login'] = formdata['login']
s.save()
redirect(req, 'index')
def logout(req):
(c, s, cursor) = session_start(req, False)
s['login'] = None
s.save()
redirect(req, 'index')
def register(req, **formdata):
(c, s, cursor) = session_start(req, True)
if req.method == "POST":
c['error_msg'] = validate_and_register(req, formdata['login'], formdata['email'])
if not c['error_msg']:
c['info_msg'] = "we have sent an email to " + formdata['email'] + " with your confirmation link"
return render_to_string("index.html", c)
return render_to_string("register.html", c)
def password(req, **formdata):
(c, s, cursor) = session_start(req, True)
if req.method == 'POST':
cursor.execute("SELECT id FROM user WHERE email = %s", (formdata['email'],))
result = cursor.fetchone()
if not result:
c['error_msg'] = "unknown email address"
else:
c['error_msg'] = set_random_password(req, result[0], 8)
if not c['error_msg']:
c['info_msg'] = "a new temporary password has been sent to your email address"
return render_to_string("index.html", c)
return render_to_string("password.html", c)
def profile(req, **formdata):
(c, s, cursor) = session_start(req, False)
if req.method == 'POST':
old_pw_hash = generate_sha_base64(formdata['old_pw'])
cursor.execute("SELECT * FROM user WHERE password = %s AND login = %s", (old_pw_hash,s['login']))
new_pw_bad = cracklib.FascistCheck(formdata['new_pw1'])
if not cursor.fetchone():
c['error_msg'] = "old pasword did not match"
elif formdata['new_pw1'] != formdata['new_pw2']:
c['error_msg'] = "new passwords did not match"
elif new_pw_bad:
c['error_msg'] = "cannot accept new password: " + new_pw_bad
else:
new_pw_hash = generate_sha_base64(formdata['new_pw1'])
cursor.execute("UPDATE user SET password = %s WHERE login = %s", (new_pw_hash, s['login']))
req.dbc.commit()
c['info_msg'] = "new password has been set"
return render_to_string("index.html", c)
cursor.execute("SELECT email FROM user WHERE login = %s", (s['login'],))
result = cursor.fetchone()
c['email'] = result[0]
return render_to_string("profile.html", c)
def projects(req, **formdata):
(c, s, cursor) = session_start(req, False)
if "action" in formdata and "proj_name" in formdata:
if formdata['action'] == "leave":
cursor.execute("delete from member where user_id = (select id from user where login = %s) "
+ "and project_id = (select id from project where project_name = %s)", (s['login'], formdata['proj_name']))
req.dbc.commit()
c['info_msg'] = "you left project " + formdata['proj_name']
elif formdata['action'] == "delete":
# check whether the person is member of the project he or she wants to delete
cursor.execute("select * from member where user_id = (select id from user where login = %s) "
+ "and project_id = (select id from project where project_name = %s)", (s['login'], formdata['proj_name']))
if cursor.fetchone() != None:
cursor.execute("update project set deleted = 1 where project_name = %s", (formdata['proj_name'],))
req.dbc.commit()
c['info_msg'] = "you deleted project " + formdata['proj_name']
c['proj_name'] = formdata['proj_name']
sendmail("dev@spline.de", "deleted_project", c)
cursor.execute("select project_name from project p join member m on m.project_id = p.id join user u on u.id = m.user_id where p.deleted <> 1 and u.login = %s", (s['login'],))
c['projects'] = cursor.fetchall()
return render_to_string("projects.html", c)
def imprint(req):
(c, s, cursor) = session_start(req, None)
return render_to_string("imprint.html", c)
def new_project(req, **formdata):
(c, s, cursor) = session_start(req, False)
if req.method == "POST":
c['name'] = formdata['project_name']
c['desc'] = formdata['project_desc']
c['priv'] = formdata['priv']
# send message to dev.spline.de
sendmail("dev@spline.de", "new_project", c)
# confirm to user
c['info_msg'] = "your application has been sent to the dev.spline.de team. you'll receive a message shortly"
return render_to_string("index.html", c)
return render_to_string("new_project.html", c)
def members(req, **formdata):
(c, s, cursor) = session_start(req, False)
if not "proj_name" in formdata:
redirect(req,"index")
proj = formdata['proj_name']
cursor.execute("select id from project where project_name = %s", (proj,))
result = cursor.fetchone()
if not result:
c['error_msg'] = "project '%s' does not exist" % proj
return render_to_string("index.html", c)
project_id = result[0]
# if he/she wants to add/delete members, the logged in user must be a member of the project, too
cursor.execute("select * from member where user_id = (select id from user where login = %s) "
+ "and project_id = %s", (s['login'], project_id))
if cursor.fetchone() == None:
c['error_msg'] = "you are not a member of project '%s'" % proj
elif "login" in formdata and "action" in formdata:
who = formdata['login']
what = formdata['action']
cursor.execute("select id from user where login = %s", (who, ))
result = cursor.fetchone()
if result:
user_id = result[0]
if what == "delete":
cursor.execute("delete from member where user_id = %s and project_id = %s", (user_id, project_id))
req.dbc.commit()
c['info_msg'] = "you deleted " + who + " from project " + proj
elif what == "add":
cursor.execute("insert into member (user_id, project_id) values (%s, %s)", (user_id, project_id))
req.dbc.commit()
c['info_msg'] = "you added " + who + " to project " + proj
else:
c['error_msg'] = "invalid user name"
cursor.execute("select login from user u join member m on u.id = m.user_id "
+ "where m.project_id = (select id from project where project_name = %s)", (proj,))
c['members'] = cursor.fetchall()
c['proj'] = proj
return render_to_string("members.html", c)