summaryrefslogblamecommitdiffstats
path: root/index.py
blob: 6592e902a0af2e3b588f2912f3c261ee6101fea2 (plain) (tree)
1
2
3
4
5
                                                               



                                                                   










                                                   


                  

                           
                                            



                                                              

                                    
 
                                                     
 


                                              
 



                                   
 
               



                                                 

                      




                                                                            



                                                                                                
 

                                      
 
                            
 

                         
 
            
 

                        
 




                                                                             
 
                                 




                                                                      
                                                                                 

 

                                                                             
                                                 
                                

                                                                                  
 



                                                                                                                                                                        
 

                                                          
 
                        
 


                                                                                 
                                                                                    
 

                                                                                         
 


                                                                          
 

                                                                                                   
 




                                  
 


                                                          
 



                                              

 



































                                                                            
                                             
                               
 


                                        
 

                                                                                                                    
 


                                                                
 


                                                   
 


                                                                            
 


                                                                                          
 



                                                                                                            
 

                                                                                    
 



                                                  
 


                                  







                               

                                             


                              
                                             
 



                                                                                             
 





                                                                                     
 

                                                                 
 



                                                                                                                       
 
                                            


                           
                                             
 


                                                                                                                               
 





                                                    
 
                          


                



                                              


                              
                                             
 




                                                                                                            
 
                                               


                              
                                             
 


                                                                                    
 



                                                                   
 


                                                                                          
 
                                               


                             
                                              
 


                                                                                                         
 

                                                        
 

                                                          
 


                                                          
 




                                                                                                           
 


                                                                            
 
 


                                                                            
 
                                              


                              





























                                                                                                                                                                                  


                 

                                              
 
                 

                                              

                                 
                                              
 




                                            
 

                                                                                
 











                                                                                       
 


                                                                                                                        
 
                                                  


                             
                                              
 

                                   
 
                                
 

                                                                             
 


                                                             
 
                          
 


                                                                                                    
 

                                                                      
 


                                                      
 

                                                                       
 

                               
 




                                                                                                                  
 








                                                                                                                     
 

                                                
 



                                                                                               
 
                                              


                     
                                              
 


                                                                       
 

                     
 
                                                                
 
                                            


                       
                                             
 

                                                                                                                        
 
                             
 


                                                                                                         
 

                                                                                                                        
 
                              
 


                                                                                                          
 
                                                    

 
                  


                                             
 


                                             
# 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 &nbsp;*.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 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")