# -*- encoding: utf-8 -*- # TODO: # - testar logout # - session fixation # - member_of... import cherrypy import sqlite3 import bcrypt from mako.lookup import TemplateLookup import urllib import html from os import path # path where this file is located server_path = path.dirname(path.realpath(__file__)) SESSION_KEY = 'userid' templates = TemplateLookup(directories=[server_path+'/templates'], input_encoding='utf-8') def credentials_ok(uid, password, db): '''Given a userid, password and database, checks if the userid exists in the database, a checks if the password is correct. The password is updated if it's initially empty. Returns the name of the student on success, otherwise returns None. ''' with sqlite3.connect(db) as c: # search student in database sql_cmd = 'SELECT name,password FROM students WHERE number=?' try: name, pwhash = c.execute(sql_cmd, [uid]).fetchone() except: cherrypy.log.error('Student %s not found!' % uid, 'APPLICATION') return None # student found in db if pwhash == '': # update password on first login hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) sql_cmd = 'UPDATE students SET password=? WHERE number=?' c.execute(sql_cmd, (hashed, uid)) cherrypy.log.error('Student %s updated his password.' % uid, 'APPLICATION') return name else: # check password if bcrypt.hashpw(password.encode('utf-8'), pwhash) == pwhash: cherrypy.log.error('Student %s logged in.' % uid, 'APPLICATION') return name else: cherrypy.log.error('Student %s wrong password.' % uid, 'APPLICATION') return None def check_auth(*args, **kwargs): """A tool that looks in config for 'auth.require'. If found and it is not None, a login is required and the entry is evaluated as a list of conditions that the user must fulfill""" conditions = cherrypy.request.config.get('auth.require', None) get_parmas = urllib.request.quote(cherrypy.request.request_line.split()[1]) if conditions is not None: user = cherrypy.session.get(SESSION_KEY, None) # if already logged in, must satisfy conditions # else redirects to login page if user is not None: cherrypy.request.login = user for condition in conditions: # A condition is just a callable that returns true or false if not condition(): raise cherrypy.HTTPRedirect("/auth/login?from_page=%s" % get_parmas) else: raise cherrypy.HTTPRedirect("/auth/login?from_page=%s" % get_parmas) cherrypy.tools.auth = cherrypy.Tool('before_handler', check_auth) def require(*conditions): """A decorator that appends conditions to the auth.require config variable.""" def decorate(f): if not hasattr(f, '_cp_config'): f._cp_config = dict() if 'auth.require' not in f._cp_config: f._cp_config['auth.require'] = [] f._cp_config['auth.require'].extend(conditions) return f return decorate # Conditions are callables that return True # if the user fulfills the conditions they define, False otherwise # # They can access the current username as cherrypy.request.login # # Define those at will however suits the application. def member_of(groupname): def check(): # replace with actual check if is in return cherrypy.request.login == 'joe' and groupname == 'admin' # FIXME return check def name_is(reqd_username): return lambda: reqd_username == cherrypy.request.login # These might be handy def any_of(*conditions): """Returns True if any of the conditions match""" def check(): for c in conditions: if c(): return True return False return check # By default all conditions are required, but this might still be # needed if you want to use it inside of an any_of(...) condition def all_of(*conditions): """Returns True if all of the conditions match""" def check(): for c in conditions: if not c(): return False return True return check # ============================================================================ class AuthController(object): def __init__(self, database): self.database = database @cherrypy.expose def login(self, uid=None, pw=None, from_page='/'): # XSS vulnerability if from_page is maliciously formed. # e.g. /auth/login?from_page="/>