root/AuthKit/trunk/examples/docs/multi.py

Revision 114 (checked in by thejimmyg, 1 year ago)

Significant restructuring (hopefully 100% backwards compatible) and support for multiple authentication methods and dynamic switching in a single middleware component

Line 
1 # -*- coding: utf-8 -*-
2
3 """\
4 This is an example of multiple middleware components being setup at once in
5 such away that the authentication method used is dynamically selected at
6 runtime. What happens is that each authentication method is based an
7 ``AuthSwitcher`` object which when a status response matching a code specified
8 in ``authkit.setup.intercept`` is intercepted, will perform a ``switch()``
9 check. If the check returns ``True`` then that particular ``AuthHandler`` will
10 be triggered.
11
12 In this example the ``AuthSwitcher`` decides whether to trigger a particular
13 ``AuthHandler`` based on the value of the ``authkit.authhandler`` key in
14 ``environ`` and this is set when visiting the various paths such as
15 ``/private_openid``, ``private_basic`` etc. Notice though that the form method
16 is setup with a ``Default`` ``AuthSwitcher`` whose ``switch()`` method always
17 returns ``True``. This means of the other ``AuthHandlers`` don't handle the
18 response, the from method's handler will. This is the case if you visit
19 ``/private``.
20
21 Once the user is authenticated the ``UserSetter``s middleware sets the
22 ``REMOTE_USER`` environ variable so that the user remains signed in. This means
23 that a user can authenticate with say digest authentication and when they visit
24 ``/private_openid`` they will still be signed in, even if that wasn't the
25 method they used to authenticate.
26
27 Also, note that you are free to implement and use any ``AuthSwitcher`` you like
28 as long as it derives from ``AuthSwitcher`` so you could for example choose
29 which authentication method to show to the user based on their IP address.
30
31 The authentication details for each method in this example are:
32
33 Form: username2:password2
34 Digest: test:test (or any username which is identical to the password)
35 Basic: test:test (or any username which is identical to the password)
36 OpenID: any valid openid (get one at myopenid.com for example)
37
38 Of course, everything is totally configurable.
39 """
40
41 # Needed for the middleware
42 from authkit.authenticate import middleware, strip_base
43 from authkit.authenticate.open_id import OpenIDAuthHandler, \
44     OpenIDUserSetter, load_openid_config
45 from authkit.authenticate.form import FormAuthHandler, load_form_config
46 from authkit.authenticate.cookie import CookieUserSetter, load_cookie_config
47 from authkit.authenticate.basic import BasicAuthHandler, BasicUserSetter, \
48     load_basic_config
49 from authkit.authenticate.digest import DigestAuthHandler, \
50     DigestUserSetter, load_digest_config, digest_password
51 from authkit.authenticate.multi import MultiHandler, AuthSwitcher, \
52     status_checker
53
54 # Needed for the sample app
55 from authkit.authorize import authorize_request
56 from authkit.permissions import RemoteUser, no_authkit_users_in_environ, \
57     AuthKitConfigError
58
59 # Setup a switcher which will switch if environ['authkit.authhandler'] equals
60 # the method name specified and if the response matches one of the values of
61 # authkit.setup.intercept
62
63 class EnvironKeyAuthSwitcher(AuthSwitcher):
64     def __init__(self, method, key='authkit.authhandler'):
65         self.method = method
66         self.key = key
67
68     def switch(self, environ, status, headers):
69         if environ.has_key(self.key) and environ[self.key] == self.method:
70             return True
71         return False
72
73 class Default(AuthSwitcher):
74     def switch(self, environ, status, headers):
75         return True
76
77 def make_multi_middleware(app, auth_conf, app_conf=None, global_conf=None,
78     prefix='authkit.'):
79
80     # Load the configurations and any associated middleware
81     app, oid_auth_params, oid_user_params = load_openid_config(
82         app, strip_base(auth_conf, 'openid.'))
83     app, form_auth_params, form_user_params = load_form_config(
84         app, strip_base(auth_conf, 'form.'))
85     app, cookie_auth_params, cookie_user_params = load_cookie_config(
86         app, strip_base(auth_conf, 'cookie.'))
87     app, basic_auth_params, basic_user_params = load_basic_config(
88         app, strip_base(auth_conf, 'basic.'))
89     app, digest_auth_params, digest_user_params = load_digest_config(
90         app, strip_base(auth_conf, 'digest.'))
91
92     # The cookie plugin doesn't provide an AuthHandler so no config
93     assert cookie_auth_params == None
94     # The form plugin doesn't provide a UserSetter (it uses cookie)
95     assert form_user_params == None
96
97     # Setup the MultiHandler to switch between authentication methods
98     # based on the value of environ['authkit.authhandler'] if a 401 is
99     # raised
100     app = MultiHandler(app)
101     app.add_method('openid', OpenIDAuthHandler, **oid_auth_params)
102     app.add_checker('openid', EnvironKeyAuthSwitcher('openid'))
103     app.add_method('basic', BasicAuthHandler, **basic_auth_params)
104     app.add_checker('basic', EnvironKeyAuthSwitcher('basic'))
105     app.add_method('digest', DigestAuthHandler, **digest_auth_params)
106     app.add_checker('digest', EnvironKeyAuthSwitcher('digest'))
107     app.add_method('form', FormAuthHandler, **form_auth_params)
108     app.add_checker('form', Default())
109
110     # Add the user setters to set REMOTE_USER on each request once the
111     # user is signed on.
112     app = DigestUserSetter(app, **digest_user_params)
113     app = BasicUserSetter(app, **basic_user_params)
114     # OpenID relies on cookie so needs to be set up first
115     app = OpenIDUserSetter(app, **oid_user_params)
116     app = CookieUserSetter(app, **cookie_user_params)
117
118     return app
119
120 def sample_app(environ, start_response):
121     """
122     A sample WSGI application that returns a 401 status code when the path
123     ``/private`` is entered, triggering the authenticate middleware to
124     prompt the user to sign in.
125    
126     If used with the authenticate middleware's form method, the path
127     ``/signout`` will display a signed out message if
128     ``authkit.cookie.signout = /signout`` is specified in the config file.
129    
130     If used with the authenticate middleware's forward method, the path
131     ``/signin`` should be used to display the sign in form.
132    
133     The path ``/`` always displays the environment.
134     """
135     if environ['PATH_INFO']=='/private':
136         authorize_request(environ, RemoteUser())
137     if environ['PATH_INFO']=='/private_openid':
138         environ['authkit.authhandler'] = 'openid'
139         authorize_request(environ, RemoteUser())
140     if environ['PATH_INFO']=='/private_digest':
141         environ['authkit.authhandler'] = 'digest'
142         authorize_request(environ, RemoteUser())
143     if environ['PATH_INFO']=='/private_basic':
144         environ['authkit.authhandler'] = 'basic'
145         authorize_request(environ, RemoteUser())
146     if environ['PATH_INFO'] == '/signout':
147         start_response(
148             '200 OK',
149             [('Content-type', 'text/plain; charset=UTF-8')]
150         )
151         if environ.has_key('REMOTE_USER'):
152             return ["Signed Out"]
153         else:
154             return ["Not signed in"]
155     elif environ['PATH_INFO'] == '/signin':
156         start_response(
157             '200 OK',
158             [('Content-type', 'text/plain; charset=UTF-8')]
159         )
160         return ["Your application would display a \nsign in form here."]
161     else:
162         start_response(
163             '200 OK',
164             [('Content-type', 'text/plain; charset=UTF-8')]
165         )
166     result = [
167         'You Have Access To This Page.\n\nHere is the environment...\n\n'
168     ]
169     for k,v in environ.items():
170         result.append('%s: %s\n'%(k,v))
171     return result
172
173 def digest_authenticate(environ, realm, username):
174     password = username
175     return digest_password(realm, username, password)
176
177 def basic_authenticate(environ, username, password):
178     return username == password
179
180 app = middleware(
181     sample_app,
182     middleware = make_multi_middleware,
183     openid_path_signedin='/private',
184     openid_store_type='file',
185     openid_store_config='',
186     openid_charset='UTF-8',
187     cookie_secret='secret encryption string',
188     cookie_signoutpath = '/signout',
189     openid_sreg_required = 'fullname,nickname,city,country',
190     openid_sreg_optional = 'timezone,email',
191     openid_sreg_policyurl =  'http://localhost:5000',
192     form_authenticate_user_data = """
193         username2:password2
194     """,
195     form_charset='UTF-8',
196     digest_realm='Test Realm',
197     digest_authenticate_function=digest_authenticate,
198     basic_realm='Test Realm',
199     basic_authenticate_function=basic_authenticate,
200 )
201
202 # XXX No Session variables in the config now.
203
204 if __name__ == '__main__':
205     from paste.httpserver import serve
206     serve(app, host='0.0.0.0', port=8080)
207
Note: See TracBrowser for help on using the browser.