David Tulloh
2016-04-20 d7df42ae13a2a9bfb73a76ed96997dad88a794a9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
.. _configuration_points:
 
Configuring :mod:`repoze.who`
=============================
 
Configuration Points
--------------------
 
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.
 
You may extend the classification system by making :mod:`repoze.who` aware
of a different request classifier implementation.
 
Challenge Deciders
++++++++++++++++++
 
: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.
 
You may supply a different challenge decider as necessary.
 
Plugins
+++++++
 
: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.
 
.. 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``.
 
 
.. _imperative_configuration:
 
Configuring :mod:`repoze.who` via Python Code
---------------------------------------------
 
.. module:: repoze.who.middleware
 
.. class:: PluggableAuthenticationMiddleware(app, identifiers, challengers, authenticators, 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.redirector import RedirectorPlugin
    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', digest_algo="sha512")
    redirector = RedirectorPlugin('/login.html')
    redirector.classifications = {IChallenger:['browser'],} # only for browser
    identifiers = [('auth_tkt', auth_tkt),
                   ('basicauth', basicauth)]
    authenticators = [('auth_tkt', auth_tkt),
                      ('htpasswd', htpasswd)]
    challengers = [('redirector', redirector),
                   ('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:
 
- Two ``IIdentifier`` plugins (auth_tkt cookie, and a
  basic auth plugin).  In this setup, when "identification" needs to
  be performed, the auth_tkt plugin will be checked first, then
  the basic auth plugin.  The application is responsible for handling
  login via a form:  this view would use the API (via :method:`remember`)
  to generate apprpriate response headers.
 
- Two ``IAuthenticator`` plugins: the auth_tkt plugin and an htpasswd plugin.
  The auth_tkt plugin performs both ``IIdentifier`` and ``IAuthenticator``
  functions.  The 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 redirector plugin, then the basic auth
  plugin.  The redirector 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.
 
 
.. _declarative_configuration:
 
Configuring :mod:`repoze.who` 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, the global configuration dictionary, and the path to the
config file. The global configuration dictionary is a dictonary passed 
by PasteDeploy. The only key 'make_middleware_with_config' needs is 
'here' pointing to the config file directory. For debugging people
might find it useful to enable logging by adding the log_file argument,
e.g. log_file="repoze_who.log" ::
 
    from repoze.who.config import make_middleware_with_config
    global_conf = {"here": "."}  # if this is not defined elsewhere
    who = make_middleware_with_config(app, global_conf, '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:redirector]
    # identificaion and challenge
    use = repoze.who.plugins.redirector:make_plugin
    login_url = /login.html
 
    [plugin:auth_tkt]
    # identification and authentication
    use = repoze.who.plugins.auth_tkt:make_plugin
    secret = s33kr1t
    cookie_name = oatmeal
    secure = False
    include_ip = False
    digest_algo = sha512
 
    [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
    # Note the double %%:  we have to escape it from the config parser in
    # order to preserve it as a template for the psycopg2, whose 'paramstyle'
    # is 'pyformat'.
    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
    # Note the double %%:  we have to escape it from the config parser in
    # order to preserve it as a template for the psycopg2, whose 'paramstyle'
    # is 'pyformat'.
    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 =
          auth_tkt
          basicauth
 
    [authenticators]
    # plugin_name;classifier_name.. or just plugin_name (good for any)
    plugins =
          auth_tkt
          htpasswd
          sqlusers
 
    [challengers]
    # plugin_name;classifier_name:.. or just plugin_name (good for any)
    plugins =
          redirector;browser
          basicauth
 
    [mdproviders]
    plugins =
          sqlproperties
 
The basicauth section configures a plugin that does identification and
challenge for basic auth credentials.  The redirector section configures a
plugin that does challenges.  The auth_tkt section configures a plugin that
does identification for cookie auth credentials, as well as authenticating
them.  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 auth_tkt 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 redirector challenger
will fire if it's a browser request, and the basic auth challenger
will fire if it's not (fallback).