Configuring repoze.who
¶
Configuration Points¶
Classifiers¶
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.
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. repoze.who
provides a
default classifier that you may use.
You may extend the classification system by making repoze.who
aware
of a different request classifier implementation.
Challenge Deciders¶
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).
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¶
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.
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 repoze.who
does for the current request.
Note
As of 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
.
Configuring repoze.who
via Python Code¶
- class repoze.who.middleware.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
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 ofIChallenger
plugins, mdproviders is a sequence ofIMetadataProvider
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 awrite
method) or alogging.Logger
object, log_level is a numeric value that maps to thelogging
module’s notion of log levels, remote_user_key is the key in which theREMOTE_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 bothIIdentifier
andIAuthenticator
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 abrowser
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.
Configuring repoze.who
via Config File¶
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 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')
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
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).