myauth.py
5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# -*- encoding: utf-8 -*-
# TODO:
# - testar logout
# - session fixation
# - member_of...
import cherrypy
import sqlite3
from hashlib import sha256
from mako.lookup import TemplateLookup
import urllib
import html
SESSION_KEY = 'userid'
templates = TemplateLookup(directories=['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.
'''
success = False
tryhash = sha256(password.encode('utf-8')).hexdigest()
# search student in database
conn = sqlite3.connect(db)
sql_cmd = 'SELECT * FROM students WHERE number=?'
found = conn.execute(sql_cmd, [uid]).fetchone()
if found is not None:
num, name, pw_hash = found
if pw_hash == '':
# update password on first login
pw_hash = tryhash
sql_cmd = 'UPDATE students SET password=? WHERE number=?'
conn.execute(sql_cmd, (pw_hash, num))
conn.commit()
cherrypy.log.error('Student %s updated his password.' % uid, 'APPLICATION')
# check password
success = (tryhash == pw_hash)
if success:
cherrypy.log.error('Student %s logged in.' % uid, 'APPLICATION')
else:
cherrypy.log.error('Student %s wrong password.' % uid, 'APPLICATION')
else:
cherrypy.log.error('Student %s not found!' % uid, 'APPLICATION')
conn.close()
return name if success else 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 <username> is in <groupname>
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="/><script>...
# will inject the string "/><string>... into the variable from_page
# of this function. Rendering a login page will then inject (reflect)
# the script. Not sure how serious it can be, since it is not stored
# in the server, only executed on the client browser...
# html.escape will replace > < & etc used in html by < etc
from_page = html.escape(from_page)
db = self.database
if uid is not None and pw is not None:
name = credentials_ok(uid, pw, db)
if name is not None:
cherrypy.session[SESSION_KEY] = cherrypy.request.login = uid
cherrypy.session['name'] = name
raise cherrypy.HTTPRedirect(from_page)
logintemplate = templates.get_template('login.html')
return logintemplate.render(from_page=from_page)
@cherrypy.expose
def logout(self):
# FIXME logout is not working!!!
cherrypy.lib.sessions.expire() # session cookie expires on client
user = cherrypy.session.get(SESSION_KEY, None)
cherrypy.session[SESSION_KEY] = None
if user is not None:
cherrypy.request.login = None
raise cherrypy.HTTPRedirect('/')