David Tulloh
2016-04-20 d7df42ae13a2a9bfb73a76ed96997dad88a794a9
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')