commit | author | age
|
d95e97
|
1 |
.. _about_plugins: |
TS |
2 |
|
|
3 |
About :mod:`repoze.who` Plugins |
|
4 |
=============================== |
|
5 |
|
|
6 |
Plugin Types |
|
7 |
------------ |
|
8 |
|
|
9 |
Identifier Plugins |
|
10 |
++++++++++++++++++ |
|
11 |
|
|
12 |
You can register a plugin as willing to act as an "identifier". An |
|
13 |
identifier examines the WSGI environment and attempts to extract |
|
14 |
credentials from the environment. These credentials are used by |
4a067e
|
15 |
authenticator plugins to perform authentication. |
TS |
16 |
|
d95e97
|
17 |
|
TS |
18 |
Authenticator Plugins |
|
19 |
+++++++++++++++++++++ |
|
20 |
|
|
21 |
You may register a plugin as willing to act as an "authenticator". |
|
22 |
Authenticator plugins are responsible for resolving a set of |
|
23 |
credentials provided by an identifier plugin into a user id. |
|
24 |
Typically, authenticator plugins will perform a lookup into a database |
|
25 |
or some other persistent store, check the provided credentials against |
|
26 |
the stored data, and return a user id if the credentials can be |
|
27 |
validated. |
|
28 |
|
|
29 |
The user id provided by an authenticator is eventually passed to |
|
30 |
downstream WSGI applications in the "REMOTE_USER' environment |
|
31 |
variable. Additionally, the "identity" of the user (as provided by |
|
32 |
the identifier from whence the identity came) is passed along to |
|
33 |
downstream application in the ``repoze.who.identity`` environment |
|
34 |
variable. |
|
35 |
|
4a067e
|
36 |
|
d95e97
|
37 |
Metadata Provider Plugins |
TS |
38 |
+++++++++++++++++++++++++ |
|
39 |
|
|
40 |
You may register a plugin as willing to act as a "metadata provider" |
|
41 |
(aka mdprovider). Metadata provider plugins are responsible for |
|
42 |
adding arbitrary information to the identity dictionary for |
|
43 |
consumption by downstream applications. For instance, a metadata |
|
44 |
provider plugin may add "group" information to the the identity. |
4a067e
|
45 |
|
d95e97
|
46 |
|
TS |
47 |
Challenger Plugins |
|
48 |
++++++++++++++++++ |
|
49 |
|
|
50 |
You may register a plugin as willing to act as a "challenger". |
|
51 |
Challenger plugins are responsible for initiating a challenge to the |
|
52 |
requesting user. Challenger plugins are invoked by :mod:`repoze.who` when it |
|
53 |
decides a challenge is necessary. A challenge might consist of |
|
54 |
displaying a form or presenting the user with a basic or digest |
|
55 |
authentication dialog. |
4a067e
|
56 |
|
d95e97
|
57 |
|
eb49d5
|
58 |
.. _default_plugins: |
TS |
59 |
|
d95e97
|
60 |
Default Plugin Implementations |
TS |
61 |
------------------------------ |
|
62 |
|
|
63 |
:mod:`repoze.who` ships with a variety of default plugins that do |
|
64 |
authentication, identification, challenge and metadata provision. |
|
65 |
|
|
66 |
.. module:: repoze.who.plugins.auth_tkt |
|
67 |
|
|
68 |
.. class:: AuthTktCookiePlugin(secret [, cookie_name='auth_tkt' [, secure=False [, include_ip=False]]]) |
|
69 |
|
ac7dde
|
70 |
An :class:`AuthTktCookiePlugin` is an ``IIdentifier`` and ``IAuthenticator`` |
TS |
71 |
plugin which remembers its identity state in a client-side cookie. |
d7df42
|
72 |
This plugin uses the ``paste.auth.auth_tkt``"auth ticket" protocol and |
DT |
73 |
is compatible with Apache's mod_auth_tkt. |
ac7dde
|
74 |
It should be instantiated passing a *secret*, which is used to encrypt the |
d95e97
|
75 |
cookie on the client side and decrypt the cookie on the server side. |
TS |
76 |
The cookie name used to store the cookie value can be specified |
|
77 |
using the *cookie_name* parameter. If *secure* is False, the cookie |
|
78 |
will be sent across any HTTP or HTTPS connection; if it is True, the |
|
79 |
cookie will be sent only across an HTTPS connection. If |
|
80 |
*include_ip* is True, the ``REMOTE_ADDR`` of the WSGI environment |
|
81 |
will be placed in the cookie. |
|
82 |
|
ac7dde
|
83 |
Normally, using the plugin as an identifier requires also using it as |
TS |
84 |
an authenticator. |
|
85 |
|
d95e97
|
86 |
.. note:: |
TS |
87 |
Using the *include_ip* setting for public-facing applications may |
|
88 |
cause problems for some users. `One study |
|
89 |
<http://westpoint.ltd.uk/advisories/Paul_Johnston_GSEC.pdf>`_ reports |
|
90 |
that as many as 3% of users change their IP addresses legitimately |
|
91 |
during a session. |
67b3a7
|
92 |
|
J |
93 |
.. note:: |
|
94 |
Plugin supports remembering user data in the cookie by saving user dict into ``identity['userdata']`` |
|
95 |
parameter of ``remember`` method. They are sent unencrypted and protected by checksum. |
|
96 |
Data will then be returned every time by ``identify``. This dict must be compatible with |
|
97 |
``urllib.urlencode`` function (``urllib.urlparse.urlencode`` in python 3). |
|
98 |
Saving keys/values with unicode characters is supported only under python 3. |
d95e97
|
99 |
|
d7df42
|
100 |
.. note:: |
DT |
101 |
Plugin supports multiple digest algorithms. It defaults to md5 to match |
|
102 |
the default for mod_auth_tkt and paste.auth.auth_tkt. However md5 is not |
|
103 |
recommended as there are viable attacks against the hash. Any algorithm |
|
104 |
from the hashlib library can be specified, currently only sha256 and sha512 |
|
105 |
are supported by mod_auth_tkt. |
|
106 |
|
d95e97
|
107 |
.. module:: repoze.who.plugins.basicauth |
TS |
108 |
|
|
109 |
.. class:: BasicAuthPlugin(realm) |
|
110 |
|
|
111 |
A :class:`BasicAuthPlugin` plugin is both an ``IIdentifier`` and |
|
112 |
``IChallenger`` plugin that implements the Basic Access |
|
113 |
Authentication scheme described in :rfc:`2617`. It looks for |
|
114 |
credentials within the ``HTTP-Authorization`` header sent by |
|
115 |
browsers. It challenges by sending an ``WWW-Authenticate`` header |
|
116 |
to the browser. The single argument *realm* indicates the basic |
|
117 |
auth realm that should be sent in the ``WWW-Authenticate`` header. |
|
118 |
|
|
119 |
.. module:: repoze.who.plugins.htpasswd |
|
120 |
|
|
121 |
.. class:: HTPasswdPlugin(filename, check) |
|
122 |
|
|
123 |
A :class:`HTPasswdPlugin` is an ``IAuthenticator`` implementation |
|
124 |
which compares identity information against an Apache-style htpasswd |
|
125 |
file. The *filename* argument should be an absolute path to the |
|
126 |
htpasswd file' the *check* argument is a callable which takes two |
|
127 |
arguments: "password" and "hashed", where the "password" argument is |
|
128 |
the unencrypted password provided by the identifier plugin, and the |
|
129 |
hashed value is the value stored in the htpasswd file. If the |
|
130 |
hashed value of the password matches the hash, this callable should |
|
131 |
return True. A default implementation named ``crypt_check`` is |
|
132 |
available for use as a check function (on UNIX) as |
|
133 |
``repoze.who.plugins.htpasswd:crypt_check``; it assumes the values |
|
134 |
in the htpasswd file are encrypted with the UNIX ``crypt`` function. |
|
135 |
|
ab7f3c
|
136 |
.. module:: repoze.who.plugins.redirector |
TS |
137 |
|
|
138 |
.. class:: RedirectorPlugin(login_url, came_from_param, reason_param, reason_header) |
|
139 |
|
|
140 |
A :class:`RedirectorPlugin` is an ``IChallenger`` plugin. |
|
141 |
It redirects to a configured login URL at egress if a challenge is |
|
142 |
required . |
|
143 |
*login_url* is the URL that should be redirected to when a |
|
144 |
challenge is required. *came_from_param* is the name of an optional |
|
145 |
query string parameter: if configured, the plugin provides the current |
|
146 |
request URL in the redirected URL's query string, using the supplied |
|
147 |
parameter name. *reason_param* is the name of an optional |
|
148 |
query string parameter: if configured, and the application supplies |
|
149 |
a header matching *reason_header* (defaulting to |
|
150 |
``X-Authorization-Failure-Reason``), the plugin includes that reason in |
|
151 |
the query string of the redirected URL, using the supplied parameter name. |
|
152 |
*reason_header* is an optional parameter overriding the default response |
|
153 |
header name (``X-Authorization-Failure-Reason``) which |
|
154 |
the plugin checks to find the application-supplied reason for the challenge. |
e75a29
|
155 |
*reason_header* cannot be set unless *reason_param* is also set. |
ab7f3c
|
156 |
|
d95e97
|
157 |
.. module:: repoze.who.plugins.sql |
TS |
158 |
|
|
159 |
.. class:: SQLAuthenticatorPlugin(query, conn_factory, compare_fn) |
|
160 |
|
|
161 |
A :class:`SQLAuthenticatorPlugin` is an ``IAuthenticator`` |
|
162 |
implementation which compares login-password identity information |
|
163 |
against data in an arbitrary SQL database. The *query* argument |
|
164 |
should be a SQL query that returns two columns in a single row |
|
165 |
considered to be the user id and the password respectively. The SQL |
|
166 |
query should contain Python-DBAPI style substitution values for |
|
167 |
``%(login)``, e.g. ``SELECT user_id, password FROM users WHERE login |
|
168 |
= %(login)``. The *conn_factory* argument should be a callable that |
|
169 |
returns a DBAPI database connection. The *compare_fn* argument |
|
170 |
should be a callable that accepts two arguments: ``cleartext`` and |
|
171 |
``stored_password_hash``. It should compare the hashed version of |
|
172 |
cleartext and return True if it matches the stored password hash, |
|
173 |
otherwise it should return False. A comparison function named |
|
174 |
``default_password_compare`` exists in the |
|
175 |
``repoze.who.plugins.sql`` module demonstrating this. The |
|
176 |
:class:`SQLAuthenticatorPlugin`\'s ``authenticate`` method will |
|
177 |
return the user id of the user unchanged to :mod:`repoze.who`. |
|
178 |
|
|
179 |
.. class:: SQLMetadataProviderPlugin(name, query, conn_factory, filter) |
|
180 |
|
|
181 |
A :class:`SQLMetatadaProviderPlugin` is an ``IMetadataProvider`` |
|
182 |
implementation which adds arbitrary metadata to the identity on |
|
183 |
ingress using data from an arbitrary SQL database. The *name* |
|
184 |
argument should be a string. It will be used as a key in the |
|
185 |
identity dictionary. The *query* argument should be a SQL query |
|
186 |
that returns arbitrary data from the database in a form that accepts |
|
187 |
Python-binding style DBAPI arguments. It should expect that a |
|
188 |
``__userid`` value will exist in the dictionary that is bound. The |
|
189 |
SQL query should contain Python-DBAPI style substitution values for |
|
190 |
(at least) ``%(__userid)``, e.g. ``SELECT group FROM groups WHERE |
|
191 |
user_id = %(__userid)``. The *conn_factory* argument should be a |
|
192 |
callable that returns a DBAPI database connection. The *filter* |
|
193 |
argument should be a callable that accepts the result of the DBAPI |
|
194 |
``fetchall`` based on the SQL query. It should massage the data |
|
195 |
into something that will be set in the environment under the *name* |
|
196 |
key. |
|
197 |
|
|
198 |
|
|
199 |
Writing :mod:`repoze.who` Plugins |
|
200 |
--------------------------------- |
|
201 |
|
|
202 |
:mod:`repoze.who` can be extended arbitrarily through the creation of |
|
203 |
plugins. Plugins are of one of four types: identifier plugins, |
|
204 |
authenticator plugins, metadata provider plugins, and challenge |
|
205 |
plugins. |
|
206 |
|
4a067e
|
207 |
|
d95e97
|
208 |
Writing An Identifier Plugin |
TS |
209 |
++++++++++++++++++++++++++++ |
|
210 |
|
|
211 |
An identifier plugin (aka an ``IIdentifier`` plugin) must do three |
|
212 |
things: extract credentials from the request and turn them into an |
|
213 |
"identity", "remember" credentials, and "forget" credentials. |
|
214 |
|
|
215 |
Here's a simple cookie identification plugin that does these three |
|
216 |
things :: |
|
217 |
|
|
218 |
class InsecureCookiePlugin(object): |
|
219 |
|
|
220 |
def __init__(self, cookie_name): |
|
221 |
self.cookie_name = cookie_name |
|
222 |
|
|
223 |
def identify(self, environ): |
a79685
|
224 |
from paste.request import get_cookies |
d95e97
|
225 |
cookies = get_cookies(environ) |
TS |
226 |
cookie = cookies.get(self.cookie_name) |
|
227 |
|
|
228 |
if cookie is None: |
|
229 |
return None |
|
230 |
|
|
231 |
import binascii |
|
232 |
try: |
|
233 |
auth = cookie.value.decode('base64') |
|
234 |
except binascii.Error: # can't decode |
|
235 |
return None |
|
236 |
|
|
237 |
try: |
|
238 |
login, password = auth.split(':', 1) |
|
239 |
return {'login':login, 'password':password} |
|
240 |
except ValueError: # not enough values to unpack |
|
241 |
return None |
|
242 |
|
|
243 |
def remember(self, environ, identity): |
|
244 |
cookie_value = '%(login)s:%(password)s' % identity |
|
245 |
cookie_value = cookie_value.encode('base64').rstrip() |
|
246 |
from paste.request import get_cookies |
|
247 |
cookies = get_cookies(environ) |
|
248 |
existing = cookies.get(self.cookie_name) |
|
249 |
value = getattr(existing, 'value', None) |
|
250 |
if value != cookie_value: |
|
251 |
# return a Set-Cookie header |
|
252 |
set_cookie = '%s=%s; Path=/;' % (self.cookie_name, cookie_value) |
|
253 |
return [('Set-Cookie', set_cookie)] |
|
254 |
|
|
255 |
def forget(self, environ, identity): |
|
256 |
# return a expires Set-Cookie header |
|
257 |
expired = ('%s=""; Path=/; Expires=Sun, 10-May-1971 11:59:00 GMT' % |
|
258 |
self.cookie_name) |
|
259 |
return [('Set-Cookie', expired)] |
|
260 |
|
|
261 |
def __repr__(self): |
|
262 |
return '<%s %s>' % (self.__class__.__name__, id(self)) |
|
263 |
|
4a067e
|
264 |
|
d95e97
|
265 |
.identify |
TS |
266 |
~~~~~~~~~ |
|
267 |
|
|
268 |
The ``identify`` method of our InsecureCookiePlugin accepts a single |
|
269 |
argument "environ". This will be the WSGI environment dictionary. |
|
270 |
Our plugin attempts to grub through the cookies sent by the client, |
|
271 |
trying to find one that matches our cookie name. If it finds one that |
|
272 |
matches, it attempts to decode it and turn it into a login and a |
|
273 |
password, which it returns as values in a dictionary. This dictionary |
|
274 |
is thereafter known as an "identity". If it finds no credentials in |
|
275 |
cookies, it returns None (which is not considered an identity). |
|
276 |
|
|
277 |
More generally, the ``identify`` method of an ``IIdentifier`` plugin |
|
278 |
is called once on WSGI request "ingress", and it is expected to grub |
|
279 |
arbitrarily through the WSGI environment looking for credential |
|
280 |
information. In our above plugin, the credential information is |
|
281 |
expected to be in a cookie but credential information could be in a |
|
282 |
cookie, a form field, basic/digest auth information, a header, a WSGI |
|
283 |
environment variable set by some upstream middleware or whatever else |
|
284 |
someone might use to stash authentication information. If the plugin |
|
285 |
finds credentials in the request, it's expected to return an |
|
286 |
"identity": this must be a dictionary. The dictionary is not required |
|
287 |
to have any particular keys or value composition, although it's wise |
|
288 |
if the identification plugin looks for both a login name and a |
|
289 |
password information to return at least {'login':login_name, |
|
290 |
'password':password}, as some authenticator plugins may depend on |
|
291 |
presence of the names "login" and "password" (e.g. the htpasswd and |
|
292 |
sql ``IAuthenticator`` plugins). If an ``IIdentifier`` plugin finds |
|
293 |
no credentials, it is expected to return None. |
|
294 |
|
|
295 |
|
|
296 |
.remember |
|
297 |
~~~~~~~~~ |
|
298 |
|
|
299 |
If we've passed a REMOTE_USER to the WSGI application during ingress |
|
300 |
(as a result of providing an identity that could be authenticated), |
|
301 |
and the downstream application doesn't kick back with an unauthorized |
|
302 |
response, on egress we want the requesting client to "remember" the |
|
303 |
identity we provided if there's some way to do that and if he hasn't |
|
304 |
already, in order to ensure he will pass it back to us on subsequent |
|
305 |
requests without requiring another login. The remember method of an |
|
306 |
``IIdentifier`` plugin is called for each non-unauthenticated |
|
307 |
response. It is the responsibility of the ``IIdentifier`` plugin to |
|
308 |
conditionally return HTTP headers that will cause the client to |
|
309 |
remember the credentials implied by "identity". |
|
310 |
|
|
311 |
Our InsecureCookiePlugin implements the "remember" method by returning |
|
312 |
headers which set a cookie if and only if one is not already set with |
|
313 |
the same name and value in the WSGI environment. These headers will |
|
314 |
be tacked on to the response headers provided by the downstream |
|
315 |
application during the response. |
|
316 |
|
|
317 |
When you write a remember method, most of the work involved is |
|
318 |
determining *whether or not* you need to return headers. It's typical |
|
319 |
to see remember methods that compute an "old state" and a "new state" |
|
320 |
and compare the two against each other in order to determine if |
|
321 |
headers need to be returned. In our example InsecureCookiePlugin, the |
|
322 |
"old state" is ``cookie_value`` and the "new state" is ``value``. |
|
323 |
|
4a067e
|
324 |
|
d95e97
|
325 |
.forget |
TS |
326 |
~~~~~~~ |
|
327 |
|
|
328 |
Eventually the WSGI application we're serving will issue a "401 |
|
329 |
Unauthorized" or another status signifying that the request could not |
|
330 |
be authorized. :mod:`repoze.who` intercepts this status and calls |
|
331 |
``IIdentifier`` plugins asking them to "forget" the credentials |
|
332 |
implied by the identity. It is the "forget" method's job at this |
|
333 |
point to return HTTP headers that will effectively clear any |
|
334 |
credentials on the requesting client implied by the "identity" |
|
335 |
argument. |
|
336 |
|
|
337 |
Our InsecureCookiePlugin implements the "forget" method by returning |
|
338 |
a header which resets the cookie that was set earlier by the remember |
|
339 |
method to one that expires in the past (on my birthday, in fact). |
|
340 |
This header will be tacked onto the response headers provided by the |
|
341 |
downstream application. |
4a067e
|
342 |
|
d95e97
|
343 |
|
TS |
344 |
Writing an Authenticator Plugin |
|
345 |
+++++++++++++++++++++++++++++++ |
|
346 |
|
|
347 |
An authenticator plugin (aka an ``IAuthenticator`` plugin) must do |
|
348 |
only one thing (on "ingress"): accept an identity and check if the |
|
349 |
identity is "good". If the identity is good, it should return a "user |
|
350 |
id". This user id may or may not be the same as the "login" provided |
|
351 |
by the user. An ``IAuthenticator`` plugin will be called for each |
|
352 |
identity found during the identification phase (there may be multiple |
|
353 |
identities for a single request, as there may be multiple |
|
354 |
``IIdentifier`` plugins active at any given time), so it may be called |
|
355 |
multiple times in the same request. |
|
356 |
|
|
357 |
Here's a simple authenticator plugin that attempts to match an |
|
358 |
identity against ones defined in an "htpasswd" file that does just |
|
359 |
that:: |
|
360 |
|
|
361 |
class SimpleHTPasswdPlugin(object): |
|
362 |
|
|
363 |
def __init__(self, filename): |
|
364 |
self.filename = filename |
|
365 |
|
|
366 |
# IAuthenticatorPlugin |
|
367 |
def authenticate(self, environ, identity): |
|
368 |
try: |
|
369 |
login = identity['login'] |
|
370 |
password = identity['password'] |
|
371 |
except KeyError: |
|
372 |
return None |
|
373 |
|
|
374 |
f = open(self.filename, 'r') |
|
375 |
|
|
376 |
for line in f: |
|
377 |
try: |
|
378 |
username, hashed = line.rstrip().split(':', 1) |
|
379 |
except ValueError: |
|
380 |
continue |
|
381 |
if username == login: |
|
382 |
if crypt_check(password, hashed): |
|
383 |
return username |
|
384 |
return None |
|
385 |
|
|
386 |
def crypt_check(password, hashed): |
|
387 |
from crypt import crypt |
|
388 |
salt = hashed[:2] |
|
389 |
return hashed == crypt(password, salt) |
|
390 |
|
|
391 |
An ``IAuthenticator`` plugin implements one "interface" method: |
|
392 |
"authentictate". The formal specification for the arguments and |
|
393 |
return values expected from these methods are available in the |
|
394 |
``interfaces.py`` file in :mod:`repoze.who` as the ``IAuthenticator`` |
|
395 |
interface, but let's examine this method here less formally. |
|
396 |
|
4a067e
|
397 |
|
d95e97
|
398 |
.authenticate |
TS |
399 |
~~~~~~~~~~~~~ |
|
400 |
|
|
401 |
The ``authenticate`` method accepts two arguments: the WSGI |
|
402 |
environment and an identity. Our SimpleHTPasswdPlugin |
|
403 |
``authenticate`` implementation grabs the login and password out of |
|
404 |
the identity and attempts to find the login in the htpasswd file. If |
|
405 |
it finds it, it compares the crypted version of the password provided |
|
406 |
by the user to the crypted version stored in the htpasswd file, and |
|
407 |
finally, if they match, it returns the login. If they do not match, |
|
408 |
it returns None. |
|
409 |
|
|
410 |
.. note:: |
|
411 |
|
|
412 |
Our plugin's ``authenticate`` method does not assume that the keys |
|
413 |
``login`` or ``password`` exist in the identity; although it |
|
414 |
requires them to do "real work" it returns None if they are not |
|
415 |
present instead of raising an exception. This is required by the |
|
416 |
``IAuthenticator`` interface specification. |
4a067e
|
417 |
|
d95e97
|
418 |
|
TS |
419 |
Writing a Challenger Plugin |
|
420 |
+++++++++++++++++++++++++++ |
|
421 |
|
|
422 |
A challenger plugin (aka an ``IChallenger`` plugin) must do only one |
|
423 |
thing on "egress": return a WSGI application which performs a |
|
424 |
"challenge". A WSGI application is a callable that accepts an |
|
425 |
"environ" and a "start_response" as its parameters; see "PEP 333" for |
|
426 |
further definition of what a WSGI application is. A challenge asks |
|
427 |
the user for credentials. |
|
428 |
|
|
429 |
Here's an example of a simple challenger plugin:: |
|
430 |
|
|
431 |
from paste.httpheaders import WWW_AUTHENTICATE |
|
432 |
from paste.httpexceptions import HTTPUnauthorized |
|
433 |
|
|
434 |
class BasicAuthChallengerPlugin(object): |
|
435 |
|
|
436 |
def __init__(self, realm): |
|
437 |
self.realm = realm |
|
438 |
|
|
439 |
# IChallenger |
|
440 |
def challenge(self, environ, status, app_headers, forget_headers): |
|
441 |
head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm) |
|
442 |
if head[0] not in forget_headers: |
|
443 |
head = head + forget_headers |
|
444 |
return HTTPUnauthorized(headers=head) |
|
445 |
|
|
446 |
Note that the plugin implements a single "interface" method: |
|
447 |
"challenge". The formal specification for the arguments and return |
|
448 |
values expected from this method is available in the "interfaces.py" |
|
449 |
file in :mod:`repoze.who` as the ``IChallenger`` interface. This method |
|
450 |
is called when :mod:`repoze.who` determines that the application has |
|
451 |
returned an "unauthorized" response (e.g. a 401). Only one challenger |
|
452 |
will be consulted during "egress" as necessary (the first one to |
|
453 |
return a non-None response). |
|
454 |
|
4a067e
|
455 |
|
d95e97
|
456 |
.challenge |
TS |
457 |
~~~~~~~~~~ |
|
458 |
|
|
459 |
The challenge method takes environ (the WSGI environment), 'status' |
|
460 |
(the status as set by the downstream application), the "app_headers" |
|
461 |
(headers returned by the application), and the "forget_headers" |
|
462 |
(headers returned by all participating ``IIdentifier`` plugins whom |
|
463 |
were asked to "forget" this user). |
|
464 |
|
|
465 |
Our BasicAuthChallengerPlugin takes advantage of the fact that the |
|
466 |
HTTPUnauthorized exception imported from paste.httpexceptions can be |
|
467 |
used as a WSGI application. It first makes sure that we don't repeat |
|
468 |
headers if an identification plugin has already set a |
|
469 |
"WWW-Authenticate" header like ours, then it returns an instance of |
|
470 |
HTTPUnauthorized, passing in merged headers. This will cause a basic |
|
471 |
authentication dialog to be presented to the user. |
4a067e
|
472 |
|
d95e97
|
473 |
|
TS |
474 |
Writing a Metadata Provider Plugin |
|
475 |
++++++++++++++++++++++++++++++++++ |
|
476 |
|
|
477 |
A metadata provider plugin (aka an ``IMetadataProvider`` plugin) must |
|
478 |
do only one thing (on "ingress"): "scribble" on the identity |
|
479 |
dictionary provided to it when it is called. An ``IMetadataProvider`` |
|
480 |
plugin will be called with the final "best" identity found during the |
|
481 |
authentication phase, or not at all if no "best" identity could be |
|
482 |
authenticated. Thus, each ``IMetadataProvider`` plugin will be called |
|
483 |
exactly zero or one times during a request. |
|
484 |
|
|
485 |
Here's a simple metadata provider plugin that provides "property" |
|
486 |
information from a dictionary:: |
|
487 |
|
|
488 |
_DATA = { |
|
489 |
'chris': {'first_name':'Chris', 'last_name':'McDonough'} , |
|
490 |
'whit': {'first_name':'Whit', 'last_name':'Morriss'} |
|
491 |
} |
|
492 |
|
|
493 |
class SimpleMetadataProvider(object): |
|
494 |
|
|
495 |
def add_metadata(self, environ, identity): |
|
496 |
userid = identity.get('repoze.who.userid') |
|
497 |
info = _DATA.get(userid) |
|
498 |
if info is not None: |
|
499 |
identity.update(info) |
|
500 |
|
4a067e
|
501 |
|
d95e97
|
502 |
.add_metadata |
TS |
503 |
~~~~~~~~~~~~~ |
|
504 |
|
|
505 |
Arbitrarily add information to the identity dict based in other data |
|
506 |
in the environment or identity. Our plugin adds ``first_name`` and |
|
507 |
``last_name`` values to the identity if the userid matches ``chris`` |
|
508 |
or ``whit``. |
eb49d5
|
509 |
|
TS |
510 |
|
|
511 |
Known Plugins for :mod:`repoze.who` |
|
512 |
=================================== |
|
513 |
|
|
514 |
|
|
515 |
Plugins shipped with :mod:`repoze.who` |
|
516 |
-------------------------------------- |
|
517 |
|
|
518 |
See :ref:`default_plugins`. |
|
519 |
|
|
520 |
|
a446d6
|
521 |
Deprecated plugins |
TS |
522 |
------------------ |
|
523 |
|
|
524 |
The :mod:`repoze.who.deprecatedplugins` distribution bundles the following |
|
525 |
plugin implementations which were shipped with :mod:`repoze.who` prior |
|
526 |
to version 2.0a3. These plugins are deprecated, and should only be used |
|
527 |
while migrating an existing deployment to replacement versions. |
|
528 |
|
|
529 |
:class:`repoze.who.plugins.cookie.InsecureCookiePlugin` |
|
530 |
An ``IIdentifier`` plugin which stores identification information in an |
|
531 |
insecure form (the base64 value of the username and password separated by |
|
532 |
a colon) in a client-side cookie. Please use the |
|
533 |
:class:`AuthTktCookiePlugin` instead. |
|
534 |
|
|
535 |
:class:`repoze.who.plugins.form.FormPlugin` |
|
536 |
|
|
537 |
An ``IIdentifier`` and ``IChallenger`` plugin, which intercepts form POSTs |
|
538 |
to gather identification at ingress and conditionally displays a login form |
|
539 |
at egress if challenge is required. |
|
540 |
|
|
541 |
Applications should supply their |
|
542 |
own login form, and use :class:`repoze.who.api.API` to authenticate |
|
543 |
and remember users. To replace the challenger role, please use |
|
544 |
:class:`repoze.who.plugins.redirector.RedirectorPlugin`, configured with |
|
545 |
the URL of your application's login form. |
|
546 |
|
|
547 |
:class:`repoze.who.plugins.form.RedirectingFormPlugin` |
|
548 |
|
|
549 |
An ``IIdentifier`` and ``IChallenger`` plugin, which intercepts form POSTs |
|
550 |
to gather identification at ingress and conditionally redirects a login form |
|
551 |
at egress if challenge is required. |
|
552 |
|
|
553 |
Applications should supply their |
|
554 |
own login form, and use :class:`repoze.who.api.API` to authenticate |
|
555 |
and remember users. To replace the challenger role, please use |
|
556 |
:class:`repoze.who.plugins.redirector.RedirectorPlugin`, configured with |
|
557 |
the URL of your application's login form. |
|
558 |
|
|
559 |
|
eb49d5
|
560 |
Third-party Plugins |
TS |
561 |
------------------- |
|
562 |
|
|
563 |
:class:`repoze.who.plugins.zodb.ZODBPlugin` |
|
564 |
This class implements the :class:`repoze.who.interfaces.IAuthenticator` |
|
565 |
and :class:`repoze.who.interfaces.IMetadataProvider` plugin interfaces |
|
566 |
using ZODB database lookups. See |
|
567 |
http://pypi.python.org/pypi/repoze.whoplugins.zodb/ |
|
568 |
|
|
569 |
:class:`repoze.who.plugins.ldap.LDAPAuthenticatorPlugin` |
|
570 |
This class implements the :class:`repoze.who.interfaces.IAuthenticator` |
|
571 |
plugin interface using the :mod:`python-ldap` library to query an LDAP |
|
572 |
database. See http://code.gustavonarea.net/repoze.who.plugins.ldap/ |
|
573 |
|
|
574 |
:class:`repoze.who.plugins.ldap.LDAPAttributesPlugin` |
|
575 |
This class implements the :class:`repoze.who.interfaces.IMetadataProvider` |
|
576 |
plugin interface using the :mod:`python-ldap` library to query an LDAP |
|
577 |
database. See http://code.gustavonarea.net/repoze.who.plugins.ldap/ |
|
578 |
|
|
579 |
:class:`repoze.who.plugins.friendlyform.FriendlyFormPlugin` |
|
580 |
This class implements the :class:`repoze.who.interfaces.IIdentifier` and |
|
581 |
:class:`repoze.who.interfaces.IChallenger` plugin interfaces. It is |
|
582 |
similar to :class:`repoze.who.plugins.form.RedirectingFormPlugin`, |
|
583 |
bt with with additional features: |
|
584 |
|
|
585 |
- Users are not challenged on logout, unless the referrer URL is a |
|
586 |
private one (but that’s up to the application). |
|
587 |
|
|
588 |
- Developers may define post-login and/or post-logout pages. |
|
589 |
|
|
590 |
- In the login URL, the amount of failed logins is available in the |
|
591 |
environ. It’s also increased by one on every login try. This counter |
|
592 |
will allow developers not using a post-login page to handle logins that |
|
593 |
fail/succeed. |
|
594 |
|
|
595 |
See http://code.gustavonarea.net/repoze.who-friendlyform/ |
|
596 |
|
|
597 |
:func:`repoze.who.plugins.openid.identifiers.OpenIdIdentificationPlugin` |
|
598 |
This class implements the :class:`repoze.who.interfaces.IIdentifier`, |
|
599 |
:class:`repoze.who.interfaces.IAuthenticator`, and |
|
600 |
:class:`repoze.who.interfaces.IChallenger` plugin interfaces using OpenId. |
|
601 |
See http://quantumcore.org/docs/repoze.who.plugins.openid/ |
|
602 |
|
|
603 |
:func:`repoze.who.plugins.openid.classifiers.openid_challenge_decider` |
|
604 |
This function provides the :class:`repoze.who.interfaces.IChallengeDecider` |
|
605 |
interface using OpenId. See |
|
606 |
http://quantumcore.org/docs/repoze.who.plugins.openid/ |
|
607 |
|
|
608 |
:class:`repoze.who.plugins.use_beaker.UseBeakerPlugin` |
|
609 |
This packkage provids a :class:`repoze.who.interfaces.IIdentifier` plugin |
|
610 |
using :mod:`beaker.session` cache. See |
|
611 |
http://pypi.python.org/pypi/repoze.who-use_beaker/ |
|
612 |
|
|
613 |
:class:`repoze.who.plugins.cas.main_plugin.CASChallengePlugin` |
|
614 |
This class implements the :class:`repoze.who.interfaces.IIdentifier` |
|
615 |
:class:`repoze.who.interfaces.IAuthenticator`, and |
|
616 |
:class:`repoze.who.interfaces.IChallenger` plugin interfaces using CAS. |
|
617 |
See http://pypi.python.org/pypi/repoze.who.plugins.cas |
|
618 |
|
|
619 |
:class:`repoze.who.plugins.cas.challenge_decider.my_challenge_decider` |
|
620 |
This function provides the :class:`repoze.who.interfaces.IChallengeDecider` |
|
621 |
interface using CAS. See |
|
622 |
http://pypi.python.org/pypi/repoze.who.plugins.cas/ |
|
623 |
|
|
624 |
:class:`repoze.who.plugins.recaptcha.captcha.RecaptchaPlugin` |
|
625 |
This class implements the :class:`repoze.who.interfaces.IAuthenticator` |
|
626 |
plugin interface, using the recaptch API. |
|
627 |
See http://pypi.python.org/pypi/repoze.who.plugins.recaptcha/ |
|
628 |
|
|
629 |
:class:`repoze.who.plugins.sa.SQLAlchemyUserChecker` |
|
630 |
User existence checker for |
|
631 |
:class:`repoze.who.plugins.auth_tkt.AuthTktCookiePlugin`, based on |
|
632 |
the SQLAlchemy ORM. See http://pypi.python.org/pypi/repoze.who.plugins.sa/ |
|
633 |
|
|
634 |
:class:`repoze.who.plugins.sa.SQLAlchemyAuthenticatorPlugin` |
|
635 |
This class implements the :class:`repoze.who.interfaces.IAuthenticator` |
|
636 |
plugin interface, using the the SQLAlchemy ORM. |
|
637 |
See http://pypi.python.org/pypi/repoze.who.plugins.sa/ |
|
638 |
|
|
639 |
:class:`repoze.who.plugins.sa.SQLAlchemyUserMDPlugin` |
|
640 |
This class implements the :class:`repoze.who.interfaces.IMetadataProvider` |
|
641 |
plugin interface, using the the SQLAlchemy ORM. |
|
642 |
See http://pypi.python.org/pypi/repoze.who.plugins.sa/ |
|
643 |
|
|
644 |
:class:`repoze.who.plugins.formcookie.CookieRedirectingFormPlugin` |
|
645 |
This class implements the :class:`repoze.who.interfaces.IIdentifier` and |
|
646 |
:class:`repoze.who.interfaces.IChallenger` plugin interfaces, similar |
|
647 |
to :class:`repoze.who.plugins.form.RedirectingFormPlugin`. The |
|
648 |
plugin tracks the ``came_from`` URL via a cookie, rather than the query |
|
649 |
string. See http://pypi.python.org/pypi/repoze.who.plugins.formcookie/ |