
The dimensions of repoze.whoThe repoze.who package available from http://static.repoze.org/whodocs/ provides a flexible interface which allows WSGI applications to mix and match different implementations for different aspects of web user authentication.
Configuring who using WHIFF resources
Configuring a WHIFF application group for basic authentication using an htpasswd file
The htpasswd file
Deploying an application with authentication resources
Restricting page access using the protect middleware
Accessing the authentication protected file...
Configuring a WHIFF application group for form based authentication
Saving some typing in the include directives
Determining whether the user is logged in
The login form
The failure page
Allowing only logged in users to see a page
Allowing only certain users to see a page
Restricting access to directory contents
Deploying a directory with authentication under a web server
Summary
The WHIFF standard middleware package
middleware/repoze includes
support interfaces that make the repoze.who
functionality easily configurable for WHIFF applications.
The repoze.who modules must be installed
separately from the whiff package
before the interfaces will work.
who.
Or if you are brave and have easy_install
simply type
$ easy_install repoze.who
This tutorial walks through a couple example configurations
using repoze.who with WHIFF.
The source files for the installations described here are available in the
WHIFF distribution under INSTALL/test.
repoze.who package provides a relatively sophisticated
object oriented interface where different dimensions of user authentication
are handled by different component types:
AuthTktCookiePlugin identifier illustrated here examines a "cookie"
automatically sent by browsers without direct user interaction.
HTPasswdPlugin compares user-id's and passwords against entries in a simple text file.
;c).
default_request_classifier
and I can't think of any reason to ever do otherwise; but if you can think of a reason
the WHIFF interface supports using some other request classifier.
For a WHIFF application to use who there must be a sensible mechanism for configuring
these components within a WHIFF application.
who assume the different components are deployed
as WHIFF resources in a root application, usually using standard resource paths.
For example to locate the list of authentication Identifiers the middlewares normally
ask for the resource located at the resource path ["who.identifiers"].
To prepare an application deployment to use who a root application application must deploy
the resources such as ["who.identifiers"] by registering a resource, something like this
identifiers = [...]
application = resolver.moduleRootApplication(...)
application.registerStaticResource(prefix="who.identifiers",
resourceValue=identifiers)
who resources configured, applications contained in the module root application
may use the WHIFF interfaces for who to authenticate users and make use of the
authentication information. For example the following configuration template fragment authenticates
the user and prints out the authenticated user name
{{include "whiff_middleware/repoze/protect"}}
{{using allowUsers}}true{{/using}}
{{using failureRedirect}}failure{{/using}}
{{using app}}
<h1>Greetings {{include "whiff_middleware/repoze/uid"/}}</h1>
...
{{/using}}
{{/include}}
"whiff_middleware/repoze/protect" and
"whiff_middleware/repoze/uid" use the deployed who
resources to perform the authentication and store the authentication information.
The protect middleware in this case only allows logged in users to
see the Greeting.... The uid middleware extracts the authenticated
user-id from the who data structures in the WSGI environment.
imports to save some typing
so you don't have to type whiff_middleware/repoze/... over and over and over.
The following sections describe the complete process for installing authentication in an application and using it to restrict access to web pages or web page trees.
INSTALL/test/whobasic/ to authenticate
users using basic HTTP authentication, where the user name and password information
are stored in a simple htpasswd text file.
The whobasic module should not be aware of how users are authenticated
so it can be used under different authentication schemes. For this reason the authentication
information is deployed separately from the whobasic module itself.
The deployment configuration for whobasic is defined in the configuration
script INSTALL/test/basicWho.py. Below are the imports used by the basicWho
script.
import os
import sys
import logging
from StringIO import StringIO
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.htpasswd import HTPasswdPlugin
# basicWho.py continued below...
who deployment we first need to specify which
components we want to use as identifiers, authenticators, and so forth.
The following code lines construct the who components
we will deploy.
# basicWho.py continued...
# find the user/password file in the same directory at ./htpasswd
htfilename = os.path.split(os.path.abspath(__file__))[0] + "/htpasswd"
htfile = file(htfilename)
# the passwords are in clear text
def cleartext_check(password, hashed):
return password == hashed
# use the htpasswd file to find user names and passwords
htpasswd = HTPasswdPlugin(htfile, cleartext_check)
# allow HTTP basic authentication.
basicauth = BasicAuthPlugin('repoze.who')
# the repoze.who identifiers
identifiers = [('basicauth',basicauth)]
# the repoze.who authenticators
authenticators = [('htpasswd', htpasswd)]
# the repoze.who challengers
challengers = [('basicauth',basicauth)]
# no metadata providers, please.
mdproviders = []
# use default classifiers and deciders
from repoze.who.classifiers import default_request_classifier
from repoze.who.classifiers import default_challenge_decider
log_stream = None
#... basicWho.py continued below
HTPasswdPlugin
instance constructed above reads a file named htpasswd listing the user-ids and passwords
for users in clear text. Here is the content of that file:
admin:admin
chris:chris
carla:carla
sam:sam
repoze.who package and available plug-ins provide other ways
to specify user names and passwords, including plug-ins that allow ids and passwords
to be found in an SQL database.
basicWho script uses the who components constructed above
to create a root application that deploys the components in the
whoTestRootApplication defined below:
# basicWho.py continued...
def whoTestRootApplication(path="/"):
from whiff import resolver
from whiff.middleware import displayTraceback
import whobasic
# create the root application
testapp = resolver.moduleRootApplication(path, whobasic,
exception_middleware=displayTraceback.__middleware__,
on_not_found=None, # show traceback (could comment)
)
# set up all the repoze.who resources at standard
# resource prefixes.
# these MUST be defined for the plugin to work:
testapp.registerStaticResource(prefix="who.identifiers",
resourceValue=identifiers)
testapp.registerStaticResource(prefix="who.authenticators",
resourceValue=authenticators)
testapp.registerStaticResource(prefix="who.challengers",
resourceValue=challengers)
# these are optional:
testapp.registerStaticResource(prefix="who.mdproviders",
resourceValue=mdproviders)
testapp.registerStaticResource(prefix="who.classifier",
resourceValue=default_request_classifier)
testapp.registerStaticResource(prefix="who.challenge_decider",
resourceValue=default_challenge_decider)
testapp.registerStaticResource(prefix="who.log_stream",
resourceValue=log_stream)
testapp.registerStaticResource(prefix="who.log_level",
resourceValue=logging.DEBUG)
return testapp
# basicWho.py continued below...
whoTestRootApplication function constructs a
moduleRootApplication using the whobasic
module and then attaches resources for who based authentication
at standard locations using the registerStaticResource
method.
To allow the configured root application to run as a stand alone server in test mode we add the following script interpretation.
# basicWho.py continued...
if __name__=="__main__":
# script interpretation: launch the app in a server at port 8888
import wsgiref.simple_server
testapp = whoTestRootApplication()
print "serving whotest at 8888"
srv = wsgiref.simple_server.make_server('localhost', 8888, testapp)
srv.serve_forever()
whobasic is not aware
of how authentication is implemented -- instead it assumes that some
higher configuration like basicWho.py has set things up
properly so that authentication is available.
The python package initialization file whobasic/__init__.py
identifies the package as a __wsgi_directory__ and publishes
templates and files with recognized extensions, as usual.
from whiff import resolver
__wsgi_directory__ = True
resolver.publish_templates(__path__[0], globals(), mime_extensions=True,
directory_middleware=True)
whobasic may now make
use of the authentication mechanism configured by basicWho.py.
For example the configuration template whobasic/ChrisssPage.whiff
specifies that the user must be authenticated as either "chris" or "admin"
before the page content can sent to the user
{{env whiff.content_type: "text/html"/}}
{{include "whiff_middleware/repoze/protect"}}
{{using allowUsers}} ["admin", "chris"] {{/using}}
{{using app}}
<h1>Greetings 'admin' or 'chris'</h1>
Did you know that if chickens didn't lay eggs we'd
have to color something else at Easter?
That's why chickens lay eggs.
<p>
<a href="index">return to index.</a>
{{/using}}
{{/include}}
"whiff_middleware/repoze/protect"
middleware is configured to allow only the users ["admin", "chris"]
to see the app content <h1>Greetings ... -- all others
will be asked to log in or will be refused.
basicWho.py which launches a test server.
INSTALL/test/ $ python basicWho.py
serving whotest at 8888
...
http://localhost:8888 we see
the standard WHIFF directory listing, which is not protected by authentication.
who component
RedirectingFormPlugin addresses these issues.
RedirectingFormPlugin implementation is sophisticated and
not very easy to understand. I hope the following discussion will get you started
with a working configuration which you can modify to your own purposes, even
if some of the mechanisms shown are not easily understood.
The following configuration module INSTALL/test/runWho.py is similar to the
basicWho.py configuration script shown above, except that it
configures the WHIFF directory root whotest and it deploys different
who components.
The first part of the INSTALL/test/runWho.py file defines the who
components to use for authentication, introducing a cookie based AuthTktCookiePlugin
and a web form based RedirectingFormPlugin.
import os
import sys
import logging
from StringIO import StringIO
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.form import RedirectingFormPlugin
from repoze.who.plugins.htpasswd import HTPasswdPlugin
# find the user/password file in the same directory at ./htpasswd
htfilename = os.path.split(os.path.abspath(__file__))[0] + "/htpasswd"
htfile = file(htfilename)
# the passwords are in clear text
def cleartext_check(password, hashed):
return password == hashed
# use the htpasswd file to find user names and passwords
htpasswd = HTPasswdPlugin(htfile, cleartext_check)
# allow HTTP basic authentication.
basicauth = BasicAuthPlugin('repoze.who')
# also allow auth_tkt based authentication
auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt')
# use the repoze.who redirecting form plugin for challenges and identification
form = RedirectingFormPlugin('login_url', '/login_handler_path',
'/logout_handler_path', rememberer_name='auth_tkt')
# set up the form classifications
form.classifications = { IIdentifier:['browser'],
IChallenger:['browser'] }
# the repoze.who identifiers
identifiers = [('form', form),('auth_tkt',auth_tkt),('basicauth',basicauth)]
# the repoze.who authenticators
authenticators = [('htpasswd', htpasswd)]
# the repoze.who challengers
challengers = [('form',form), ('basicauth',basicauth)]
# no metadata providers, please.
mdproviders = []
# use default classifiers and deciders
from repoze.who.classifiers import default_request_classifier
from repoze.who.classifiers import default_challenge_decider
# log to standard output if WHO_LOG is set in the os.environ
log_stream = None
# use the repoze.who redirecting form plugin for challenges and identification
form = RedirectingFormPlugin('/login_url', '/login_handler_path',
'/logout_handler_path', rememberer_name='auth_tkt')
form handler which will use the web form at URL /login_url to
ask the user for the user name and password. The form handler will also look for
requests with
url paths that end with /logout_handler_path and "log out" any authenticated user
using the response to the request. The form handler will also
look for requests for URLs that end in /login_handler_path and attempt to identify
submitted user information in the request.
The remainder of runWho.py is nearly identical to basicWho.py except that it configures
the whotest root directory in place of the whobasic directory.
def whoTestRootApplication(path="/"):
from whiff import resolver
from whiff.middleware import displayTraceback
import whotest
# create the root application
testapp = resolver.moduleRootApplication(path, whotest,
exception_middleware=displayTraceback.__middleware__,
on_not_found=None, # show traceback (could comment)
)
# set up all the repoze.who resources at standard
# resource prefixes.
# these MUST be defined for the plugin to work:
testapp.registerStaticResource(prefix="who.identifiers",
resourceValue=identifiers)
testapp.registerStaticResource(prefix="who.authenticators",
resourceValue=authenticators)
testapp.registerStaticResource(prefix="who.challengers",
resourceValue=challengers)
# these are optional:
testapp.registerStaticResource(prefix="who.mdproviders",
resourceValue=mdproviders)
testapp.registerStaticResource(prefix="who.classifier",
resourceValue=default_request_classifier)
testapp.registerStaticResource(prefix="who.challenge_decider",
resourceValue=default_challenge_decider)
testapp.registerStaticResource(prefix="who.log_stream",
resourceValue=log_stream)
testapp.registerStaticResource(prefix="who.log_level",
resourceValue=logging.DEBUG)
return testapp
if __name__=="__main__":
# script interpretation: launch the app in a server at port 8888
import wsgiref.simple_server
testapp = whoTestRootApplication()
print "serving whotest at 8888"
srv = wsgiref.simple_server.make_server('localhost', 8888, testapp)
srv.serve_forever()
INSTALL/test/whotest must include a
standard initialization file INSTALL/test/whotest/__init__.py.
from whiff import resolver
__wsgi_directory__ = True
resolver.publish_templates(__path__[0], globals(), mime_extensions=True)
# import conveniences...
from whiff.middleware.repoze import uid
from whiff.middleware.repoze import protect
from whiff.middleware.repoze import who
from whiff.middleware.repoze import allow
from whiff.middleware.repoze import onStatus
from whiff.middleware import absPath
# INSTALL/test/whotest/__init__.py TO BE CONTINUED BELOW....
__init__.py file identifies whotest as
a WHIFF directory and publishes templates, as usual. The __init__.py
file also imports the whiff.middleware.repoze
middlewares as a convenience so that files in whotest can refer to
{{include "uid"... instead of the equivalent
{{include "whiff_middleware/repoze/uid"....
INSTALL/test/whotest/index.whiff
determines whether the user is logged in and offers
a link to the login page if the user is not logged in.
The "who" middleware authenticates the
user (or fails to authenticate the user). The "onStatus"
middleware presents the loggedIn page to users
who automatically identify and authenticate
successfully, and presents the unknown
page to users who do not automatically authenticate successfully.
{{env whiff.content_type: "text/html"/}}
<h1>Index page for authentication test directory</h1>
{{include "who"}}
{{include "onStatus"}}
{{using loggedIn}}
You are logged in as {{include "uid"/}}:
<a href="login_url/logout_handler_path">
logout or log in as a different user. </a>
{{/using}}
{{using unknown}}
You are not logged in:
<a href="login_url/form?came_from={{get-env whiff.app_path/}}">
login. </a>
{{/using}}
{{/include}}
{{/include}}
<br><br>
<h1>Pages</h1>
....
href links shown above are tightly bound to the
RedirectingFormPlugin implementation logic.
The login_url link
"login_url/form?came_from={{get-env whiff.app_path/}}"
{{get-env whiff.app_path/}}).
The ../logout_handler_path link
"login_url/logout_handler_path"
/logout_handler_path
which the RedirectingFormPlugin handler will recognize as a logout request
RedirectingFormPlugin instance configured above requires a
login page at login_url. Here is the WHIFF configuration template
INSTALL/test/whotest/login_url.whiff which implements the login_url
page.
{{env whiff.content_type: "text/html"/}}
{{include "who" whiff.parse_cgi: true}}
{{cgi-default came_from}}index{{/cgi-default}}
{{include "onStatus"}}
{{using loggedIn}}
You are logged in as {{include "uid"}}UNKNOWN{{/include}}.<br>
You must log out before logging in again.<br>
<a href="/index">Go to the index page.</a>
{{/using}}
{{using unknown}}
You are not logged in.
<h1> Please log in... </h1>
<form action="../login_url/login_handler_path">
<input type="hidden" name="came_from" value="{{get-cgi came_from/}}">
<table border="0">
<tr>
<td>User Name</td>
<td><input type="text" name="login"></input></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" name="password"></input></td>
</tr>
<tr>
<td></td>
<td><input type="submit" name="submit" value="Log In"/></td>
</tr>
</table>
</form>
<a href="/index">Go to the index page without logging in.</a>
{{/using}}
{{/include}}
{{/include}}
RedirectingFormPlugin implementation logic.
"who" middleware attempts to identify and authenticate the user.
"onStatus" middleware refuses to allow a user who is currently logged in to log in again.
../login_url/login_handler_path URL.
RedirectingFormPlugin handler will intercept the request for login_url/login_handler_path
and attempt to log in the user.
RedirectingFormPlugin handler will log in the
user.
RedirectingFormPlugin handler will leave the user logged out.
login_url/login_handler_path will be
redirected to the URL in the came_from input parameter.
index and login_url pages to test the log/in/out mechanism.
For example if we start a test server using the script interpretation of the INSTALL/test/runWho.py configuration
INSTALL/test/ $ python runWho.py
serving whotest at 8888
...
http://localhost:8888/index
we see that we are not logged in:
admin and password admin
admin.
protect middleware invocation
{{include "whiff_middleware/repoze/protect"}}
{{using allowUsers}}true{{/using}}
{{using failureRedirect}}failure{{/using}}
{{using app}}
<h1>Greetings {{include "whiff_middleware/repoze/uid"/}}</h1>
...
{{/using}}
{{/include}}
app should
be redirected to the failureRedirect URL failure.
We implement the failure page using the colorful WHIFF configuration template
INSTALL/test/whotest/failure.whiff
{{env whiff.content_type: "text/html"/}}
<html>
<head> <title> ACCESS DENIED </title> </head>
<body style="color:green; background-color:red">
<h1>Protected page</h1>
You tried to view a page that was protected.
Only specific users may view the page.
<p>
<a href="index">return to index.</a>
</body></html>
INSTALL/test/whotest/loggedIn.whiff uses
the protect middleware to present the app content only
to users who have logged in.
{{env whiff.content_type: "text/html"/}}
{{include "protect"}}
{{using allowUsers}}true{{/using}}
{{using failureRedirect}}failure{{/using}}
{{using app}}
<h1>Greetings {{include "uid"/}}</h1>
As a logged in user you are allowed to learn that the capitol of England is London!
<p>
<a href="index">return to index.</a>
{{/using}}
{{/include}}
{{using allowUsers}}true{{/using}} indicates that
any authenticated user is allowed to view the page.
The parameter {{using failureRedirect}}failure{{/using}}
insists that users who are not logged in should be redirected to the failure
page.
And indeed when we run the test server and attempt to view
http://localhost:8888/loggedIn we receive the failure
page instead:
carla and return to http://localhost:8888/loggedIn
we see the protected content:
INSTALL/test/whotest/ChrissPage.whiff
uses the protect middleware to allow users
chris or admin to view the
app content, with all others redirected to
the failure page.
{{env whiff.content_type: "text/html"/}}
{{include "protect"}}
{{using allowUsers}} ["admin", "chris"] {{/using}}
{{using failureRedirect}}failure{{/using}}
{{using app}}
<h1>Greetings 'admin' or 'chris'</h1>
Did you know that if chickens didn't lay eggs we'd
have to color something else at Easter?
That's why chickens lay eggs.
<p>
<a href="index">return to index.</a>
{{/using}}
{{/include}}
{{using allowUsers}} ["admin", "chris"] {{/using}}
admin or chris are
allowed to see the content. This page is very similar to one presented
earlier.
whiff.middleware.repoze.protect.protectDirectory
is a directory root application factory which is analogous to the protect
middleware, except that it applies access restrictions to a directory and all
the directory contents.
The code fragment below provides the remainder of the source for
INSTALL/test/whotest/__init__.py omitted in the listing above.
The assignments in the code fragment apply access restrictions to
INSTALL/test/whotest subdirectories.
# ... INSTALL/test/whotest/__init__.py continued
from whiff.middleware.repoze.protect import protectDirectory
import loggedInDir
# only logged in users can view loggedInDir content
loggedInDir = protectDirectory(loggedInDir, "loggedInDir", True, "failure")
import loggedOutDir
# only logged out users can view loggedOutDir content
loggedOutDir = protectDirectory(loggedOutDir, "loggedOutDir", None, "failure")
import adminOnlyDir
# only 'admin' can view adminOnlyDir content
adminOnlyDir = protectDirectory(adminOnlyDir, "adminOnlyDir", ["admin"], "failure")
import ChrissDir
# only 'chris' or 'admin' can view ChrissDir content
ChrissDir = protectDirectory(ChrissDir, "ChrissDir", ["chris", "admin"], "failure")
carla attempts to view
http://localhost:8888/ChrissDir/readme.txt she will see the
failure page, but if chris attempts to view
http://localhost:8888/ChrissDir/readme.txt he will see the plain text
contents of the file ADMIN/test/whotest/ChrissDir/readme.txt.
who authentication may
be deployed under any web server configuration which supports the WSGI
interface standard. For example the following CGI script
/usr/local/apache2/cgi-bin/who.cgi deploys
the whotest root application under an Apache server using
the CGI interface.
#!/Library/Frameworks/Python.framework/Versions/Current/bin/python
import sys
import wsgiref.handlers
import whiff.resolver
# import the root directory containing whotest
sys.path.append("/Users/Aaron/work/whiff/test/")
import runWho
# create a WSGI application to serve the whotest directory
application = runWho.whoTestRootApplication(path="/cgi-bin/who.cgi")
# serve a CGI request using the directory
wsgiref.handlers.CGIHandler().run(application)
runWho.whoTestRootApplication(path="/cgi-bin/who.cgi")
function call to construct the application. Note that the CGI script must be told the root path
it replies to /cgi-bin/who.cgi.
Please view this application installed at http://aaron.oirt.rutgers.edu/cgi-bin/who.cgi/. To install the CGI script on your computer you will need to change some of the absolute paths in the script to reflect correct locations on you system, and you will also need to mark the CGI script executable (as usual for CGI scripts).
repoze.who to
repoze.who plugins;