This repository has been archived by the owner on Oct 12, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
/
PasswordProtectedPages
137 lines (112 loc) · 5.69 KB
/
PasswordProtectedPages
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
Many sites have both public and private pages, where anyone can access the public pages, but only logged-in, registered users can access the private pages.
Here's a simple implementation using CherryPy 2.0 and Python 2.4 decorators:
{{{
#!python
from cherrypy import cpg
def html(title):
'''HTML decorator: wrap the results of the called function in HTML
header and footer tags, setting the page title in the HTML HEADer.
Because this decorator takes arguments, it has to return _another_
decorator that actually uses the arguments (see PEP 318 for
details).
'''
def _wrapper(fn):
def _innerWrapper(*args, **kwargs):
'''The .join(list)() nonsense is just in case you're using
the generator filter (since the filter doesn't get a
chance to act inside the decorator.'''
return '<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY>%s</BODY></HTML>' % (title,
"".join(list(fn(*args, **kwargs))))
return _innerWrapper
return _wrapper
def needsLogin(fn):
'''Login decorator: if the user is not logged in, send up a login
rather than the page content. Point the login form back to the
same page to avoid a redirect when the user logs in correctly.
'''
def _wrapper(*args, **kwargs):
if 'userid' in cpg.request.sessionMap:
# User is logged in; allow access
return fn(*args, **kwargs)
else:
# User isn't logged in yet.
# See if the user just tried to log in
try:
submit = kwargs['login']
email = kwargs['loginEmail']
password = kwargs['loginPassword']
except KeyError:
# No, this wasn't a login attempt. Send the user to
# the login "page".
return loginPage(cpg.request.path)
# Now look up the user id by the email and password
userid = getUserId(email, password)
if userid is None:
# Bad login attempt
return loginPage(cpg.request.path, 'Invalid email address or password.')
# User is now logged in, so retain the userid and show the content
cpg.request.sessionMap['userid'] = userid
# If you don't want the 'login', 'loginEmail' and 'loginPassword'
# values to be passed to the original method that was interrupted
# by the login challenge, uncomment the following
# del kwargs['login']
# del kwargs['loginEmail']
# del kwargs['loginPassword']
return fn(*args, **kwargs)
return _wrapper
def getUserId(email, password):
'''Simple function to look up a user id from email and password.
Naturally, this would be stored in a database rather than
hardcoded, and the password would be stored in a hashed format
rather than in cleartext.
Returns the userid on success, or None on failure.
'''
accounts = {('tim@lesher.ws', 'foo'): 'tim'}
return accounts.get((email,password), None)
@html('Sitename: Log In')
def loginPage(targetPage, message=None):
'''Return a login "pagelet" that replaces the regular content if
the user is not logged in.'''
result = []
result.append('<h1>Sitename Login</h1>')
if message is not None:
result.append('<p>%s</p>' % message)
result.append('<form action=%s method=post>' % targetPage)
result.append('<p>Email Address: <input type=text name="loginEmail"></p>')
result.append('<p>Password: <input type=password name="loginPassword"></p>')
result.append('<p><input type="submit" name="login" value="Log In"></p>')
result.append('</form>')
return '\n'.join(result)
class Site:
@cpg.expose
@html("Sitename: Home Page")
def index(self, *args, **kwargs):
return '''<h1>SiteName</h1>
<h2>Home Page</h2>
<p><a href="public">Public Page</a></p>
<p><a href="private">Private Page</a> <i>(registered users only)</i></p>
'''
@cpg.expose
@html("Sitename: Public Page")
def public(self, *args, **kwargs):
return '''<h1>SiteName</h1>
<h2>Public Page</h2>
<p><a href="/">Go back home</a></p>'''
@cpg.expose
@needsLogin
@html("Sitename: Private Page")
def private(self, *args, **kwargs):
return '''<h1>SiteName</h1>
<h2>Private Page</h2>
<p><a href="/">Go back home</a></p>'''
cpg.root = Site()
cpg.server.start(configFile = 'site.conf')
}}}
This example uses two decorators:
1. An {{{html}}} decorator that wraps the return value of its function in HTML tags, including a <TITLE> tag in the HTML <HEAD>.
1. A {{{needsLogin}}} decorator that checks whether a user is logged in before providing "private" content.
I originally implemented this using {{{cherrypy.lib.aspect}}}, but I ran into some limitations of that class:
1. You get precisely one {{{_before}}} and {{{_after}}} method for each {{{Aspect}}}-derived class, so you can't easily mix-and-match multiple aspects.
1. If your {{{_before}}} handler returns STOP, your {{{_after}}} handler never gets called. This means that if you're trying to implement header and footer aspects, your header aspect has to do the same work as the footer aspect if it halts processing.
1. The handlers don't get access to either the {{{self}}} object or the arguments of the method to be called.
I started implementing a version of {{{Aspect}}} that corrected these problems, but I soon realized I was re-implementing decorators, so I switched to a decorator-based implementation.