| | |
| | | Middleware Responsibilities |
| | | =========================== |
| | | :mod:`repoze.who` Narrative Documentation |
| | | ========================================= |
| | | |
| | | :mod:`repoze.who` as middleware has one major function on ingress: it |
| | | conditionally places identification and authentication information |
| | | (including a ``REMOTE_USER`` value) into the WSGI environment and |
| | | allows the request to continue to a downstream WSGI application. |
| | | Using :mod:`repoze.who` as WSGI Middleware |
| | | ------------------------------------------ |
| | | |
| | | :mod:`repoze.who` as middleware has one major function on egress: it |
| | | examines the headers set by the downstream application, the WSGI |
| | | environment, or headers supplied by other plugins and conditionally |
| | | challenges for credentials. |
| | | :mod:`repoze.who` was originally developed for use as authentication |
| | | middleware in a WSGI pipeline, for use by applications which only |
| | | needed to obtain an "authenticated user" to enforce a given security |
| | | policy. |
| | | |
| | | Configuration Points |
| | | ==================== |
| | | See :ref:`middleware_responsibilities` for a description of this use case. |
| | | |
| | | Classifiers |
| | | ----------- |
| | | |
| | | :mod:`repoze.who` "classifies" the request on middleware ingress. |
| | | Request classification happens before identification and |
| | | authentication. A request from a browser might be classified a |
| | | different way than a request from an XML-RPC client. |
| | | :mod:`repoze.who` uses request classifiers to decide which other |
| | | components to consult during subsequent identification, |
| | | authentication, and challenge steps. Plugins are free to advertise |
| | | themselves as willing to participate in identification and |
| | | authorization for a request based on this classification. The request |
| | | classification system is pluggable. :mod:`repoze.who` provides a |
| | | default classifier that you may use. |
| | | Using :mod:`repoze.who` without WSGI Middleware |
| | | ----------------------------------------------- |
| | | |
| | | You may extend the classification system by making :mod:`repoze.who` aware |
| | | of a different request classifier implementation. |
| | | Some applications might want to use a configured set of |
| | | :mod:`repoze.who` plugins to do identification and authentication for |
| | | a request, outside the context of using :mod:`repoze.who` middleware. |
| | | For example, a performance-sensitive application might wish to defer |
| | | the effort of identifying and authenticating a user until the point at |
| | | which authorization is required, knowing that some code paths will not |
| | | need to do the work. |
| | | |
| | | Challenge Deciders |
| | | ------------------ |
| | | See :ref:`api_narrative` for a description of this use case. |
| | | |
| | | :mod:`repoze.who` uses a "challenge decider" to decide whether the |
| | | response returned from a downstream application requires a challenge |
| | | plugin to fire. When using the default challenge decider, only the |
| | | status is used (if it starts with ``401``, a challenge is required). |
| | | |
| | | :mod:`repoze.who` also provides an alternate challenge decider, |
| | | ``repoze.who.classifiers.passthrough_challenge_decider``, which avoids |
| | | challenging ``401`` responses which have been "pre-challenged" by the |
| | | application. |
| | | Mixing Middleware and API Uses |
| | | ------------------------------ |
| | | |
| | | You may supply a different challenge decider as necessary. |
| | | Some applications might use the :mod:`repoze.who` middleware for most |
| | | authentication purposes, but need to participate more directly in the |
| | | mechanics of identification and authorization for some portions of the |
| | | application. For example, consider a system which allows users to |
| | | sign up online for membership in a site: once the user completes |
| | | registration, such an application might wish to log the user in |
| | | transparently, and thus needs to interact with the configured |
| | | :mod:`repoze.who` middleware to generate response headers, ensuring |
| | | that the user's next request is properly authenticated. |
| | | |
| | | Plugins |
| | | ------- |
| | | See :ref:`middleware_api_hybrid` for a description of this use case. |
| | | |
| | | :mod:`repoze.who` has core functionality designed around the concept |
| | | of plugins. Plugins are instances that are willing to perform one or |
| | | more identification- and/or authentication-related duties. Each |
| | | plugin can be configured arbitrarily. |
| | | |
| | | :mod:`repoze.who` consults the set of configured plugins when it |
| | | intercepts a WSGI request, and gives some subset of them a chance to |
| | | influence what :mod:`repoze.who` does for the current request. |
| | | Configuring :mod:`repoze.who` |
| | | ----------------------------- |
| | | |
| | | .. note:: As of :mod:`repoze.who` 1.0.7, the ``repoze.who.plugins`` |
| | | package is a namespace package, intended to make it possible for |
| | | people to ship eggs which are who plugins as, |
| | | e.g. ``repoze.who.plugins.mycoolplugin``. |
| | | |
| | | Lifecycle of a Request |
| | | ====================== |
| | | |
| | | :mod:`repoze.who` performs duties both on middleware "ingress" and on |
| | | middleware "egress". |
| | | |
| | | Request (Ingress) Stages |
| | | ------------------------ |
| | | |
| | | :mod:`repoze.who` performs the following operations in the following |
| | | order during middleware ingress: |
| | | |
| | | 1. Request Classification |
| | | |
| | | The WSGI environment is examined and the request is classified |
| | | into one "type" of request. The callable named as the |
| | | ``classifer`` argument to the :mod:`repoze.who` middleware |
| | | constructor is used to perform the classification. It returns a |
| | | value that is considered to be the request classification (a |
| | | single string). |
| | | |
| | | 2. Identification |
| | | |
| | | Identifiers which nominate themselves as willing to extract data |
| | | for a particular class of request (as provided by the request |
| | | classifier) will be consulted to retrieve credentials data from |
| | | the environment. For example, a basic auth identifier might use |
| | | the ``HTTP_AUTHORIZATION`` header to find login and password |
| | | information. Identifiers are also responsible for providing |
| | | header information to set and remove authentication information in |
| | | the response during egress. |
| | | |
| | | 3. Authentication |
| | | |
| | | Authenticators which nominate themselves as willing to |
| | | authenticate for a particular class of request will be consulted |
| | | to compare information provided by the identification plugins |
| | | that returned credentials. For example, an htpasswd |
| | | authenticator might look in a file for a user record matching |
| | | any of the identities. If it finds one, and if the password |
| | | listed in the record matches the password provided by an |
| | | identity, the userid of the user would be returned (which would |
| | | be the same as the login name). |
| | | |
| | | 4. Metadata Provision |
| | | |
| | | The identity of the authenticated user found during the |
| | | authentication step can be augmented with arbitrary metadata. |
| | | For example, a metadata provider plugin might augment the |
| | | identity with first, middle and last names, or a more |
| | | specialized metadata provider might augment the identity with a |
| | | list of role or group names. |
| | | |
| | | Response (Egress) Stages |
| | | ------------------------ |
| | | |
| | | :mod:`repoze.who` performs the following operations in the following |
| | | order during middleware egress: |
| | | |
| | | #. Challenge Decision |
| | | |
| | | The WSGI environment and the status and headers returned by the |
| | | downstream application may be examined to determine whether a |
| | | challenge is required. Typically, only the status is used (if it |
| | | starts with ``401``, a challenge is required, and the challenge |
| | | decider returns True). This behavior is pluggable. It is |
| | | replaced by changing the ``challenge_decider`` argument to the |
| | | middleware. If a challenge is required, the challenge decider |
| | | will return True; if it's not, it will return False. |
| | | |
| | | #. Challenge |
| | | |
| | | If the challenge decider returns True, challengers which nominate |
| | | themselves as willing to execute a challenge for a particular |
| | | class of request (as provided by the classifier) will be |
| | | consulted, and one will be chosen to perform a challenge. A |
| | | challenger plugin can use application-returned headers, the WSGI |
| | | environment, and other items to determine what sort of operation |
| | | should be performed to actuate the challenge. Note that |
| | | :mod:`repoze.who` defers to the identifier plugin which provided the |
| | | identity (if any) to reset credentials at challenge time; this is |
| | | not the responsibility of the challenger. This is known as |
| | | "forgetting" credentials. |
| | | |
| | | #. Remember |
| | | |
| | | The identifier plugin that the "best" set of credentials came from |
| | | (if any) will be consulted to "remember" these credentials if the |
| | | challenge decider returns False. |
| | | |
| | | Plugin Types |
| | | ============ |
| | | |
| | | Identifier Plugins |
| | | ------------------ |
| | | |
| | | You can register a plugin as willing to act as an "identifier". An |
| | | identifier examines the WSGI environment and attempts to extract |
| | | credentials from the environment. These credentials are used by |
| | | authenticator plugins to perform authentication. In some cases, an |
| | | identification plugin can "preauthenticate" an identity (and can thus |
| | | act as an authenticator plugin). |
| | | |
| | | Authenticator Plugins |
| | | --------------------- |
| | | |
| | | You may register a plugin as willing to act as an "authenticator". |
| | | Authenticator plugins are responsible for resolving a set of |
| | | credentials provided by an identifier plugin into a user id. |
| | | Typically, authenticator plugins will perform a lookup into a database |
| | | or some other persistent store, check the provided credentials against |
| | | the stored data, and return a user id if the credentials can be |
| | | validated. |
| | | |
| | | The user id provided by an authenticator is eventually passed to |
| | | downstream WSGI applications in the "REMOTE_USER' environment |
| | | variable. Additionally, the "identity" of the user (as provided by |
| | | the identifier from whence the identity came) is passed along to |
| | | downstream application in the ``repoze.who.identity`` environment |
| | | variable. |
| | | |
| | | Metadata Provider Plugins |
| | | ------------------------- |
| | | |
| | | You may register a plugin as willing to act as a "metadata provider" |
| | | (aka mdprovider). Metadata provider plugins are responsible for |
| | | adding arbitrary information to the identity dictionary for |
| | | consumption by downstream applications. For instance, a metadata |
| | | provider plugin may add "group" information to the the identity. |
| | | |
| | | Challenger Plugins |
| | | ------------------ |
| | | |
| | | You may register a plugin as willing to act as a "challenger". |
| | | Challenger plugins are responsible for initiating a challenge to the |
| | | requesting user. Challenger plugins are invoked by :mod:`repoze.who` when it |
| | | decides a challenge is necessary. A challenge might consist of |
| | | displaying a form or presenting the user with a basic or digest |
| | | authentication dialog. |
| | | |
| | | Default Plugin Implementations |
| | | ============================== |
| | | |
| | | :mod:`repoze.who` ships with a variety of default plugins that do |
| | | authentication, identification, challenge and metadata provision. |
| | | |
| | | .. module:: repoze.who.plugins.auth_tkt |
| | | |
| | | .. class:: AuthTktCookiePlugin(secret [, secretfile=None, [, cookie_name='auth_tkt' [, secure=False [, include_ip=False [, timeout=None [, reissue_time=None [, userid_checker=None]]]]]]]) |
| | | |
| | | An :class:`AuthTktCookiePlugin` is an ``IIdentifier`` plugin which |
| | | remembers its identity state in a client-side cookie. This plugin |
| | | uses the ``paste.auth.auth_tkt``"auth ticket" protocol. It should |
| | | be instantiated passing a *secret*, which is used to encrypt the |
| | | cookie on the client side and decrypt the cookie on the server side. |
| | | The cookie name used to store the cookie value can be specified |
| | | using the *cookie_name* parameter. If *secure* is False, the cookie |
| | | will be sent across any HTTP or HTTPS connection; if it is True, the |
| | | cookie will be sent only across an HTTPS connection. If |
| | | *include_ip* is True, the ``REMOTE_ADDR`` of the WSGI environment |
| | | will be placed in the cookie. If *timeout* is specfied, it is the |
| | | maximum age in seconds allowed for a cookie. If *reissue_time* is |
| | | specified, when we encounter a cookie that is older than the reissue |
| | | time (in seconds), but younger that the timeout, a new cookie will |
| | | be issued. If *timeout* is specified, you must also set |
| | | *reissue_time* to a lower value. |
| | | |
| | | If ``userid_checker`` is provided, it must be a dotted Python name |
| | | that resolves to a function which accepts a userid and returns a |
| | | boolean True or False, indicating whether that user exists in a |
| | | database. This is a workaround. Due to a design bug in repoze.who, |
| | | the only way who can check for user existence is to use one or more |
| | | IAuthenticator plugin ``authenticate`` methods. If an |
| | | IAuthenticator's ``authenticate`` method returns true, it means that |
| | | the user exists. However most IAuthenticator plugins expect *both* |
| | | a username and a password, and will return False unconditionally if |
| | | both aren't supplied. This means that an authenticator can't be |
| | | used to check if the user "only" exists. The identity provided by |
| | | an auth_tkt does not contain a password to check against. The |
| | | actual design bug in repoze.who is this: when a user presents |
| | | credentials from an auth_tkt, he is considered "preauthenticated". |
| | | IAuthenticator.authenticate is just never called for a |
| | | "preauthenticated" identity, which works fine, but it means that the |
| | | user will be considered authenticated even if you deleted the user's |
| | | record from whatever database you happen to be using. However, if |
| | | you use a userid_checker, you can ensure that a user exists for the |
| | | auth_tkt supplied userid. If the userid_checker returns False, the |
| | | auth_tkt credentials are considered "no good". |
| | | |
| | | .. note:: |
| | | Using the *include_ip* setting for public-facing applications may |
| | | cause problems for some users. `One study |
| | | <http://westpoint.ltd.uk/advisories/Paul_Johnston_GSEC.pdf>`_ reports |
| | | that as many as 3% of users change their IP addresses legitimately |
| | | during a session. |
| | | |
| | | .. module:: repoze.who.plugins.basicauth |
| | | |
| | | .. class:: BasicAuthPlugin(realm) |
| | | |
| | | A :class:`BasicAuthPlugin` plugin is both an ``IIdentifier`` and |
| | | ``IChallenger`` plugin that implements the Basic Access |
| | | Authentication scheme described in :rfc:`2617`. It looks for |
| | | credentials within the ``HTTP-Authorization`` header sent by |
| | | browsers. It challenges by sending an ``WWW-Authenticate`` header |
| | | to the browser. The single argument *realm* indicates the basic |
| | | auth realm that should be sent in the ``WWW-Authenticate`` header. |
| | | |
| | | .. module:: repoze.who.plugins.cookie |
| | | |
| | | .. class:: InsecureCookiePlugin(cookie_name) |
| | | |
| | | A :class:`InsecureCookiePlugin` is an ``IIdentifier`` plugin. It |
| | | stores identification information in an insecure form (the base64 |
| | | value of the username and password separated by a colon) in a |
| | | client-side cookie. It accepts a single argument named |
| | | *cookie_name*. This is the cookie name of the cookie used to store |
| | | the identification information. |
| | | |
| | | .. module:: repoze.who.plugins.form |
| | | |
| | | .. class:: FormPlugin(login_form_qs, rememberer_name [, formbody=None [, formcallable=None]]) |
| | | |
| | | A :class:`FormPlugin` is both an ``IIdentifier`` and ``IChallenger`` |
| | | plugin. It intercepts form POSTs to gather identification at |
| | | ingress and conditionally displays a login form at egress if |
| | | challenge is required. *login_form_qs* is a query string name used |
| | | to denote that a form POST is destined for the form plugin (anything |
| | | unique is fine), *rememberer_name* is the "configuration name" of |
| | | another ``IIdentifier`` plugin that will be used to perform |
| | | ``remember`` and ``forget`` duties for the FormPlugin (it does not |
| | | do these itself). For example, if you have a cookie identification |
| | | plugin named ``cookie`` defined in your middleware configuration, |
| | | you might set *rememberer_name* to ``cookie``. *formbody* is a |
| | | literal string that should be displayed as the form body. |
| | | *formcallable* is a callable that will return a form body if |
| | | *formbody* is None. If both *formbody* and *formcallable* are None, |
| | | a default form is used. |
| | | |
| | | .. class:: RedirectingFormPlugin(login_form_url, login_handler_path, logout_handler_path, rememberer_name) |
| | | |
| | | A :class:`RedirectingFormPlugin` is both an ``IIdentifier`` and |
| | | ``IChallenger`` plugin. It intercepts form POSTs to gather |
| | | identification at ingress and conditionally redirects a login form |
| | | at egress if challenge is required (as opposed to the |
| | | :class:`FormPlugin`, it does not handle its own form generation). |
| | | *login_form_url* is a URL that should be redirected to when a |
| | | challenge is required. *login_handler_path* is the path that the |
| | | form will POST to, signifying that the plugin should gather |
| | | credentials. *logout_handler_path* is a path that can be called to |
| | | log the current user out when visited. *rememberer_name* is the |
| | | configuration name of another ``IIdentifier`` plugin that will be |
| | | used to perform ``remember`` and ``forget`` duties for the |
| | | RedirectingFormPlugin (it does not do these itself). For example, |
| | | if you have a cookie identification plugin named ``cookie`` defined |
| | | in your middleware configuration, you might set *rememberer_name* to |
| | | ``cookie``. |
| | | |
| | | .. module:: repoze.who.plugins.htpasswd |
| | | |
| | | .. class:: HTPasswdPlugin(filename, check) |
| | | |
| | | A :class:`HTPasswdPlugin` is an ``IAuthenticator`` implementation |
| | | which compares identity information against an Apache-style htpasswd |
| | | file. The *filename* argument should be an absolute path to the |
| | | htpasswd file' the *check* argument is a callable which takes two |
| | | arguments: "password" and "hashed", where the "password" argument is |
| | | the unencrypted password provided by the identifier plugin, and the |
| | | hashed value is the value stored in the htpasswd file. If the |
| | | hashed value of the password matches the hash, this callable should |
| | | return True. A default implementation named ``crypt_check`` is |
| | | available for use as a check function (on UNIX) as |
| | | ``repoze.who.plugins.htpasswd:crypt_check``; it assumes the values |
| | | in the htpasswd file are encrypted with the UNIX ``crypt`` function. |
| | | |
| | | .. module:: repoze.who.plugins.sql |
| | | |
| | | .. class:: SQLAuthenticatorPlugin(query, conn_factory, compare_fn) |
| | | |
| | | A :class:`SQLAuthenticatorPlugin` is an ``IAuthenticator`` |
| | | implementation which compares login-password identity information |
| | | against data in an arbitrary SQL database. The *query* argument |
| | | should be a SQL query that returns two columns in a single row |
| | | considered to be the user id and the password respectively. The SQL |
| | | query should contain Python-DBAPI style substitution values for |
| | | ``%(login)``, e.g. ``SELECT user_id, password FROM users WHERE login |
| | | = %(login)``. The *conn_factory* argument should be a callable that |
| | | returns a DBAPI database connection. The *compare_fn* argument |
| | | should be a callable that accepts two arguments: ``cleartext`` and |
| | | ``stored_password_hash``. It should compare the hashed version of |
| | | cleartext and return True if it matches the stored password hash, |
| | | otherwise it should return False. A comparison function named |
| | | ``default_password_compare`` exists in the |
| | | ``repoze.who.plugins.sql`` module demonstrating this. The |
| | | :class:`SQLAuthenticatorPlugin`\'s ``authenticate`` method will |
| | | return the user id of the user unchanged to :mod:`repoze.who`. |
| | | |
| | | .. class:: SQLMetadataProviderPlugin(name, query, conn_factory, filter) |
| | | |
| | | A :class:`SQLMetatadaProviderPlugin` is an ``IMetadataProvider`` |
| | | implementation which adds arbitrary metadata to the identity on |
| | | ingress using data from an arbitrary SQL database. The *name* |
| | | argument should be a string. It will be used as a key in the |
| | | identity dictionary. The *query* argument should be a SQL query |
| | | that returns arbitrary data from the database in a form that accepts |
| | | Python-binding style DBAPI arguments. It should expect that a |
| | | ``__userid`` value will exist in the dictionary that is bound. The |
| | | SQL query should contain Python-DBAPI style substitution values for |
| | | (at least) ``%(__userid)``, e.g. ``SELECT group FROM groups WHERE |
| | | user_id = %(__userid)``. The *conn_factory* argument should be a |
| | | callable that returns a DBAPI database connection. The *filter* |
| | | argument should be a callable that accepts the result of the DBAPI |
| | | ``fetchall`` based on the SQL query. It should massage the data |
| | | into something that will be set in the environment under the *name* |
| | | key. |
| | | |
| | | Middleware Configuration via Python Code |
| | | ======================================== |
| | | |
| | | .. module:: repoze.who.middleware |
| | | |
| | | .. class:: PluggableAuthenticationMiddleware(app, identifiers, challengers, mdproviders, classifier, challenge_decider [, log_stream=None [, log_level=logging.INFO[, remote_user_key='REMOTE_USER']]]) |
| | | |
| | | The primary method of configuring the :mod:`repoze.who` middleware is |
| | | to use straight Python code, meant to be consumed by frameworks |
| | | which construct and compose middleware pipelines without using a |
| | | configuration file. |
| | | |
| | | In the middleware constructor: *app* is the "next" application in |
| | | the WSGI pipeline. *identifiers* is a sequence of ``IIdentifier`` |
| | | plugins, *challengers* is a sequence of ``IChallenger`` plugins, |
| | | *mdproviders* is a sequence of ``IMetadataProvider`` plugins. Any |
| | | of these can be specified as the empty sequence. *classifier* is a |
| | | request classifier callable, *challenge_decider* is a challenge |
| | | decision callable. *log_stream* is a stream object (an object with |
| | | a ``write`` method) *or* a ``logging.Logger`` object, *log_level* is |
| | | a numeric value that maps to the ``logging`` module's notion of log |
| | | levels, *remote_user_key* is the key in which the ``REMOTE_USER`` |
| | | (userid) value should be placed in the WSGI environment for |
| | | consumption by downstream applications. |
| | | |
| | | An example configuration which uses the default plugins follows:: |
| | | |
| | | from repoze.who.middleware import PluggableAuthenticationMiddleware |
| | | from repoze.who.interfaces import IIdentifier |
| | | from repoze.who.interfaces import IChallenger |
| | | from repoze.who.plugins.basicauth import BasicAuthPlugin |
| | | from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin |
| | | from repoze.who.plugins.cookie import InsecureCookiePlugin |
| | | from repoze.who.plugins.form import FormPlugin |
| | | from repoze.who.plugins.htpasswd import HTPasswdPlugin |
| | | |
| | | io = StringIO() |
| | | salt = 'aa' |
| | | for name, password in [ ('admin', 'admin'), ('chris', 'chris') ]: |
| | | io.write('%s:%s\n' % (name, password)) |
| | | io.seek(0) |
| | | def cleartext_check(password, hashed): |
| | | return password == hashed |
| | | htpasswd = HTPasswdPlugin(io, cleartext_check) |
| | | basicauth = BasicAuthPlugin('repoze.who') |
| | | auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt') |
| | | form = FormPlugin('__do_login', rememberer_name='auth_tkt') |
| | | form.classifications = { IIdentifier:['browser'], |
| | | IChallenger:['browser'] } # only for browser |
| | | identifiers = [('form', form),('auth_tkt',auth_tkt),('basicauth',basicauth)] |
| | | authenticators = [('htpasswd', htpasswd)] |
| | | challengers = [('form',form), ('basicauth',basicauth)] |
| | | mdproviders = [] |
| | | |
| | | from repoze.who.classifiers import default_request_classifier |
| | | from repoze.who.classifiers import default_challenge_decider |
| | | log_stream = None |
| | | import os |
| | | if os.environ.get('WHO_LOG'): |
| | | log_stream = sys.stdout |
| | | |
| | | middleware = PluggableAuthenticationMiddleware( |
| | | app, |
| | | identifiers, |
| | | authenticators, |
| | | challengers, |
| | | mdproviders, |
| | | default_request_classifier, |
| | | default_challenge_decider, |
| | | log_stream = log_stream, |
| | | log_level = logging.DEBUG |
| | | ) |
| | | |
| | | The above example configures the repoze.who middleware with: |
| | | |
| | | - Three ``IIdentifier`` plugins (form auth, auth_tkt cookie, and a |
| | | basic auth plugin). The form auth plugin is set up to fire only |
| | | when the request is a ``browser`` request (as per the combination of |
| | | the request classifier returning ``browser`` and the framework |
| | | checking against the *classifications* attribute of the plugin, |
| | | which limits ``IIdentifier`` and ``IChallenger`` to the ``browser`` |
| | | classification only). In this setup, when "identification" needs to |
| | | be performed, the form auth plugin will be checked first (if the |
| | | request is a browser request), then the auth_tkt cookie plugin, then |
| | | the basic auth plugin. |
| | | |
| | | - One ``IAuthenticator`` plugin: an htpasswd one. This htpasswd |
| | | plugin is configured with two valid username/password combinations: |
| | | chris/chris, and admin/admin. When an username and password is |
| | | found via any identifier, it will be checked against this |
| | | authenticator. |
| | | |
| | | - Two ``IChallenger`` plugins: the form plugin, then the basic auth |
| | | plugin. The form auth will fire if the request is a ``browser`` |
| | | request, otherwise the basic auth plugin will fire. |
| | | |
| | | The rest of the middleware configuration is for values like logging |
| | | and the classifier and decider implementations. These use the "stock" |
| | | implementations. |
| | | |
| | | .. note:: The ``app`` referred to in the example is the "downstream" |
| | | WSGI application that who is wrapping. |
| | | |
| | | Middleware Configuration via Config File |
| | | ======================================== |
| | | |
| | | :mod:`repoze.who` may be configured using a ConfigParser-style .INI |
| | | file. The configuration file has five main types of sections: plugin |
| | | sections, a general section, an identifiers section, an authenticators |
| | | section, and a challengers section. Each "plugin" section defines a |
| | | configuration for a particular plugin. The identifiers, |
| | | authenticators, and challengers sections refer to these plugins to |
| | | form a site configuration. The general section is general middleware |
| | | configuration. |
| | | |
| | | To configure :mod:`repoze.who` in Python, using an .INI file, call |
| | | the `make_middleware_with_config` entry point, passing the right-hand |
| | | application and the path to the confi file :: |
| | | |
| | | from repoze.who.config import make_middleware_with_config |
| | | who = make_middleware_with_config(app, '/path/to/who.ini') |
| | | |
| | | :mod:`repoze.who`'s configuration file can be pointed to within a PasteDeploy |
| | | configuration file :: |
| | | |
| | | [filter:who] |
| | | use = egg:repoze.who#config |
| | | config_file = %(here)s/who.ini |
| | | log_file = stdout |
| | | log_level = debug |
| | | |
| | | Below is an example of a configuration file (what ``config_file`` |
| | | might point at above ) that might be used to configure the |
| | | :mod:`repoze.who` middleware. A set of plugins are defined, and they |
| | | are referred to by following non-plugin sections. |
| | | |
| | | In the below configuration, five plugins are defined. The form, and |
| | | basicauth plugins are nominated to act as challenger plugins. The |
| | | form, cookie, and basicauth plugins are nominated to act as |
| | | identification plugins. The htpasswd and sqlusers plugins are |
| | | nominated to act as authenticator plugins. :: |
| | | |
| | | [plugin:form] |
| | | # identificaion and challenge |
| | | use = repoze.who.plugins.form:make_plugin |
| | | login_form_qs = __do_login |
| | | rememberer_name = auth_tkt |
| | | form = %(here)s/login_form.html |
| | | |
| | | [plugin:auth_tkt] |
| | | # identification |
| | | use = repoze.who.plugins.auth_tkt:make_plugin |
| | | secret = s33kr1t |
| | | cookie_name = oatmeal |
| | | secure = False |
| | | include_ip = False |
| | | |
| | | [plugin:basicauth] |
| | | # identification and challenge |
| | | use = repoze.who.plugins.basicauth:make_plugin |
| | | realm = 'sample' |
| | | |
| | | [plugin:htpasswd] |
| | | # authentication |
| | | use = repoze.who.plugins.htpasswd:make_plugin |
| | | filename = %(here)s/passwd |
| | | check_fn = repoze.who.plugins.htpasswd:crypt_check |
| | | |
| | | [plugin:sqlusers] |
| | | # authentication |
| | | use = repoze.who.plugins.sql:make_authenticator_plugin |
| | | query = "SELECT userid, password FROM users where login = %(login)s;" |
| | | conn_factory = repoze.who.plugins.sql:make_psycopg_conn_factory |
| | | compare_fn = repoze.who.plugins.sql:default_password_compare |
| | | |
| | | [plugin:sqlproperties] |
| | | name = properties |
| | | use = repoze.who.plugins.sql:make_metadata_plugin |
| | | query = "SELECT firstname, lastname FROM users where userid = %(__userid)s;" |
| | | filter = my.package:filter_propmd |
| | | conn_factory = repoze.who.plugins.sql:make_psycopg_conn_factory |
| | | |
| | | [general] |
| | | request_classifier = repoze.who.classifiers:default_request_classifier |
| | | challenge_decider = repoze.who.classifiers:default_challenge_decider |
| | | remote_user_key = REMOTE_USER |
| | | |
| | | [identifiers] |
| | | # plugin_name;classifier_name:.. or just plugin_name (good for any) |
| | | plugins = |
| | | form;browser |
| | | auth_tkt |
| | | basicauth |
| | | |
| | | [authenticators] |
| | | # plugin_name;classifier_name.. or just plugin_name (good for any) |
| | | plugins = |
| | | htpasswd |
| | | sqlusers |
| | | |
| | | [challengers] |
| | | # plugin_name;classifier_name:.. or just plugin_name (good for any) |
| | | plugins = |
| | | form;browser |
| | | basicauth |
| | | |
| | | [mdproviders] |
| | | plugins = |
| | | sqlproperties |
| | | |
| | | The basicauth section configures a plugin that does identification and |
| | | challenge for basic auth credentials. The form section configures a |
| | | plugin that does identification and challenge (its implementation |
| | | defers to the cookie plugin for identification "forget" and "remember" |
| | | duties, thus the "identifier_impl_name" key; this is looked up at |
| | | runtime). The auth_tkt section configures a plugin that does |
| | | identification for cookie auth credentials. The htpasswd plugin |
| | | obtains its user info from a file. The sqlusers plugin obtains its |
| | | user info from a Postgres database. |
| | | |
| | | The identifiers section provides an ordered list of plugins that are |
| | | willing to provide identification capability. These will be consulted |
| | | in the defined order. The tokens on each line of the ``plugins=`` key |
| | | are in the form "plugin_name:requestclassifier_name:..." (or just |
| | | "plugin_name" if the plugin can be consulted regardless of the |
| | | classification of the request). The configuration above indicates |
| | | that the system will look for credentials using the form plugin (if |
| | | the request is classified as a browser request), then the cookie |
| | | identifier (unconditionally), then the basic auth plugin |
| | | (unconditionally). |
| | | |
| | | The authenticators section provides an ordered list of plugins that |
| | | provide authenticator capability. These will be consulted in the |
| | | defined order, so the system will look for users in the file, then in |
| | | the sql database when attempting to validate credentials. No |
| | | classification prefixes are given to restrict which of the two plugins |
| | | are used, so both plugins are consulted regardless of the |
| | | classification of the request. Each authenticator is called with each |
| | | set of identities found by the identifier plugins. The first identity |
| | | that can be authenticated is used to set ``REMOTE_USER``. |
| | | |
| | | The mdproviders section provides an ordered list of plugins that |
| | | provide metadata provider capability. These will be consulted in the |
| | | defined order. Each will have a chance (on ingress) to provide add |
| | | metadata to the authenticated identity. Our example mdproviders |
| | | section shows one plugin configured: "sqlproperties". The |
| | | sqlproperties plugin will add information related to user properties |
| | | (e.g. first name and last name) to the identity dictionary. |
| | | |
| | | The challengers section provides an ordered list of plugins that |
| | | provide challenger capability. These will be consulted in the defined |
| | | order, so the system will consult the cookie auth plugin first, then |
| | | the basic auth plugin. Each will have a chance to initiate a |
| | | challenge. The above configuration indicates that the form challenger |
| | | will fire if it's a browser request, and the basic auth challenger |
| | | will fire if it's not (fallback). |
| | | |
| | | Writing :mod:`repoze.who` Plugins |
| | | ================================= |
| | | |
| | | :mod:`repoze.who` can be extended arbitrarily through the creation of |
| | | plugins. Plugins are of one of four types: identifier plugins, |
| | | authenticator plugins, metadata provider plugins, and challenge |
| | | plugins. |
| | | |
| | | Writing An Identifier Plugin |
| | | ---------------------------- |
| | | |
| | | An identifier plugin (aka an ``IIdentifier`` plugin) must do three |
| | | things: extract credentials from the request and turn them into an |
| | | "identity", "remember" credentials, and "forget" credentials. |
| | | |
| | | Here's a simple cookie identification plugin that does these three |
| | | things :: |
| | | |
| | | class InsecureCookiePlugin(object): |
| | | |
| | | def __init__(self, cookie_name): |
| | | self.cookie_name = cookie_name |
| | | |
| | | def identify(self, environ): |
| | | cookies = get_cookies(environ) |
| | | cookie = cookies.get(self.cookie_name) |
| | | |
| | | if cookie is None: |
| | | return None |
| | | |
| | | import binascii |
| | | try: |
| | | auth = cookie.value.decode('base64') |
| | | except binascii.Error: # can't decode |
| | | return None |
| | | |
| | | try: |
| | | login, password = auth.split(':', 1) |
| | | return {'login':login, 'password':password} |
| | | except ValueError: # not enough values to unpack |
| | | return None |
| | | |
| | | def remember(self, environ, identity): |
| | | cookie_value = '%(login)s:%(password)s' % identity |
| | | cookie_value = cookie_value.encode('base64').rstrip() |
| | | from paste.request import get_cookies |
| | | cookies = get_cookies(environ) |
| | | existing = cookies.get(self.cookie_name) |
| | | value = getattr(existing, 'value', None) |
| | | if value != cookie_value: |
| | | # return a Set-Cookie header |
| | | set_cookie = '%s=%s; Path=/;' % (self.cookie_name, cookie_value) |
| | | return [('Set-Cookie', set_cookie)] |
| | | |
| | | def forget(self, environ, identity): |
| | | # return a expires Set-Cookie header |
| | | expired = ('%s=""; Path=/; Expires=Sun, 10-May-1971 11:59:00 GMT' % |
| | | self.cookie_name) |
| | | return [('Set-Cookie', expired)] |
| | | |
| | | def __repr__(self): |
| | | return '<%s %s>' % (self.__class__.__name__, id(self)) |
| | | |
| | | .identify |
| | | ~~~~~~~~~ |
| | | |
| | | The ``identify`` method of our InsecureCookiePlugin accepts a single |
| | | argument "environ". This will be the WSGI environment dictionary. |
| | | Our plugin attempts to grub through the cookies sent by the client, |
| | | trying to find one that matches our cookie name. If it finds one that |
| | | matches, it attempts to decode it and turn it into a login and a |
| | | password, which it returns as values in a dictionary. This dictionary |
| | | is thereafter known as an "identity". If it finds no credentials in |
| | | cookies, it returns None (which is not considered an identity). |
| | | |
| | | More generally, the ``identify`` method of an ``IIdentifier`` plugin |
| | | is called once on WSGI request "ingress", and it is expected to grub |
| | | arbitrarily through the WSGI environment looking for credential |
| | | information. In our above plugin, the credential information is |
| | | expected to be in a cookie but credential information could be in a |
| | | cookie, a form field, basic/digest auth information, a header, a WSGI |
| | | environment variable set by some upstream middleware or whatever else |
| | | someone might use to stash authentication information. If the plugin |
| | | finds credentials in the request, it's expected to return an |
| | | "identity": this must be a dictionary. The dictionary is not required |
| | | to have any particular keys or value composition, although it's wise |
| | | if the identification plugin looks for both a login name and a |
| | | password information to return at least {'login':login_name, |
| | | 'password':password}, as some authenticator plugins may depend on |
| | | presence of the names "login" and "password" (e.g. the htpasswd and |
| | | sql ``IAuthenticator`` plugins). If an ``IIdentifier`` plugin finds |
| | | no credentials, it is expected to return None. |
| | | |
| | | An ``IIdentifier`` plugin is also permitted to "preauthenticate" an |
| | | identity. If the identifier plugin knows that the identity is "good" |
| | | (e.g. in the case of ticket-based authentication where the userid is |
| | | embedded into the ticket), it can insert a special key into the |
| | | identity dictionary: ``repoze.who.userid``. If this key is present in |
| | | the identity dictionary, no authenticators will be asked to |
| | | authenticate the identity. This effectively allows an ``IIdentifier`` |
| | | plugin to become an ``IAuthenticator`` plugin when breaking apart the |
| | | responsibility into two separate plugins is "make-work". |
| | | Preauthenticated identities will be selected first when deciding which |
| | | identity to use for any given request. Our cookie plugin doesn't use |
| | | this feature. |
| | | |
| | | .remember |
| | | ~~~~~~~~~ |
| | | |
| | | If we've passed a REMOTE_USER to the WSGI application during ingress |
| | | (as a result of providing an identity that could be authenticated), |
| | | and the downstream application doesn't kick back with an unauthorized |
| | | response, on egress we want the requesting client to "remember" the |
| | | identity we provided if there's some way to do that and if he hasn't |
| | | already, in order to ensure he will pass it back to us on subsequent |
| | | requests without requiring another login. The remember method of an |
| | | ``IIdentifier`` plugin is called for each non-unauthenticated |
| | | response. It is the responsibility of the ``IIdentifier`` plugin to |
| | | conditionally return HTTP headers that will cause the client to |
| | | remember the credentials implied by "identity". |
| | | |
| | | Our InsecureCookiePlugin implements the "remember" method by returning |
| | | headers which set a cookie if and only if one is not already set with |
| | | the same name and value in the WSGI environment. These headers will |
| | | be tacked on to the response headers provided by the downstream |
| | | application during the response. |
| | | |
| | | When you write a remember method, most of the work involved is |
| | | determining *whether or not* you need to return headers. It's typical |
| | | to see remember methods that compute an "old state" and a "new state" |
| | | and compare the two against each other in order to determine if |
| | | headers need to be returned. In our example InsecureCookiePlugin, the |
| | | "old state" is ``cookie_value`` and the "new state" is ``value``. |
| | | |
| | | .forget |
| | | ~~~~~~~ |
| | | |
| | | Eventually the WSGI application we're serving will issue a "401 |
| | | Unauthorized" or another status signifying that the request could not |
| | | be authorized. :mod:`repoze.who` intercepts this status and calls |
| | | ``IIdentifier`` plugins asking them to "forget" the credentials |
| | | implied by the identity. It is the "forget" method's job at this |
| | | point to return HTTP headers that will effectively clear any |
| | | credentials on the requesting client implied by the "identity" |
| | | argument. |
| | | |
| | | Our InsecureCookiePlugin implements the "forget" method by returning |
| | | a header which resets the cookie that was set earlier by the remember |
| | | method to one that expires in the past (on my birthday, in fact). |
| | | This header will be tacked onto the response headers provided by the |
| | | downstream application. |
| | | |
| | | Writing an Authenticator Plugin |
| | | ------------------------------- |
| | | |
| | | An authenticator plugin (aka an ``IAuthenticator`` plugin) must do |
| | | only one thing (on "ingress"): accept an identity and check if the |
| | | identity is "good". If the identity is good, it should return a "user |
| | | id". This user id may or may not be the same as the "login" provided |
| | | by the user. An ``IAuthenticator`` plugin will be called for each |
| | | identity found during the identification phase (there may be multiple |
| | | identities for a single request, as there may be multiple |
| | | ``IIdentifier`` plugins active at any given time), so it may be called |
| | | multiple times in the same request. |
| | | |
| | | Here's a simple authenticator plugin that attempts to match an |
| | | identity against ones defined in an "htpasswd" file that does just |
| | | that:: |
| | | |
| | | class SimpleHTPasswdPlugin(object): |
| | | |
| | | def __init__(self, filename): |
| | | self.filename = filename |
| | | |
| | | # IAuthenticatorPlugin |
| | | def authenticate(self, environ, identity): |
| | | try: |
| | | login = identity['login'] |
| | | password = identity['password'] |
| | | except KeyError: |
| | | return None |
| | | |
| | | f = open(self.filename, 'r') |
| | | |
| | | for line in f: |
| | | try: |
| | | username, hashed = line.rstrip().split(':', 1) |
| | | except ValueError: |
| | | continue |
| | | if username == login: |
| | | if crypt_check(password, hashed): |
| | | return username |
| | | return None |
| | | |
| | | def crypt_check(password, hashed): |
| | | from crypt import crypt |
| | | salt = hashed[:2] |
| | | return hashed == crypt(password, salt) |
| | | |
| | | An ``IAuthenticator`` plugin implements one "interface" method: |
| | | "authentictate". The formal specification for the arguments and |
| | | return values expected from these methods are available in the |
| | | ``interfaces.py`` file in :mod:`repoze.who` as the ``IAuthenticator`` |
| | | interface, but let's examine this method here less formally. |
| | | |
| | | .authenticate |
| | | ~~~~~~~~~~~~~ |
| | | |
| | | The ``authenticate`` method accepts two arguments: the WSGI |
| | | environment and an identity. Our SimpleHTPasswdPlugin |
| | | ``authenticate`` implementation grabs the login and password out of |
| | | the identity and attempts to find the login in the htpasswd file. If |
| | | it finds it, it compares the crypted version of the password provided |
| | | by the user to the crypted version stored in the htpasswd file, and |
| | | finally, if they match, it returns the login. If they do not match, |
| | | it returns None. |
| | | |
| | | .. note:: |
| | | |
| | | Our plugin's ``authenticate`` method does not assume that the keys |
| | | ``login`` or ``password`` exist in the identity; although it |
| | | requires them to do "real work" it returns None if they are not |
| | | present instead of raising an exception. This is required by the |
| | | ``IAuthenticator`` interface specification. |
| | | |
| | | Writing a Challenger Plugin |
| | | --------------------------- |
| | | |
| | | A challenger plugin (aka an ``IChallenger`` plugin) must do only one |
| | | thing on "egress": return a WSGI application which performs a |
| | | "challenge". A WSGI application is a callable that accepts an |
| | | "environ" and a "start_response" as its parameters; see "PEP 333" for |
| | | further definition of what a WSGI application is. A challenge asks |
| | | the user for credentials. |
| | | |
| | | Here's an example of a simple challenger plugin:: |
| | | |
| | | from paste.httpheaders import WWW_AUTHENTICATE |
| | | from paste.httpexceptions import HTTPUnauthorized |
| | | |
| | | class BasicAuthChallengerPlugin(object): |
| | | |
| | | def __init__(self, realm): |
| | | self.realm = realm |
| | | |
| | | # IChallenger |
| | | def challenge(self, environ, status, app_headers, forget_headers): |
| | | head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm) |
| | | if head[0] not in forget_headers: |
| | | head = head + forget_headers |
| | | return HTTPUnauthorized(headers=head) |
| | | |
| | | Note that the plugin implements a single "interface" method: |
| | | "challenge". The formal specification for the arguments and return |
| | | values expected from this method is available in the "interfaces.py" |
| | | file in :mod:`repoze.who` as the ``IChallenger`` interface. This method |
| | | is called when :mod:`repoze.who` determines that the application has |
| | | returned an "unauthorized" response (e.g. a 401). Only one challenger |
| | | will be consulted during "egress" as necessary (the first one to |
| | | return a non-None response). |
| | | |
| | | .challenge |
| | | ~~~~~~~~~~ |
| | | |
| | | The challenge method takes environ (the WSGI environment), 'status' |
| | | (the status as set by the downstream application), the "app_headers" |
| | | (headers returned by the application), and the "forget_headers" |
| | | (headers returned by all participating ``IIdentifier`` plugins whom |
| | | were asked to "forget" this user). |
| | | |
| | | Our BasicAuthChallengerPlugin takes advantage of the fact that the |
| | | HTTPUnauthorized exception imported from paste.httpexceptions can be |
| | | used as a WSGI application. It first makes sure that we don't repeat |
| | | headers if an identification plugin has already set a |
| | | "WWW-Authenticate" header like ours, then it returns an instance of |
| | | HTTPUnauthorized, passing in merged headers. This will cause a basic |
| | | authentication dialog to be presented to the user. |
| | | |
| | | Writing a Metadata Provider Plugin |
| | | ---------------------------------- |
| | | |
| | | A metadata provider plugin (aka an ``IMetadataProvider`` plugin) must |
| | | do only one thing (on "ingress"): "scribble" on the identity |
| | | dictionary provided to it when it is called. An ``IMetadataProvider`` |
| | | plugin will be called with the final "best" identity found during the |
| | | authentication phase, or not at all if no "best" identity could be |
| | | authenticated. Thus, each ``IMetadataProvider`` plugin will be called |
| | | exactly zero or one times during a request. |
| | | |
| | | Here's a simple metadata provider plugin that provides "property" |
| | | information from a dictionary:: |
| | | |
| | | _DATA = { |
| | | 'chris': {'first_name':'Chris', 'last_name':'McDonough'} , |
| | | 'whit': {'first_name':'Whit', 'last_name':'Morriss'} |
| | | } |
| | | |
| | | class SimpleMetadataProvider(object): |
| | | |
| | | def add_metadata(self, environ, identity): |
| | | userid = identity.get('repoze.who.userid') |
| | | info = _DATA.get(userid) |
| | | if info is not None: |
| | | identity.update(info) |
| | | |
| | | .add_metadata |
| | | ~~~~~~~~~~~~~ |
| | | |
| | | Arbitrarily add information to the identity dict based in other data |
| | | in the environment or identity. Our plugin adds ``first_name`` and |
| | | ``last_name`` values to the identity if the userid matches ``chris`` |
| | | or ``whit``. |
| | | |
| | | Interfaces |
| | | ========== |
| | | |
| | | .. module:: repoze.who.interfaces |
| | | |
| | | .. code-block:: python |
| | | |
| | | class IPlugin(Interface): |
| | | pass |
| | | |
| | | class IRequestClassifier(IPlugin): |
| | | """ On ingress: classify a request. |
| | | """ |
| | | def __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. |
| | | """ |
| | | |
| | | class IChallengeDecider(IPlugin): |
| | | """ On egress: decide whether a challenge needs to be presented |
| | | to the user. |
| | | """ |
| | | def __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. |
| | | """ |
| | | |
| | | class IIdentifier(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). |
| | | """ |
| | | |
| | | def 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". |
| | | """ |
| | | |
| | | def 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. |
| | | """ |
| | | |
| | | def 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. |
| | | """ |
| | | |
| | | class IAuthenticator(IPlugin): |
| | | |
| | | """ On ingress: validate the identity and return a user id or None. |
| | | """ |
| | | |
| | | def 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). |
| | | """ |
| | | |
| | | class IChallenger(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. |
| | | """ |
| | | |
| | | def 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. |
| | | """ |
| | | |
| | | |
| | | class IMetadataProvider(IPlugin): |
| | | """On ingress: When an identity is authenticated, metadata |
| | | providers may scribble on the identity dictionary arbitrarily. |
| | | Return values from metadata providers are ignored. |
| | | """ |
| | | |
| | | def 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. |
| | | """ |
| | | |
| | | Developers and integrators can configure :mod:`repoze.who` using either |
| | | imperative Python code (see :ref:`imperative_configuration`) or using an |
| | | INI-style declarative configuration file (see :ref:`declarative_configuration`). |
| | | In either case, the result of the configuration will be a |
| | | :class:`repoze.who.api:APIFactory` instance, complete with a request |
| | | classifier, a challenge decider, and a set of plugins for each plugin |
| | | interface. |