File: multi.py

package info (click to toggle)
python-authkit 0.4.1~r143-1
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 740 kB
  • ctags: 703
  • sloc: python: 4,643; makefile: 39; sh: 33
file content (207 lines) | stat: -rw-r--r-- 8,468 bytes parent folder | download | duplicates (3)
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# -*- coding: utf-8 -*-

"""\
This is an example of multiple middleware components being setup at once in
such away that the authentication method used is dynamically selected at
runtime. What happens is that each authentication method is based an
``AuthSwitcher`` object which when a status response matching a code specified
in ``authkit.setup.intercept`` is intercepted, will perform a ``switch()``
check. If the check returns ``True`` then that particular ``AuthHandler`` will
be triggered. 

In this example the ``AuthSwitcher`` decides whether to trigger a particular
``AuthHandler`` based on the value of the ``authkit.authhandler`` key in
``environ`` and this is set when visiting the various paths such as
``/private_openid``, ``private_basic`` etc. Notice though that the form method
is setup with a ``Default`` ``AuthSwitcher`` whose ``switch()`` method always
returns ``True``. This means of the other ``AuthHandlers`` don't handle the
response, the from method's handler will. This is the case if you visit
``/private``.

Once the user is authenticated the ``UserSetter``s middleware sets the
``REMOTE_USER`` environ variable so that the user remains signed in. This means
that a user can authenticate with say digest authentication and when they visit
``/private_openid`` they will still be signed in, even if that wasn't the
method they used to authenticate.

Also, note that you are free to implement and use any ``AuthSwitcher`` you like
as long as it derives from ``AuthSwitcher`` so you could for example choose
which authentication method to show to the user based on their IP address.

The authentication details for each method in this example are:

Form: username2:password2 
Digest: test:test (or any username which is identical to the password)
Basic: test:test (or any username which is identical to the password)
OpenID: any valid openid (get one at myopenid.com for example)

Of course, everything is totally configurable.
"""

# Needed for the middleware
from authkit.authenticate import middleware, strip_base
from authkit.authenticate.open_id import OpenIDAuthHandler, \
    OpenIDUserSetter, load_openid_config
from authkit.authenticate.form import FormAuthHandler, load_form_config
from authkit.authenticate.cookie import CookieUserSetter, load_cookie_config
from authkit.authenticate.basic import BasicAuthHandler, BasicUserSetter, \
    load_basic_config
from authkit.authenticate.digest import DigestAuthHandler, \
    DigestUserSetter, load_digest_config, digest_password
from authkit.authenticate.multi import MultiHandler, AuthSwitcher, \
    status_checker

# Needed for the sample app
from authkit.authorize import authorize_request
from authkit.permissions import RemoteUser, no_authkit_users_in_environ, \
    AuthKitConfigError

# Setup a switcher which will switch if environ['authkit.authhandler'] equals
# the method name specified and if the response matches one of the values of
# authkit.setup.intercept

class EnvironKeyAuthSwitcher(AuthSwitcher):
    def __init__(self, method, key='authkit.authhandler'):
        self.method = method
        self.key = key

    def switch(self, environ, status, headers):
        if environ.has_key(self.key) and environ[self.key] == self.method:
            return True
        return False

class Default(AuthSwitcher):
    def switch(self, environ, status, headers):
        return True

def make_multi_middleware(app, auth_conf, app_conf=None, global_conf=None, 
    prefix='authkit.'):

    # Load the configurations and any associated middleware
    app, oid_auth_params, oid_user_params = load_openid_config(
        app, strip_base(auth_conf, 'openid.'))
    app, form_auth_params, form_user_params = load_form_config(
        app, strip_base(auth_conf, 'form.'))
    app, cookie_auth_params, cookie_user_params = load_cookie_config(
        app, strip_base(auth_conf, 'cookie.'))
    app, basic_auth_params, basic_user_params = load_basic_config(
        app, strip_base(auth_conf, 'basic.'))
    app, digest_auth_params, digest_user_params = load_digest_config(
        app, strip_base(auth_conf, 'digest.'))

    # The cookie plugin doesn't provide an AuthHandler so no config
    assert cookie_auth_params == None
    # The form plugin doesn't provide a UserSetter (it uses cookie)
    assert form_user_params == None

    # Setup the MultiHandler to switch between authentication methods
    # based on the value of environ['authkit.authhandler'] if a 401 is 
    # raised
    app = MultiHandler(app)
    app.add_method('openid', OpenIDAuthHandler, **oid_auth_params)
    app.add_checker('openid', EnvironKeyAuthSwitcher('openid'))
    app.add_method('basic', BasicAuthHandler, **basic_auth_params)
    app.add_checker('basic', EnvironKeyAuthSwitcher('basic'))
    app.add_method('digest', DigestAuthHandler, **digest_auth_params)
    app.add_checker('digest', EnvironKeyAuthSwitcher('digest'))
    app.add_method('form', FormAuthHandler, **form_auth_params)
    app.add_checker('form', Default())

    # Add the user setters to set REMOTE_USER on each request once the
    # user is signed on.
    app = DigestUserSetter(app, **digest_user_params)
    app = BasicUserSetter(app, **basic_user_params)
    # OpenID relies on cookie so needs to be set up first
    app = OpenIDUserSetter(app, **oid_user_params)
    app = CookieUserSetter(app, **cookie_user_params)

    return app

def sample_app(environ, start_response):
    """
    A sample WSGI application that returns a 401 status code when the path 
    ``/private`` is entered, triggering the authenticate middleware to 
    prompt the user to sign in.
    
    If used with the authenticate middleware's form method, the path 
    ``/signout`` will display a signed out message if 
    ``authkit.cookie.signout = /signout`` is specified in the config file.
    
    If used with the authenticate middleware's forward method, the path 
    ``/signin`` should be used to display the sign in form.
    
    The path ``/`` always displays the environment.
    """
    if environ['PATH_INFO']=='/private':
        authorize_request(environ, RemoteUser())
    if environ['PATH_INFO']=='/private_openid':
        environ['authkit.authhandler'] = 'openid'
        authorize_request(environ, RemoteUser())
    if environ['PATH_INFO']=='/private_digest':
        environ['authkit.authhandler'] = 'digest'
        authorize_request(environ, RemoteUser())
    if environ['PATH_INFO']=='/private_basic':
        environ['authkit.authhandler'] = 'basic'
        authorize_request(environ, RemoteUser())
    if environ['PATH_INFO'] == '/signout':
        start_response(
            '200 OK', 
            [('Content-type', 'text/plain; charset=UTF-8')]
        )
        if environ.has_key('REMOTE_USER'):
            return ["Signed Out"]
        else:
            return ["Not signed in"]
    elif environ['PATH_INFO'] == '/signin':
        start_response(
            '200 OK', 
            [('Content-type', 'text/plain; charset=UTF-8')]
        )
        return ["Your application would display a \nsign in form here."]
    else:
        start_response(
            '200 OK', 
            [('Content-type', 'text/plain; charset=UTF-8')]
        )
    result = [
        'You Have Access To This Page.\n\nHere is the environment...\n\n'
    ]
    for k,v in environ.items():
        result.append('%s: %s\n'%(k,v))
    return result

def digest_authenticate(environ, realm, username):
    password = username
    return digest_password(realm, username, password)

def basic_authenticate(environ, username, password):
    return username == password

app = middleware(
    sample_app, 
    middleware = make_multi_middleware, 
    openid_path_signedin='/private',
    openid_store_type='file',
    openid_store_config='',
    openid_charset='UTF-8',
    cookie_secret='secret encryption string',
    cookie_signoutpath = '/signout',
    openid_sreg_required = 'fullname,nickname,city,country',
    openid_sreg_optional = 'timezone,email',
    openid_sreg_policyurl =  'http://localhost:5000',
    form_authenticate_user_data = """
        username2:password2
    """,
    form_charset='UTF-8',
    digest_realm='Test Realm',
    digest_authenticate_function=digest_authenticate,
    basic_realm='Test Realm', 
    basic_authenticate_function=basic_authenticate,
)

# XXX No Session variables in the config now.

if __name__ == '__main__':
    from paste.httpserver import serve
    serve(app, host='0.0.0.0', port=8080)