# setup django environment, so we can use their template engine import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "account.settings") from django.conf import 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 # trac.ini parsing import trac.config # import other common stuff from random import choice import base64, cracklib, sha, string, os, re # 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.encode('utf8')) # 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') # this function calls /var/www/localhost/scripts/bin/generate_users to create # project specific htpasswd files # start via http://dev.spline.de/account/genusers def generate_users_file(cursor): cursor.execute("SELECT project_name FROM project WHERE project_name != 'ALL'") projects = cursor.fetchall() for project in projects: cursor.execute("SELECT DISTINCT login, password FROM project_members WHERE password IS NOT NULL AND (project_name = %s OR project_name = 'ALL')", (project[0],)) users = cursor.fetchall() htpasswd = open("/var/www/localhost/users/" + project[0], "w") for user in users: htpasswd.write("%s:%s\n" % (user[0], user[1])) htpasswd.close() # 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 IS 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() generate_users_file(req.cursor) 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" if not re.match("^[a-z._]{3,25}$", login): return "your login is either too long, too short, or contains other characters than [a-z] including . and _" # 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! forgot your password?' # 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 and ('activated' not in s or not s['activated']): 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']: s['activated'] = True s.save() c['info_msg'] = "your account has been activated. you will receive an email with a temporary password." 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 AND activated = 1", (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'])) 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" else: try: cracklib.FascistCheck(formdata['new_pw1']) 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() generate_users_file(req.cursor) c['info_msg'] = "new password has been set" return render_to_string("index.html", c) except ValueError, ex: c['error_msg'] = "cannot accept new password: " + ex.message 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("select count(*) from member where project_id = (select id from project where project_name = %s)", (formdata['proj_name'],)) result = cursor.fetchone() if result[0] == 1: c['error_msg'] = "you cannot leave this project! you're its only member! maybe you want to delete it?" else: 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() generate_users_file(req.cursor) 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 contact(req): (c, s, cursor) = session_start(req, None) return render_to_string("contact.html", c) def new_project(req, **formdata): (c, s, cursor) = session_start(req, False) umlauts = False if req.method == "POST": c['name'] = formdata['project_name'] c['desc'] = formdata['project_desc'] c['priv'] = formdata['priv'] cursor.execute("SELECT email FROM user WHERE login = %s", (s['login'],)) c['email'] = cursor.fetchone()[0] cursor.execute("SELECT id FROM project WHERE project_name = %s", (c['name'], )) if cursor.fetchone(): c['error_msg'] = "project %s already exists." % c['name'] elif len(c['name']) < 3: c['error_msg'] = "project name must be at least 3 chars long" elif len(c['desc']) < 0: c['error_msg'] = "project description may not be empty" elif c['name'] == "test": c['error_msg'] = "NO TEST PROJECTS" else: # 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() generate_users_file(req.cursor) c['info_msg'] = "you deleted " + who + " from project " + proj elif what == "add": cursor.execute("select * from member where user_id = %s and project_id = %s", (user_id, project_id)); if cursor.fetchone() == None: cursor.execute("insert into member (user_id, project_id) values (%s, %s)", (user_id, project_id)) req.dbc.commit() generate_users_file(req.cursor) c['info_msg'] = "you added " + who + " to project " + proj else: c['error_msg'] = who + " is already a member of 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) def del_profile(req): (c, s, cursor) = session_start(req, False) cursor.execute("delete from user where login = %s", (s['login'], )) req.dbc.commit() generate_users_file(req.cursor) s['login'] = None s.save() c['info_msg'] = "your profile has been deleted successfully" return render_to_string("index.html", c) def list_projects(req): (c, s, cursor) = session_start(req, None) cursor.execute("SELECT project_name FROM project WHERE private = 0 AND project_name != 'ALL' ORDER BY project_name") result = cursor.fetchall() c['public_projects'] = [] for project in result: ini = trac.config.Configuration('/var/lib/trac/' + project[0] + '/conf/trac.ini') c['public_projects'].append((project[0], ini.get('project', 'name'), ini.get('project','descr'))) cursor.execute("SELECT project_name FROM project WHERE private = 1 AND project_name != 'ALL' ORDER BY project_name") result = cursor.fetchall() c['private_projects'] = [] for project in result: ini = trac.config.Configuration('/var/lib/trac/' + project[0] + '/conf/trac.ini') c['private_projects'].append((project[0], ini.get('project', 'name'), ini.get('project','descr'))) return render_to_string("list_projects.html", c) def genusers(req): (c, s, cursor) = session_start(req, None) generate_users_file(cursor) return "OK" # this is returned whenever a db error occurs def db_error(req): return render_to_string("db_error.html")