Using the repoze.who
Application Programming Interface (API)¶
Using repoze.who
without Middleware¶
An application which does not use the repoze.who
middleware needs
to perform two separate tasks to use repoze.who
machinery:
- At application startup, it must create an
repoze.who.api:APIFactory
instance, populating it with a request classifier, a challenge decider, and a set of plugins. It can do this process imperatively (see Configuring repoze.who via Python Code), or using a declarative configuration file (see Configuring repoze.who via Config File). For the latter case, there is a convenience function,repoze.who.config.make_api_factory_with_config()
:
# myapp/run.py
from repoze.who.config import make_api_factory_with_config
who_api_factory = None
def startup(global_conf):
global who_api_factory
who_api_factory = make_api_factory_with_config(global_conf,
'/path/to/who.config')
- When it needs to use the API, it must call the
APIFactory
, passing the WSGI environment to it. TheAPIFactory
returns an object implementing therepoze.who.interfaces:IRepozeWhoAPI
interface.
# myapp/views.py
from myapp.run import who_api_factory
def my_view(context, request):
who_api = who_api_factory(request.environ)
- Calling the
APIFactory
multiple times within the same request is allowed, and should be very cheap (the API object is cached in the request environment).
Mixed Use of repoze.who
Middleware and API¶
An application which uses the repoze.who
middleware may still need
to interact directly with the IRepozeWhoAPI
object for some purposes.
In such cases, it should call repoze.who.api:get_api()
, passing
the WSGI environment.
from repoze.who.api import get_api
def my_view(context, request):
who_api = get_api(request.environ)
Alternately, the application might configure the APIFactory
at startup,
as above, and then use it to find the API object, or create it if it was
not already created for the current request (e.g. perhaps by the middleware):
def my_view(context, request):
who_api = context.who_api_factory(request.environ)
Writing a Custom Login View¶
repoze.who.api.API
provides a helper method to assist developers
who want to control the details of the login view. The following
BFG example illustrates how this API might be used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def login_view(context, request):
message = ''
who_api = get_api(request.environ)
if 'form.login' in request.POST:
creds = {}
creds['login'] = request.POST['login']
creds['password'] = request.POST['password']
authenticated, headers = who_api.login(creds)
if authenticated:
return HTTPFound(location='/', headers=headers)
message = 'Invalid login.'
else:
# Forcefully forget any existing credentials.
_, headers = who_api.login({})
request.response_headerlist = headers
if 'REMOTE_USER' in request.environ:
del request.environ['REMOTE_USER']
return {'message': message}
|
This application is written as a “hybrid”: the repoze.who
middleware
injects the API object into the WSGI enviornment on each request.
- In line 4, this application extracts the API object from the environ
using
repoze.who.api:get_api()
. - Lines 6 - 8 fabricate a set of credentials, based on the values the user entered in the form.
- In line 9, the application asks the API to authenticate those credentials, returning an identity and a set of respones headers.
- Lines 10 and 11 handle the case of successful authentication: in this case, the application redirects to the site root, setting the headers returned by the API object, which will “remember” the user across requests.
- Line 13 is reached on failed login. In this case, the headers returned in line 9 will be “forget” headers, clearing any existing cookies or other tokens.
- Lines 14 - 16 perform a “fake” login, in order to get the “forget” headers.
- Line 18 sets the “forget” headers to clear any authenticated user for subsequent requests.
- Lines 19 - 20 clear any authenticated user for the current request.
- Line 22 returns any message about a failed login to the rendering template.
Interfaces¶
-
interface
repoze.who.interfaces.
IAPI
¶ Facade for stateful invocation of underlying plugins.
-
authenticate
()¶ -> {identity}
o Return an authenticated identity mapping, extracted from the request environment.
o If no identity can be authenticated, return None.
o Identity will include at least a ‘repoze.who.userid’ key, as well as any keys added by metadata plugins.
-
challenge
(status='403 Forbidden', app_headers=())¶ -> wsgi application
o Return a WSGI application which represents a “challenge” (request for credentials) in response to the current request.
-
remember
(identity=None)¶ -> [headers]
O Return a sequence of response headers which suffice to remember the given identity.
o If ‘identity’ is not passed, use the identity in the environment.
-
forget
(identity=None)¶ -> [headers]
O Return a sequence of response headers which suffice to destroy any credentials used to establish an identity.
o If ‘identity’ is not passed, use the identity in the environment.
-
login
(credentials, identifier_name=None)¶ -> (identity, headers)
o This is an API for browser-based application login forms.
- o If ‘identifier_name’ is passed, use it to look up the identifier;
- othewise, use the first configured identifier.
- o Attempt to authenticate ‘credentials’ as though the identifier
- had extracted them.
- o On success, ‘identity’ will be authenticated mapping, and ‘headers’
- will be “remember” headers.
- o On failure, ‘identity’ will be None, and response_headers will be
- “forget” headers.
-
logout
(identifier_name=None)¶ -> (headers)
o This is an API for browser-based application logout.
- o If ‘identifier_name’ is passed, use it to look up the identifier;
- othewise, use the first configured identifier.
o Returned headers will be “forget” headers.
-
-
interface
repoze.who.interfaces.
IPlugin
¶
-
interface
repoze.who.interfaces.
IRequestClassifier
¶ Extends:
repoze.who.interfaces.IPlugin
On ingress: classify a request.
-
__call__
(environ)¶ environ -> request classifier string
This interface is responsible for returning a string value representing a request classification.
o ‘environ’ is the WSGI environment.
-
-
interface
repoze.who.interfaces.
IChallengeDecider
¶ Extends:
repoze.who.interfaces.IPlugin
On egress: decide whether a challenge needs to be presented to the user.
-
__call__
(environ, status, headers)¶ args -> True | False
o ‘environ’ is the WSGI environment.
- o ‘status’ is the HTTP status as returned by the downstream
- WSGI application.
- o ‘headers’ are the headers returned by the downstream WSGI
- application.
This interface is responsible for returning True if a challenge needs to be presented to the user, False otherwise.
-
-
interface
repoze.who.interfaces.
IIdentifier
¶ Extends:
repoze.who.interfaces.IPlugin
On ingress: Extract credentials from the WSGI environment and turn them into an identity.
On egress (remember): Conditionally set information in the response headers allowing the remote system to remember this identity.
On egress (forget): Conditionally set information in the response headers allowing the remote system to forget this identity (during a challenge).
-
identify
(environ)¶ On ingress:
- environ -> { k1 : v1
- , … , kN : vN } | None
o ‘environ’ is the WSGI environment.
- o If credentials are found, the returned identity mapping will
- contain an arbitrary set of key/value pairs. If the identity is based on a login and password, the environment is recommended to contain at least ‘login’ and ‘password’ keys as this provides compatibility between the plugin and existing authenticator plugins. If the identity can be ‘preauthenticated’ (e.g. if the userid is embedded in the identity, such as when we’re using ticket-based authentication), the plugin should set the userid in the special ‘repoze.who.userid’ key; no authenticators will be asked to authenticate the identity thereafer.
- o Return None to indicate that the plugin found no appropriate
- credentials.
- o Only IIdentifier plugins which match one of the the current
- request’s classifications will be asked to perform identification.
- o An identifier plugin is permitted to add a key to the
- environment named ‘repoze.who.application’, which should be an arbitrary WSGI application. If an identifier plugin does so, this application is used instead of the downstream application set up within the middleware. This feature is useful for identifier plugins which need to perform redirection to obtain credentials. If two identifier plugins add a ‘repoze.who.application’ WSGI application to the environment, the last one consulted will”win”.
-
remember
(environ, identity)¶ On egress (no challenge required):
args -> [ (header-name, header-value), …] | None
Return a list of headers suitable for allowing the requesting system to remember the identification information (e.g. a Set-Cookie header). Return None if no headers need to be set. These headers will be appended to any headers returned by the downstream application.
-
forget
(environ, identity)¶ On egress (challenge required):
args -> [ (header-name, header-value), …] | None
Return a list of headers suitable for allowing the requesting system to forget the identification information (e.g. a Set-Cookie header with an expires date in the past). Return None if no headers need to be set. These headers will be included in the response provided by the challenge app.
-
-
interface
repoze.who.interfaces.
IAuthenticator
¶ Extends:
repoze.who.interfaces.IPlugin
On ingress: validate the identity and return a user id or None.
-
authenticate
(environ, identity)¶ identity -> ‘userid’ | None
o ‘environ’ is the WSGI environment.
- o ‘identity’ will be a dictionary (with arbitrary keys and
- values).
- o The IAuthenticator should return a single user id (optimally
- a string) if the identity can be authenticated. If the identify cannot be authenticated, the IAuthenticator should return None.
Each instance of a registered IAuthenticator plugin that matches the request classifier will be called N times during a single request, where N is the number of identities found by any IIdentifierPlugin instances.
An authenticator must not raise an exception if it is provided an identity dictionary that it does not understand (e.g. if it presumes that ‘login’ and ‘password’ are keys in the dictionary, it should check for the existence of these keys before attempting to do anything; if they don’t exist, it should return None).
An authenticator is permitted to add extra keys to the ‘identity’ dictionary (e.g., to save metadata from a database query, rather than requiring a separate query from an IMetadataProvider plugin).
-
-
interface
repoze.who.interfaces.
IChallenger
¶ Extends:
repoze.who.interfaces.IPlugin
On egress: Conditionally initiate a challenge to the user to provide credentials.
Only challenge plugins which match one of the the current response’s classifications will be asked to perform a challenge.
-
challenge
(environ, status, app_headers, forget_headers)¶ args -> WSGI application or None
o ‘environ’ is the WSGI environment.
- o ‘status’ is the status written into start_response by the
- downstream application.
- o ‘app_headers’ is the headers list written into start_response by the
- downstream application.
- o ‘forget_headers’ is a list of headers which must be passed
- back in the response in order to perform credentials reset (logout). These come from the ‘forget’ method of IIdentifier plugin used to do the request’s identification.
Examine the values passed in and return a WSGI application (a callable which accepts environ and start_response as its two positional arguments, ala PEP 333) which causes a challenge to be performed. Return None to forego performing a challenge.
-
-
interface
repoze.who.interfaces.
IMetadataProvider
¶ Extends:
repoze.who.interfaces.IPlugin
On ingress: When an identity is authenticated, metadata providers may scribble on the identity dictionary arbitrarily. Return values from metadata providers are ignored.
-
add_metadata
(environ, identity)¶ Add metadata to the identity (which is a dictionary). One value is always guaranteed to be in the dictionary when add_metadata is called: ‘repoze.who.userid’, representing the user id of the identity. Availability and composition of other keys will depend on the identifier plugin which created the identity.
-