1.. _configuration_points:
2
3Configuring :mod:`repoze.who`
4=============================
5
6Configuration Points
7--------------------
8
9Classifiers
10+++++++++++
11
12:mod:`repoze.who` "classifies" the request on middleware ingress.
13Request classification happens before identification and
14authentication.  A request from a browser might be classified a
15different way than a request from an XML-RPC client.
16:mod:`repoze.who` uses request classifiers to decide which other
17components to consult during subsequent identification,
18authentication, and challenge steps.  Plugins are free to advertise
19themselves as willing to participate in identification and
20authorization for a request based on this classification.  The request
21classification system is pluggable.  :mod:`repoze.who` provides a
22default classifier that you may use.
23
24You may extend the classification system by making :mod:`repoze.who` aware
25of a different request classifier implementation.
26
27Challenge Deciders
28++++++++++++++++++
29
30:mod:`repoze.who` uses a "challenge decider" to decide whether the
31response returned from a downstream application requires a challenge
32plugin to fire.  When using the default challenge decider, only the
33status is used (if it starts with ``401``, a challenge is required).
34
35:mod:`repoze.who` also provides an alternate challenge decider,
36``repoze.who.classifiers.passthrough_challenge_decider``, which avoids
37challenging ``401`` responses which have been "pre-challenged" by the
38application.
39
40You may supply a different challenge decider as necessary.
41
42Plugins
43+++++++
44
45:mod:`repoze.who` has core functionality designed around the concept
46of plugins.  Plugins are instances that are willing to perform one or
47more identification- and/or authentication-related duties.  Each
48plugin can be configured arbitrarily.
49
50:mod:`repoze.who` consults the set of configured plugins when it
51intercepts a WSGI request, and gives some subset of them a chance to
52influence what :mod:`repoze.who` does for the current request.
53
54.. note:: As of :mod:`repoze.who` 1.0.7, the ``repoze.who.plugins``
55   package is a namespace package, intended to make it possible for
56   people to ship eggs which are who plugins as,
57   e.g. ``repoze.who.plugins.mycoolplugin``.
58
59
60.. _imperative_configuration:
61
62Configuring :mod:`repoze.who` via Python Code
63---------------------------------------------
64
65.. module:: repoze.who.middleware
66
67.. class:: PluggableAuthenticationMiddleware(app, identifiers, challengers, authenticators, mdproviders, classifier, challenge_decider [, log_stream=None [, log_level=logging.INFO[, remote_user_key='REMOTE_USER']]])
68
69  The primary method of configuring the :mod:`repoze.who` middleware is
70  to use straight Python code, meant to be consumed by frameworks
71  which construct and compose middleware pipelines without using a
72  configuration file.
73
74  In the middleware constructor: *app* is the "next" application in
75  the WSGI pipeline. *identifiers* is a sequence of ``IIdentifier``
76  plugins, *challengers* is a sequence of ``IChallenger`` plugins,
77  *mdproviders* is a sequence of ``IMetadataProvider`` plugins.  Any
78  of these can be specified as the empty sequence.  *classifier* is a
79  request classifier callable, *challenge_decider* is a challenge
80  decision callable.  *log_stream* is a stream object (an object with
81  a ``write`` method) *or* a ``logging.Logger`` object, *log_level* is
82  a numeric value that maps to the ``logging`` module's notion of log
83  levels, *remote_user_key* is the key in which the ``REMOTE_USER``
84  (userid) value should be placed in the WSGI environment for
85  consumption by downstream applications.
86
87An example configuration which uses the default plugins follows::
88
89    from repoze.who.middleware import PluggableAuthenticationMiddleware
90    from repoze.who.interfaces import IIdentifier
91    from repoze.who.interfaces import IChallenger
92    from repoze.who.plugins.basicauth import BasicAuthPlugin
93    from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
94    from repoze.who.plugins.redirector import RedirectorPlugin
95    from repoze.who.plugins.htpasswd import HTPasswdPlugin
96
97    io = StringIO()
98    salt = 'aa'
99    for name, password in [ ('admin', 'admin'), ('chris', 'chris') ]:
100        io.write('%s:%s\n' % (name, password))
101    io.seek(0)
102    def cleartext_check(password, hashed):
103        return password == hashed
104    htpasswd = HTPasswdPlugin(io, cleartext_check)
105    basicauth = BasicAuthPlugin('repoze.who')
106    auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt', digest_algo="sha512")
107    redirector = RedirectorPlugin('/login.html')
108    redirector.classifications = {IChallenger:['browser'],} # only for browser
109    identifiers = [('auth_tkt', auth_tkt),
110                   ('basicauth', basicauth)]
111    authenticators = [('auth_tkt', auth_tkt),
112                      ('htpasswd', htpasswd)]
113    challengers = [('redirector', redirector),
114                   ('basicauth', basicauth)]
115    mdproviders = []
116
117    from repoze.who.classifiers import default_request_classifier
118    from repoze.who.classifiers import default_challenge_decider
119    log_stream = None
120    import os
121    if os.environ.get('WHO_LOG'):
122        log_stream = sys.stdout
123
124    middleware = PluggableAuthenticationMiddleware(
125        app,
126        identifiers,
127        authenticators,
128        challengers,
129        mdproviders,
130        default_request_classifier,
131        default_challenge_decider,
132        log_stream = log_stream,
133        log_level = logging.DEBUG
134        )
135
136The above example configures the repoze.who middleware with:
137
138- Two ``IIdentifier`` plugins (auth_tkt cookie, and a
139  basic auth plugin).  In this setup, when "identification" needs to
140  be performed, the auth_tkt plugin will be checked first, then
141  the basic auth plugin.  The application is responsible for handling
142  login via a form:  this view would use the API (via :method:`remember`)
143  to generate apprpriate response headers.
144
145- Two ``IAuthenticator`` plugins: the auth_tkt plugin and an htpasswd plugin.
146  The auth_tkt plugin performs both ``IIdentifier`` and ``IAuthenticator``
147  functions.  The htpasswd plugin is configured with two valid username /
148  password combinations: chris/chris, and admin/admin.  When an username
149  and password is found via any identifier, it will be checked against this
150  authenticator.
151
152- Two ``IChallenger`` plugins: the redirector plugin, then the basic auth
153  plugin.  The redirector auth will fire if the request is a ``browser``
154  request, otherwise the basic auth plugin will fire.
155
156The rest of the middleware configuration is for values like logging
157and the classifier and decider implementations.  These use the "stock"
158implementations.
159
160.. note:: The ``app`` referred to in the example is the "downstream"
161   WSGI application that who is wrapping.
162
163
164.. _declarative_configuration:
165
166Configuring :mod:`repoze.who` via Config File
167---------------------------------------------
168
169:mod:`repoze.who` may be configured using a ConfigParser-style .INI
170file.  The configuration file has five main types of sections: plugin
171sections, a general section, an identifiers section, an authenticators
172section, and a challengers section.  Each "plugin" section defines a
173configuration for a particular plugin.  The identifiers,
174authenticators, and challengers sections refer to these plugins to
175form a site configuration.  The general section is general middleware
176configuration.
177
178To configure :mod:`repoze.who` in Python, using an .INI file, call
179the `make_middleware_with_config` entry point, passing the right-hand
180application, the global configuration dictionary, and the path to the
181config file. The global configuration dictionary is a dictonary passed
182by PasteDeploy. The only key 'make_middleware_with_config' needs is
183'here' pointing to the config file directory. For debugging people
184might find it useful to enable logging by adding the log_file argument,
185e.g. log_file="repoze_who.log" ::
186
187    from repoze.who.config import make_middleware_with_config
188    global_conf = {"here": "."}  # if this is not defined elsewhere
189    who = make_middleware_with_config(app, global_conf, 'who.ini')
190
191:mod:`repoze.who`'s configuration file can be pointed to within a PasteDeploy
192configuration file ::
193
194    [filter:who]
195    use = egg:repoze.who#config
196    config_file = %(here)s/who.ini
197    log_file = stdout
198    log_level = debug
199
200Below is an example of a configuration file (what ``config_file``
201might point at above ) that might be used to configure the
202:mod:`repoze.who` middleware.  A set of plugins are defined, and they
203are referred to by following non-plugin sections.
204
205In the below configuration, five plugins are defined.  The form, and
206basicauth plugins are nominated to act as challenger plugins.  The
207form, cookie, and basicauth plugins are nominated to act as
208identification plugins.  The htpasswd and sqlusers plugins are
209nominated to act as authenticator plugins. ::
210
211    [plugin:redirector]
212    # identificaion and challenge
213    use = repoze.who.plugins.redirector:make_plugin
214    login_url = /login.html
215
216    [plugin:auth_tkt]
217    # identification and authentication
218    use = repoze.who.plugins.auth_tkt:make_plugin
219    secret = s33kr1t
220    cookie_name = oatmeal
221    secure = False
222    include_ip = False
223    digest_algo = sha512
224
225    [plugin:basicauth]
226    # identification and challenge
227    use = repoze.who.plugins.basicauth:make_plugin
228    realm = 'sample'
229
230    [plugin:htpasswd]
231    # authentication
232    use = repoze.who.plugins.htpasswd:make_plugin
233    filename = %(here)s/passwd
234    check_fn = repoze.who.plugins.htpasswd:crypt_check
235
236    [plugin:sqlusers]
237    # authentication
238    use = repoze.who.plugins.sql:make_authenticator_plugin
239    # Note the double %%:  we have to escape it from the config parser in
240    # order to preserve it as a template for the psycopg2, whose 'paramstyle'
241    # is 'pyformat'.
242    query = SELECT userid, password FROM users where login = %%(login)s
243    conn_factory = repoze.who.plugins.sql:make_psycopg_conn_factory
244    compare_fn = repoze.who.plugins.sql:default_password_compare
245
246    [plugin:sqlproperties]
247    name = properties
248    use = repoze.who.plugins.sql:make_metadata_plugin
249    # Note the double %%:  we have to escape it from the config parser in
250    # order to preserve it as a template for the psycopg2, whose 'paramstyle'
251    # is 'pyformat'.
252    query = SELECT firstname, lastname FROM users where userid = %%(__userid)s
253    filter = my.package:filter_propmd
254    conn_factory = repoze.who.plugins.sql:make_psycopg_conn_factory
255
256    [general]
257    request_classifier = repoze.who.classifiers:default_request_classifier
258    challenge_decider = repoze.who.classifiers:default_challenge_decider
259    remote_user_key = REMOTE_USER
260
261    [identifiers]
262    # plugin_name;classifier_name:.. or just plugin_name (good for any)
263    plugins =
264          auth_tkt
265          basicauth
266
267    [authenticators]
268    # plugin_name;classifier_name.. or just plugin_name (good for any)
269    plugins =
270          auth_tkt
271          htpasswd
272          sqlusers
273
274    [challengers]
275    # plugin_name;classifier_name:.. or just plugin_name (good for any)
276    plugins =
277          redirector;browser
278          basicauth
279
280    [mdproviders]
281    plugins =
282          sqlproperties
283
284The basicauth section configures a plugin that does identification and
285challenge for basic auth credentials.  The redirector section configures a
286plugin that does challenges.  The auth_tkt section configures a plugin that
287does identification for cookie auth credentials, as well as authenticating
288them.  The htpasswd plugin obtains its user info from a file.  The sqlusers
289plugin obtains its user info from a Postgres database.
290
291The identifiers section provides an ordered list of plugins that are
292willing to provide identification capability.  These will be consulted
293in the defined order.  The tokens on each line of the ``plugins=`` key
294are in the form "plugin_name;requestclassifier_name:..."  (or just
295"plugin_name" if the plugin can be consulted regardless of the
296classification of the request).  The configuration above indicates
297that the system will look for credentials using the auth_tkt cookie
298identifier (unconditionally), then the basic auth plugin
299(unconditionally).
300
301The authenticators section provides an ordered list of plugins that
302provide authenticator capability.  These will be consulted in the
303defined order, so the system will look for users in the file, then in
304the sql database when attempting to validate credentials.  No
305classification prefixes are given to restrict which of the two plugins
306are used, so both plugins are consulted regardless of the
307classification of the request.  Each authenticator is called with each
308set of identities found by the identifier plugins.  The first identity
309that can be authenticated is used to set ``REMOTE_USER``.
310
311The mdproviders section provides an ordered list of plugins that
312provide metadata provider capability.  These will be consulted in the
313defined order.  Each will have a chance (on ingress) to provide add
314metadata to the authenticated identity.  Our example mdproviders
315section shows one plugin configured: "sqlproperties".  The
316sqlproperties plugin will add information related to user properties
317(e.g. first name and last name) to the identity dictionary.
318
319The challengers section provides an ordered list of plugins that
320provide challenger capability.  These will be consulted in the defined
321order, so the system will consult the cookie auth plugin first, then
322the basic auth plugin.  Each will have a chance to initiate a
323challenge.  The above configuration indicates that the redirector challenger
324will fire if it's a browser request, and the basic auth challenger
325will fire if it's not (fallback).
326