| 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 |
|
|---|