Pluggable Authentication Service
Plone Developer Manual is a comprehensive guide to Plone programming.
1. Introduction
The Pluggable Authentication Service (PAS) is an alternative to the standard Zope User Folder or the popular Group User Folder (GRUF). PAS has a highly modular design, which is very powerful, but also a lot harder to understand.
PAS is built around the concepts of interfaces and plugins: all possible tasks related to user and group management and authentication are described in separate interfaces. These interfaces are implemented by plugins, which can be selectively enabled per interface.
Plone uses PlonePAS, which extends PAS with a couple of extra plugin types and which adds GRUF compatibility. Since PlonePAS extensions are rarely needed and are subject to change in the next Plone releases this tutorial will only focus on pure PAS features.
2. Features and interfaces
A user folder such as PAS provides a number of different services: it takes care of user authentication, it asks users to login if needed, it allows you to search for users and groups.
In order to make both configuration and implementation simpler and more powerful all these different tasks have been divided into different interfaces. Each interface describes how a specific feature, such as authenticating a user, has to be implemented.
Within PAS plugins are used to provide those features. Plugins are small pieces of logic which implement one or more functions as defined by these interfaces.
This separation is useful for different reasons:
- it makes it possible to configure different aspects of the system separately. For example how users authenticate (cookies, login forms, etc.) can be configured separately from where user information is stored (ZODB, LDAP, RADIUS, SQL, etc.). This flexibility makes it very easy to tune the system to specific needs.
- it makes it possible for developers to write small pieces of code that only perform a single task. This leads to code that is easier to understand, more testable and better maintainable.
3. The important interfaces
PAS has a number of interfaces that are important for everyone.
The most important interfaces that you may want to configure are:
- Authentication
- Authentication plugins are responsible for authenticating a set of credentials. Usually that will mean verifying if a login name and password are correct by comparing them with a user record in a database such as the ZODB or an SQL database.
- Extraction
- Extraction plugins determine the credentials for a request. Credentials can take different forms such as a HTTP cookie, HTTP form data or the users IP address.
- Groups
- These plugins determine of which group(s) a user (or group) is a member.
- Properties
- Property plugins manage all properties for users. This includes the standard information such as the user's name and email address but can also be any other piece of data that you want to store for a user. Multiple properties plugins can be used in parallel, making it possible for example to use some information from a central system such as active directory while storing data specific for your Plone site in the ZODB
- User Enumeration
- User enumeration plugins implement the searching logic for users.
4. Configuring PAS
There is no Plone interface to configure PAS: you will need to use the Zope Management Interface (ZMI). In the ZMI you will see a *acl_users* folder in the site root. This is your PAS.
If you open the acl_users folder you will see a number of different items. Each item is a PAS plugin, which implements some PAS functionality.
There is one special item: the plugins objects manages all administrative bookkeeping within PAS. It remembers which interfaces are active for each plugin and in what order the plugins should be called.
Let's take a look to see how this works. If you open the plugins object you will see a list of all the PAS interfaces, along with a short description of what they do.
We will take a look at the extraction plugins. These plugins take care of extracting the credentials such as your username and password from a request. These credentials can then be used to authenticate the user. If you click on the Extraction Plugins header you will see a screen which shows the plugins which implement this interface and allows you to configure which plugins will be used and in what order.
In the default Plone configuration there are two plugins enabled for this interface:
- the credentials_cookie_auth plugin can extract the login name and password from an HTTP cookie and HTTP form values from the login form or portlet
- the credentials_basic_auth plugin can extract the login name and password from standard HTTP authentication headers.
In the default configuration the cookie plugin takes preference over the basic authentication plugin. This means that credentials from a HTTP cookie will be preferred over credentials form HTTP authentication headers if both are present You can try this by first logging in using standard HTTP authentication in the Zope root and then visiting your Plone site and logging in with a different user there: you will see that the new user is now the active user.
You can change the order of the plugins by clicking on a plugin and moving it up or down with the arrows. Using the left and right arrows you can enable and disable a plugin for this interface.
5. Configuring an individual PAS plugin
In addition to enabling and disabling plugins via the plugins object each plugin can also have its own configuration. You can access this by opening a plugin in the ZMI.
Taking the credentials_cookie_auth as example again you will see the screen for the Activate tab. This tab is mandatory and allows you to enable and disable PAS interfaces for a plugin. This corresponds to the plugin configuration we saw earlier, but does not allow you to change the ordering of different plugins for an interface. If you enable a new interface for a particular plugin, it will be activated and placed last in the list of plugins for a particular interface.
You can also go to the properties tab to edit settings specific for this plugin:
What you can configure will differ per plugin. Some plugins do not have any configurations options, others can be very complex.
6. Concepts
PAS has a few basic concepts that you must understand in order to develop PAS related code.
There are a few basic concepts used in PAS:
- credentials
- Credentials are a set of information which can be used to authenticate a user. This can be a login name and password, an IP address, a session cookie or something else.
- user name
- The user name is the name used by the user to log into the system. To avoid confusion between user id and user name this tutorial will use the term login name instead.
- user id
- All users must be uniquely identified by their user id. A users id can be different than the login name.
- principal
- A principal is an identifier for any entity within the authentication system. This can be either a user or a group. This implies that it is not legal to have a user and a group with the same id!
7. The user object
Contrary to other user folders, a user does not have a single source in a PAS environment. Various aspects of a user (properties, groups, roles, etc.) are managed by different plugins. To accommodate this, PAS features a user object which provides a single interface to all different aspects.
There are two basic user types: a normal user (as defined by the IBasicUser interface) and a user with member properties (defined by the IPropertiedUser interface). Since basic users are not used within Plone we will only consider IPropertiedUser users.
- getId()
- returns the user id. This is a unique identifier for a user.
- getUserName()
- Return the login name used by the user to log into the system.
- getRoles()
- Return the roles assigned to a user "globally".
- getRolesInContext(context)
- Return the roles assigned to the user within a specific context. This includes the global roles as returned by getRoles().
8. User creation
PAS uses a multi-phase algorithm to create a user object
- An IUserFactoryPlugin plugin is used to create a new user object.
- All IPropertiesPlugin plugins are queried to get the property sheets.
- All IGroupsPlugin plugins are queried to get the groups.
- All IRolesPlugin plugins are queried to get the global roles
9. User factory plugin
PAS supports multiple user types. PAS contains two default user types: IBasicUser and IPropertiesUser. IBasicUser is a simple user type which supports a user id, login name, roles and domain restrictions. IPropertiedUser extends this type and adds user properties.
A user factory plugin creates a new user instance. PAS will add properties, groups and roles to this instance as part of its user creation process.
If no user factory plugin is able to create a user PAS will fall back to creating a standard PropertiedUser instance.
The IUserFactoryPlugin interface is a simple one containing a single method:
def createUser( user_id, name ):
""" Return a user, if possible.
o Return None to allow another plugin, or the default, to fire.
"""
The default PAS behaviour is demonstrated by this code::
def createUser(self, user_id, name):
return ProperiedUser(user_id, name)
10. Properties plugins
Properties are stored in property sheets: mapping-like objecst, such as a standard python dictionary, which contain the properties for a principal. The property sheets are ordered: if a property is present in multiple property sheets only the property in the sheet with the highest priority is visible.
Property sheets are created by plugins implementing the IPropertiesPlugin interface. This interface contains only a single method:
def getPropertiesForUser( user, request=None ):
""" user -> {}
o User will implement IPropertiedUser.
o Plugin may scribble on the user, if needed (but must still
return a mapping, even if empty).
o May assign properties based on values in the REQUEST object, if
present
"""
Here is a simple example:
def getPropertiesForUser(self, user, request=None):
return { "email" : user.getId() + "@ourcompany.com" }
this adds an email property to a user which is hardcoded to the user id followed by a companies domain name.
11. Group plugins
Group plugins return the identifiers for the groups a principal is a member of. Since a principal can be either a user or a group this implies that PAS can support nested group members. The default PAS configuration does not support this though.
Like other PAS interfaces the IGroupsPlugin interface is simple and only specifies a single method:
def getGroupsForPrincipal( principal, request=None ):
""" principal -> ( group_1, ... group_N )
o Return a sequence of group names to which the principal
(either a user or another group) belongs.
o May assign groups based on values in the REQUEST object, if present
"""
Here is a simple example:
def getGroupsForPrincipal(self, principal, request=None):
# Manager can not be itself
if principal=="Manager":
return ()
# Only act on the current user
if getSecurityManager().getUser().getId()!=principal:
return ()
# Only act if the request originates from the local host
if request is not None:
ip=request.get("HTTP_X_FORWARDED_FOR", request.get("REMOTE_ADDR", ""))
if ip!="127.0.0.1":
return ()
return ("Manager",)
This puts the current user in the Manager group if the site is being accessed from the Zope server itself.
12. Roles plugin
The IRolesPlugin plugins determine the global roles for a principal. Like the other interfaces the IRolesPlugin interface contains only a single method:
def getRolesForPrincipal( principal, request=None ):
""" principal -> ( role_1, ... role_N )
o Return a sequence of role names which the principal has.
o May assign roles based on values in the REQUEST object, if present.
"""
Here is a simple example:
def getRolesForPrincipal(self, principal, request=None):
# Only act on the current user
if getSecurityManager().getUser().getId()!=principal:
return ()
# Only act if the request originates from the local host
if request is not None:
ip=request.get("HTTP_X_FORWARDED_FOR", request.get("REMOTE_ADDR", ""))
if ip!="127.0.0.1":
return ()
return ("Manager",)
This gives the current user in Manager role if the site is being accessed from the Zope server itself.
13. Authorisation algorithm
These are the steps the PAS user folder follows in its validate method:
- extract all credentials. This looks for any possible form of authentication information in a request: HTTP cookies, HTTP form parameters, HTTP authentication headers, originating IP address, etc. A request can have multiple (or no) sets of credentials.
- for each set of credentials found
- try to authorise the credentials. This checks if the credentials correspond to a known user and are valid.
- create a user instance
- try to authorise the request. If succesful use this user and stop further processing.
- create an anonymous user
- try to authorise the request using the anonymous user. If succesful use this, if not:
- issue a challenge
14. Credential extraction
Within PAS credentials are a set of information which can identify and authenticate a user. A users login name and password are for example very common credentials. You may also use an HTTP cookie to track users; if you do so the cookie will be your credential.
PAS user credential extraction plugins to find all credentials in a request. Authentication of these credentials is done at a later stage by seperate authentication plugin.
Writing a plugin
If you want to write your own credential extraction plugin it has to implement the IExtractionPlugin interface. This interface only has a single method:
def extractCredentials( request ):
""" request -> {...}
o Return a mapping of any derived credentials.
o Return an empty mapping to indicate that the plugin found no
appropriate credentials.
"""
Here is a simple example:
def extractCredentials(self, request):
login=request.get("login", None)
if login is None:
return {}
password="request.get("password", None)
return { "login" : login, "password" : password }
This plugin extracts the login name and password from fields with the same name in the request object.
15. Credential authentication
The credentials as returned by the credential extraction plugins only reflect the authentication information provided by the user. These credentials need to be authenticated by an authentication plugin to check if they are correct for a real user.
The IAuthenticationPlugin interface is a simple one:
def authenticateCredentials( credentials ):
""" credentials -> (userid, login)
o 'credentials' will be a mapping, as returned by IExtractionPlugin.
o Return a tuple consisting of user ID (which may be different
from the login name) and login
o If the credentials cannot be authenticated, return None.
"""
Here is a simple example:
def authenticateCredentials(self, credentials):
users={ "hanno" : "hannosch", "martin" : "optilude",
"philipp" : "philiKON" }
if "login" not in credentials or "password" not in credentials:
return None
login=credentials["login"]
password=credentials["password"]
if users.get(login, None)==password:
return (login, login)
return None
This plugin allows the users hanno, martin and philipp to login with their nickname as password.
16. Challenges
If the current (possibly anonymous) user is not authorised to access a resource Zope asks PAS to challenge the user. Generally this will result in a login form being shown, asking the user with a appropriately priviliged account.
The IChallengeProtocolChooser and IChallengePlugins plugins work together to do this. Since Zope can be accessed via various protocols (browsers, WebDAV, XML-RPC, etc.) PAS first needs to figure out what kind of protocol it is dealing with. This is done by quering all IChallengeProtocolChooser plugins. The default implementation is ChallengeProtocolChooser, which asks all IRequestTypeSniffer plugins to test for specific protocols.
Once the protocol list has been build PAS will look at all active IChallengePlugins plugins.
Writing a plugin
The IChallengePlugin interface is very simple: it only contains one method:
def challenge( request, response ):
""" Assert via the response that credentials will be gathered.
Takes a REQUEST object and a RESPONSE object.
Returns True if it fired, False otherwise.
Two common ways to initiate a challenge:
- Add a 'WWW-Authenticate' header to the response object.
NOTE: add, since the HTTP spec specifically allows for
more than one challenge in a given response.
- Cause the response object to redirect to another URL (a
login form page, for instance)
"""
The plugin can look at the request object to determine what, or if, it needs to do. It can then modify the response object to issue its challenge to the user. For example:
def challenge(self, request, response):
response.redirect("http://www.disney.com/")
return True
this will redirect a user to the Disney homepage every time he tries to access something he is not authorised for.
17. PAS eats exceptions
A broken user folder is one of the worst things that can happen in Zope: it can make it impossible to access any objects underneath the user folders level.
In order to secure itself against errors in plugins PAS ignores all exceptions of the common exception types: NameError, AttributeError, KeyError, TypeError and ValueError.
This can make debugging plugins hard: an error in a plugin can be silently ignored if its exception is swallowed by PAS.
18. Plugins
Detail about the stock plugins provided by PAS and how to create new ones
18.1. Plugin Interfaces
PAS Plugins are broken down by the different functionalities they provide.
18.1.1. List of Plugin Interfaces
PAS Plugins are broken down by the different functionalities they provide. A particular plugin may provide one or many of the following interfaces
- Extraction Plugins
Extraction plugins are responsible for extracting credentials from the request.
- Authentication Plugins
Authentication plugins are responsible for validating credentials generated by the Extraction Plugin.
- Challenge Plugins
Challenge plugins initiate a challenge to the user to provide credentials.
- Update Credentials Plugins
Credential update plugins respond to the user changing credentials.
- Reset Credentials Plugins
Credential clear plugins respond to a user logging out.
- Userfactory Plugins
Create users.
- Anonymoususerfactory Plugins
Create anonymous users.
- Properties Plugins
Properties plugins generate property sheets for users.
- Groups Plugins
Groups plugins determine the groups to which a user belongs.
- Roles Plugins
Roles plugins determine the global roles which a user has.
- Update Plugins
Update plugins allow the user or the application to update the user's properties.
- Validation Plugins
Validation plugins specify allowable values for user properties (e.g., minimum password length, allowed characters, etc.)
- User_Enumeration Plugins
Enumeration plugins allow querying users by ID, and searching for users who match particular criteria.
- User_Adder Plugins
User Adder plugins allow the Pluggable Auth Service to create users.
- Group_Enumeration Plugins
Enumeration plugins allow querying groups by ID.
- Role_Enumeration Plugins
Enumeration plugins allow querying roles by ID.
- Role_Assigner Plugins
Role Assigner plugins allow the Pluggable Auth Service to assign
18.2. Plugin Types
A list of the different types of plugins
18.2.1. Extraction Plugins
Extraction plugins are responsible for extracting credentials from the request.
Stock Plugins
The following stock plugins provide the IExtractionPlugin Interface.
Cookie Auth Helper
This plugin helps manage the details of Cookie Authentication. Allows you to extract credentials from a cookie, update them, reset them, etc.
HTTP Basic Auth Helper
Multi-plugin for managing details of HTTP Basic Authentication. Extracts credentials from request and implements the HTTP Auth challenge.
Inline Auth Helper
Manages credentials for inline authentication.
Session Auth Helper
Extracts and manages credentials for session authentication.
Methods
Each plugin implements the following methods:
- extractCredentials() -- gets credential info from the relevant request, cookie, session, etc.
- updateCredentials() -- responds to a change of credentials
- resetCredentials() -- empties out currently stored values
if appropriate, the plugin will also implement a challenge() method which will challenge the user for authentication.
18.2.2. Authentication Plugins
Authentication plugins are responsible for validating credentials generated by the Extraction Plugin.
Stock Plugins
Delegating Multi Plugin
This plugin delegates a PAS interface to some other acl_user folder, typically a "legacy" folder that implements some specific authentication functionality. For example, you can delegate the IAuthenticationPlugin interface to a legacy user folder via a Delegating Multi Plugin.
Domain Auth Helper
Authenticates users based on their IP address. Has nothing to do with Windows "Domain" Authentication.ZODB User Manager
ZODB-based user storage. Does authentication, enumeration and properties for users and stores its data in the ZODB.18.2.3. Challenge Plugins
Challenge plugins initiate a challenge to the user to provide credentials.
Stock Plugins
Cookie Auth Helper
This plugin helps manage the details of Cookie Authentication. Allows you to extract credentials from a cookie, update them, reset them, etc.
HTTP Basic Auth Helper
Multi-plugin for managing details of HTTP Basic Authentication. Extracts credentials from request and implements the HTTP Auth challenge.
Inline Auth Helper
Manages credentials for inline authentication.
18.2.4. Update Credentials Plugins
Credential update plugins respond to the user changing credentials.
Stock Plugins
Cookie Auth Helper
This plugin helps manage the details of Cookie Authentication. Allows you to extract credentials from a cookie, update them, reset them, etc.
Delegating Multi Plugin
This plugin delegates a PAS interface to some other acl_user folder, typically a "legacy" folder that implements some specific authentication functionality. For example, you can delegate the IAuthenticationPlugin interface to a legacy user folder via a Delegating Multi Plugin.
Inline Auth Helper
Manages credentials for inline authentication.
Session Auth Helper
Extracts and manages credentials for session authentication.
18.2.5. Reset Credentials Plugins
Credential clear plugins respond to a user logging out.
Stock Plugins
Cookie Auth Helper
This plugin helps manage the details of Cookie Authentication. Allows you to extract credentials from a cookie, update them, reset them, etc.
Delegating Multi Plugin
This plugin delegates a PAS interface to some other acl_user folder, typically a "legacy" folder that implements some specific authentication functionality. For example, you can delegate the IAuthenticationPlugin interface to a legacy user folder via a Delegating Multi Plugin.
HTTP Basic Auth Helper
Multi-plugin for managing details of HTTP Basic Authentication. Extracts credentials from request and implements the HTTP Auth challenge.
Inline Auth Helper
Manages credentials for inline authentication.
Session Auth Helper
Extracts and manages credentials for session authentication.
18.2.6. Properties Plugins
Properties plugins generate property sheets for users.
Stock Plugins
Delegating Multi Plugin
This plugin delegates a PAS interface to some other acl_user folder, typically a "legacy" folder that implements some specific authentication functionality. For example, you can delegate the IAuthenticationPlugin interface to a legacy user folder via a Delegating Multi Plugin.
18.2.7. Groups Plugins
Groups plugins determine the groups to which a user belongs.
Stock Plugins
Dynamic Groups Plugin
This plugin allows you to create dynamic groups via business rules.
Recursive Groups Plugin
This plugin will recursively flatten a collection of groups.
ZODB Group Manager
This plugin lets you manage groups and groups of groups in the ZODB.
18.2.8. Roles Plugins
Roles plugins determine the global roles which a user has.
Stock Plugins
Delegating Multi Plugin
This plugin delegates a PAS interface to some other acl_user folder, typically a "legacy" folder that implements some specific authentication functionality. For example, you can delegate the IAuthenticationPlugin interface to a legacy user folder via a Delegating Multi Plugin.
Domain Auth Helper
Authenticates users based on their IP address. Has nothing to do with Windows "Domain" Authentication.
ZODB Role Manager
Stores role information for users in the ZODB. Handles roles storage, role enumeration, and role assignment.
18.2.9. User_Enumeration Plugins
Enumeration plugins allow querying users by ID, and searching for users who match particular criteria.
Stock Plugins
Delegating Multi Plugin
This plugin delegates a PAS interface to some other acl_user folder, typically a "legacy" folder that implements some specific authentication functionality. For example, you can delegate the IAuthenticationPlugin interface to a legacy user folder via a Delegating Multi Plugin.
Search Principals Plugin
Plugin to delegate enumerateUsers and enumerateGroups requests to another PluggableAuthService
ZODB User Manager
ZODB-based user storage. Does authentication, enumeration and properties for users and stores its data in the ZODB.
18.2.10. User_Adder Plugins
User Adder plugins allow the Pluggable Auth Service to create users.
Stock Plugins
ZODB User Manager
ZODB-based user storage. Does authentication, enumeration and properties for users and stores its data in the ZODB.
18.2.11. Group_Enumeration Plugins
Enumeration plugins allow querying groups by ID.
Stock Plugins
Dynamic Groups Plugin
This plugin allows you to create dynamic groups via business rules.
Search Principals Plugin
Plugin to delegate enumerateUsers and enumerateGroups requests to another PluggableAuthService
ZODB Group Manager
This plugin lets you manage groups and groups of groups in the ZODB.
18.2.12. Role_Enumeration Plugins
Enumeration plugins allow querying roles by ID.
Stock Plugins
ZODB Role Manager
Stores role information for users in the ZODB. Handles roles storage, role enumeration, and role assignment.
18.2.13. Role_Assigner Plugins
Role Assigner plugins allow the Pluggable Auth Service to assign
Stock Plugins
ZODB Role Manager
Stores role information for users in the ZODB. Handles roles storage, role enumeration, and role assignment.
