commit | author | age
|
e8080a
|
1 |
""" Simple BFG application demonstrating use of repoze.who in "hybrid" mode. |
TS |
2 |
|
|
3 |
- repoze.who middleware intercepts and validates existing request credentials, |
4a8793
|
4 |
leaving 'REMOTE_USER' in the WSGI environ if they are OK. |
e8080a
|
5 |
|
TS |
6 |
- Application handles login / logout directly, using the repoze.who API |
|
7 |
to validate credentials and set headers. |
|
8 |
""" |
|
9 |
import logging |
|
10 |
import os |
|
11 |
import sys |
|
12 |
from StringIO import StringIO |
|
13 |
|
|
14 |
from paste.httpserver import serve |
|
15 |
from repoze.bfg.authentication import RemoteUserAuthenticationPolicy |
|
16 |
from repoze.bfg.authorization import ACLAuthorizationPolicy |
|
17 |
from repoze.bfg.configuration import Configurator |
|
18 |
from repoze.bfg.security import Allow |
|
19 |
from repoze.bfg.security import Authenticated |
|
20 |
from repoze.bfg.security import DENY_ALL |
|
21 |
from repoze.bfg.security import Everyone |
|
22 |
from repoze.who.api import get_api |
|
23 |
from repoze.who.interfaces import IChallenger |
|
24 |
from repoze.who.middleware import PluggableAuthenticationMiddleware as PAM |
|
25 |
from repoze.who.plugins.basicauth import BasicAuthPlugin |
|
26 |
from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin |
|
27 |
from repoze.who.plugins.redirector import RedirectorPlugin |
|
28 |
from repoze.who.plugins.htpasswd import HTPasswdPlugin |
|
29 |
from repoze.who.classifiers import default_request_classifier |
|
30 |
from repoze.who.classifiers import default_challenge_decider |
|
31 |
from webob import Response |
|
32 |
from webob.exc import HTTPFound |
|
33 |
|
|
34 |
LINK = ' <p><a href="%(url)s">%(title)s</a></p>' |
|
35 |
|
|
36 |
ACTIONS = { |
|
37 |
'root': {'url': '/', 'title': 'Root'}, |
|
38 |
'protected': {'url': '/protected.html', 'title': 'Protected'}, |
|
39 |
'login': {'url': '/login.html', 'title': 'Login'}, |
|
40 |
'logout': {'url': '/logout.html', 'title': 'Logout'}, |
|
41 |
} |
|
42 |
|
|
43 |
def _actions(request): |
|
44 |
names = ['root'] |
|
45 |
if 'REMOTE_USER' in request.environ: |
|
46 |
names.append('protected') |
|
47 |
names.append('logout') |
|
48 |
else: |
|
49 |
names.append('login') |
|
50 |
return '\n'.join([LINK % ACTIONS[x] for x in names]) |
|
51 |
|
|
52 |
|
|
53 |
PAGE = """\ |
|
54 |
<html> |
|
55 |
<body> |
|
56 |
<h1>%(page_title)s</h1> |
|
57 |
%(actions)s |
|
58 |
</body> |
|
59 |
</html> |
|
60 |
""" |
|
61 |
|
|
62 |
def unprotected(request): |
|
63 |
return Response(PAGE % {'page_title': 'Unprotected Page', |
|
64 |
'actions': _actions(request), |
|
65 |
}) |
|
66 |
|
|
67 |
def protected(request): |
|
68 |
return Response(PAGE % {'page_title': 'protected Page', |
|
69 |
'actions': _actions(request), |
|
70 |
}) |
|
71 |
|
|
72 |
|
|
73 |
LOGIN_FORM = """\ |
|
74 |
<html> |
|
75 |
<body> |
|
76 |
<h1> Log In </h1> |
|
77 |
<form method="POST"> |
|
78 |
%(came_from)s |
|
79 |
<p>%(message)s</p> |
|
80 |
<p>Login name: <input type="text" name="login" /></p> |
|
81 |
<p>Password: <input type="password" name="password" /></p> |
|
82 |
<p><input type="submit" name="form.login" value="Log In"/></p> |
|
83 |
</form> |
|
84 |
</body> |
|
85 |
</html> |
|
86 |
""" |
|
87 |
|
|
88 |
def login(request): |
|
89 |
message = '' |
|
90 |
info = {} |
|
91 |
|
|
92 |
# Remember any 'came_from', for redirection on succcesful login. |
|
93 |
came_from = request.params.get('came_from') |
|
94 |
if came_from is not None: |
|
95 |
info['came_from'] = ( |
|
96 |
'<input type="hidden" name="came_from" value="%s" />' % came_from) |
|
97 |
else: |
|
98 |
info['came_from'] = '' |
|
99 |
|
|
100 |
who_api = get_api(request.environ) |
|
101 |
if 'form.login' in request.POST: |
|
102 |
# Validate credentials. |
|
103 |
creds = {} |
|
104 |
creds['login'] = request.POST['login'] |
|
105 |
creds['password'] = request.POST['password'] |
|
106 |
authenticated, headers = who_api.login(creds) |
|
107 |
|
|
108 |
if authenticated: |
|
109 |
# Redirect to 'came_from', or to root. |
|
110 |
# headers here are "remember" headers, setting the |
|
111 |
# auth_tkt cookies. |
|
112 |
return HTTPFound(location=came_from or '/', |
|
113 |
headers=headers) |
|
114 |
|
|
115 |
else: |
|
116 |
message = 'Invalid login.' |
|
117 |
else: |
|
118 |
# Forcefully forget any existing credentials. |
|
119 |
_, headers = who_api.login({}) |
|
120 |
|
|
121 |
# Headers here are "forget" headers, clearing the auth_tkt cookies. |
|
122 |
request.response_headerlist = headers |
|
123 |
|
|
124 |
if 'REMOTE_USER' in request.environ: |
|
125 |
del request.environ['REMOTE_USER'] |
|
126 |
|
|
127 |
info['message'] = message |
|
128 |
|
|
129 |
return Response(LOGIN_FORM % info) |
|
130 |
|
|
131 |
|
|
132 |
def logout(request): |
|
133 |
# Use repoze.who API to get "forget" headers. |
|
134 |
who_api = get_api(request.environ) |
|
135 |
return HTTPFound(location='/', headers=who_api.logout()) |
|
136 |
|
|
137 |
|
|
138 |
class Root(object): |
|
139 |
__acl__ = [(Allow, Authenticated, ('view_protected',)), |
|
140 |
(Allow, Everyone, ('view',)), |
|
141 |
DENY_ALL, |
|
142 |
] |
|
143 |
|
|
144 |
def get_root(*args, **kw): |
|
145 |
return Root() |
|
146 |
|
|
147 |
|
|
148 |
if __name__ == '__main__': |
|
149 |
# Configure the BFG application |
|
150 |
|
|
151 |
## Set up security policies, root object, etc. |
|
152 |
authentication_policy=RemoteUserAuthenticationPolicy() |
|
153 |
authorization_policy=ACLAuthorizationPolicy() |
|
154 |
config = Configurator( |
|
155 |
root_factory=get_root, |
|
156 |
default_permission='view', |
|
157 |
authentication_policy=authentication_policy, |
|
158 |
authorization_policy=authorization_policy, |
|
159 |
) |
|
160 |
config.begin() |
|
161 |
|
|
162 |
## Configure views |
|
163 |
config.add_view(unprotected) |
|
164 |
config.add_view(protected, 'protected.html', permission='view_protected') |
|
165 |
config.add_view(login, 'login.html') |
|
166 |
config.add_view(logout, 'logout.html') |
|
167 |
config.end() |
|
168 |
|
|
169 |
## Create the app object. |
|
170 |
app = config.make_wsgi_app() |
|
171 |
|
|
172 |
# Configure the repoze.who middleware: |
|
173 |
|
|
174 |
## fake .htpasswd authentication source |
|
175 |
io = StringIO() |
|
176 |
for name, password in [('admin', 'admin'), |
|
177 |
('user', 'user')]: |
|
178 |
io.write('%s:%s\n' % (name, password)) |
|
179 |
io.seek(0) |
|
180 |
def cleartext_check(password, hashed): |
|
181 |
return password == hashed |
|
182 |
htpasswd = HTPasswdPlugin(io, cleartext_check) |
|
183 |
|
|
184 |
## other plugins |
|
185 |
basicauth = BasicAuthPlugin('repoze.who') |
d7df42
|
186 |
auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt', digest_algo="sha512") |
e8080a
|
187 |
redirector = RedirectorPlugin(login_url='/login.html') |
TS |
188 |
redirector.classifications = {IChallenger:['browser'] } # only for browser |
|
189 |
|
|
190 |
## group / order plugins by function |
|
191 |
identifiers = [('auth_tkt', auth_tkt), |
|
192 |
('basicauth', basicauth)] |
|
193 |
authenticators = [('auth_tkt', auth_tkt), |
|
194 |
('htpasswd', htpasswd)] |
|
195 |
challengers = [('redirector', redirector), |
|
196 |
('basicauth', basicauth)] |
|
197 |
mdproviders = [] |
|
198 |
|
|
199 |
## set up who logging, if desired |
|
200 |
log_stream = None |
|
201 |
if os.environ.get('WHO_LOG'): |
|
202 |
log_stream = sys.stdout |
|
203 |
|
|
204 |
# Wrap the middleware around the application. |
|
205 |
middleware = PAM(app, |
|
206 |
identifiers, |
|
207 |
authenticators, |
|
208 |
challengers, |
|
209 |
mdproviders, |
|
210 |
default_request_classifier, |
|
211 |
default_challenge_decider, |
|
212 |
log_stream = log_stream, |
|
213 |
log_level = logging.DEBUG |
|
214 |
) |
|
215 |
|
|
216 |
# Serve up the WSGI stack. |
|
217 |
serve(middleware, host='0.0.0.0') |