[Checkins] SVN: zope.app.authentication/tags/3.5.0/ Tag 3.5.0

Dan Korostelev nadako at gmail.com
Fri Mar 6 08:11:03 EST 2009


Log message for revision 97574:
  Tag 3.5.0

Changed:
  A   zope.app.authentication/tags/3.5.0/
  D   zope.app.authentication/tags/3.5.0/CHANGES.txt
  A   zope.app.authentication/tags/3.5.0/CHANGES.txt
  D   zope.app.authentication/tags/3.5.0/setup.py
  A   zope.app.authentication/tags/3.5.0/setup.py
  D   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt
  A   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt
  D   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py
  A   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py
  D   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt
  A   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt
  D   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py
  A   zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py

-=-
Deleted: zope.app.authentication/tags/3.5.0/CHANGES.txt
===================================================================
--- zope.app.authentication/trunk/CHANGES.txt	2009-03-06 13:03:45 UTC (rev 97572)
+++ zope.app.authentication/tags/3.5.0/CHANGES.txt	2009-03-06 13:11:03 UTC (rev 97574)
@@ -1,63 +0,0 @@
-=======
-Changes
-=======
-
-3.5.0 (unreleased)
-------------------
-
-* Split password manager functionality off to the new ``zope.password``
-  package. Backward-compatibility imports are left in place.
-
-3.5.0a2 (2009-02-01)
---------------------
-
-* Make old encoded passwords really work.
-
-3.5.0a1 (2009-01-31)
---------------------
-
-* Use ``zope.container`` instead of ``zope.app.container``.
-
-* Encoded passwords are now stored with a prefix ({MD5}, {SHA1},
-  {SSHA}) indicating the used encoding schema. Old (encoded) passwords
-  can still be used.
-
-* Add an SSHA password manager that is compatible with standard LDAP
-  passwords. As this encoding gives better security agains dictionary
-  attacks, users are encouraged to switch to this new password schema.
-
-* InternalPrincipal now uses SSHA password manager by default.
-
-3.4.4 (2008-12-12)
-------------------
-
-* Depend on zope.session instead of zope.app.session. The first one
-  currently has all functionality we need.
-* Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6.
-
-3.4.3 (2008-08-07)
-------------------
-
-* No changes. Retag for correct release on PyPI.
-
-3.4.2 (2008-07-09)
--------------------
-
-* Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes,
-  Changed ``super(BTreeContainer, self).__init__()`` to 
-  ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class.
-
-3.4.1 (2007-10-24)
-------------------
-
-* Avoid deprecation warning.
-
-3.4.0 (2007-10-11)
-------------------
-
-* Updated package meta-data.
-
-3.4.0b1 (2007-09-27)
---------------------
-
-* First release independent of Zope.

Copied: zope.app.authentication/tags/3.5.0/CHANGES.txt (from rev 97573, zope.app.authentication/trunk/CHANGES.txt)
===================================================================
--- zope.app.authentication/tags/3.5.0/CHANGES.txt	                        (rev 0)
+++ zope.app.authentication/tags/3.5.0/CHANGES.txt	2009-03-06 13:11:03 UTC (rev 97574)
@@ -0,0 +1,65 @@
+=======
+Changes
+=======
+
+3.5.0 (2009-03-06)
+------------------
+
+* Split password manager functionality off to the new ``zope.password``
+  package. Backward-compatibility imports are left in place.
+
+* Use ``zope.site`` instead of ``zope.app.component``.
+
+3.5.0a2 (2009-02-01)
+--------------------
+
+* Make old encoded passwords really work.
+
+3.5.0a1 (2009-01-31)
+--------------------
+
+* Use ``zope.container`` instead of ``zope.app.container``.
+
+* Encoded passwords are now stored with a prefix ({MD5}, {SHA1},
+  {SSHA}) indicating the used encoding schema. Old (encoded) passwords
+  can still be used.
+
+* Add an SSHA password manager that is compatible with standard LDAP
+  passwords. As this encoding gives better security agains dictionary
+  attacks, users are encouraged to switch to this new password schema.
+
+* InternalPrincipal now uses SSHA password manager by default.
+
+3.4.4 (2008-12-12)
+------------------
+
+* Depend on zope.session instead of zope.app.session. The first one
+  currently has all functionality we need.
+* Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6.
+
+3.4.3 (2008-08-07)
+------------------
+
+* No changes. Retag for correct release on PyPI.
+
+3.4.2 (2008-07-09)
+-------------------
+
+* Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes,
+  Changed ``super(BTreeContainer, self).__init__()`` to 
+  ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class.
+
+3.4.1 (2007-10-24)
+------------------
+
+* Avoid deprecation warning.
+
+3.4.0 (2007-10-11)
+------------------
+
+* Updated package meta-data.
+
+3.4.0b1 (2007-09-27)
+--------------------
+
+* First release independent of Zope.

Deleted: zope.app.authentication/tags/3.5.0/setup.py
===================================================================
--- zope.app.authentication/trunk/setup.py	2009-03-06 13:03:45 UTC (rev 97572)
+++ zope.app.authentication/tags/3.5.0/setup.py	2009-03-06 13:11:03 UTC (rev 97574)
@@ -1,86 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Setup for zope.app.authentication package
-
-$Id$
-"""
-import os
-from setuptools import setup, find_packages
-
-def read(*rnames):
-    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
-
-setup(name='zope.app.authentication',
-      version = '3.5.0a3dev',
-      author='Zope Corporation and Contributors',
-      author_email='zope-dev at zope.org',
-      description='Pluggable Authentication Utility',
-      long_description=(
-        read('README.txt')
-        + '\n\n' +
-        'Detailed Documentation\n' +
-        '----------------------\n'
-        + '\n' +
-        read('src', 'zope', 'app', 'authentication', 'README.txt')
-        + '\n\n' +
-        read('src', 'zope', 'app', 'authentication', 'principalfolder.txt')
-        + '\n\n' +
-        read('src', 'zope', 'app', 'authentication', 'vocabulary.txt')
-        + '\n\n' +
-        read('CHANGES.txt')
-        ),
-      url='http://pypi.python.org/pypi/zope.app.authentication',
-      license='ZPL 2.1',
-      classifiers = [
-          'Development Status :: 5 - Production/Stable',
-          'Environment :: Web Environment',
-          'Intended Audience :: Developers',
-          'License :: OSI Approved :: Zope Public License',
-          'Programming Language :: Python',
-          'Natural Language :: English',
-          'Operating System :: OS Independent',
-          'Topic :: Internet :: WWW/HTTP',
-          'Framework :: Zope3'],
-      keywords='zope3 authentication pluggable principal group',
-      packages=find_packages('src'),
-      package_dir = {'': 'src'},
-      extras_require=dict(test=['zope.app.testing',
-                                'zope.app.securitypolicy',
-                                'zope.app.zcmlfiles',
-                                'zope.securitypolicy',
-                                'zope.testbrowser']),
-      namespace_packages=['zope', 'zope.app'],
-      install_requires=['setuptools',
-                        'zope.app.component',
-                        'zope.app.container',
-                        'zope.app.form',
-                        'zope.app.security',
-                        'zope.dublincore',
-                        'zope.event',
-                        'zope.exceptions',
-                        'zope.i18n',
-                        'zope.i18nmessageid',
-                        'zope.interface',
-                        'zope.location',
-                        'zope.password',
-                        'zope.publisher',
-                        'zope.schema',
-                        'zope.security',
-                        'zope.session',
-                        'zope.traversing',
-                        'ZODB3',
-                        ],
-      include_package_data = True,
-      zip_safe = False,
-      )

Copied: zope.app.authentication/tags/3.5.0/setup.py (from rev 97573, zope.app.authentication/trunk/setup.py)
===================================================================
--- zope.app.authentication/tags/3.5.0/setup.py	                        (rev 0)
+++ zope.app.authentication/tags/3.5.0/setup.py	2009-03-06 13:11:03 UTC (rev 97574)
@@ -0,0 +1,87 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup for zope.app.authentication package
+
+$Id$
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup(name='zope.app.authentication',
+      version = '3.5.0',
+      author='Zope Corporation and Contributors',
+      author_email='zope-dev at zope.org',
+      description='Pluggable Authentication Utility',
+      long_description=(
+        read('README.txt')
+        + '\n\n' +
+        'Detailed Documentation\n' +
+        '----------------------\n'
+        + '\n' +
+        read('src', 'zope', 'app', 'authentication', 'README.txt')
+        + '\n\n' +
+        read('src', 'zope', 'app', 'authentication', 'principalfolder.txt')
+        + '\n\n' +
+        read('src', 'zope', 'app', 'authentication', 'vocabulary.txt')
+        + '\n\n' +
+        read('CHANGES.txt')
+        ),
+      url='http://pypi.python.org/pypi/zope.app.authentication',
+      license='ZPL 2.1',
+      classifiers = [
+          'Development Status :: 5 - Production/Stable',
+          'Environment :: Web Environment',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: Zope Public License',
+          'Programming Language :: Python',
+          'Natural Language :: English',
+          'Operating System :: OS Independent',
+          'Topic :: Internet :: WWW/HTTP',
+          'Framework :: Zope3'],
+      keywords='zope3 authentication pluggable principal group',
+      packages=find_packages('src'),
+      package_dir = {'': 'src'},
+      extras_require=dict(test=['zope.app.testing',
+                                'zope.app.securitypolicy',
+                                'zope.app.zcmlfiles',
+                                'zope.securitypolicy',
+                                'zope.testbrowser']),
+      namespace_packages=['zope', 'zope.app'],
+      install_requires=['setuptools',
+                        'zope.app.component',
+                        'zope.app.container',
+                        'zope.app.form',
+                        'zope.app.security',
+                        'zope.dublincore',
+                        'zope.event',
+                        'zope.exceptions',
+                        'zope.i18n',
+                        'zope.i18nmessageid',
+                        'zope.interface',
+                        'zope.location',
+                        'zope.password',
+                        'zope.publisher',
+                        'zope.schema',
+                        'zope.security',
+                        'zope.session',
+                        'zope.site',
+                        'zope.traversing',
+                        'ZODB3',
+                        ],
+      include_package_data = True,
+      zip_safe = False,
+      )

Deleted: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt
===================================================================
--- zope.app.authentication/trunk/src/zope/app/authentication/README.txt	2009-03-06 13:03:45 UTC (rev 97572)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt	2009-03-06 13:11:03 UTC (rev 97574)
@@ -1,822 +0,0 @@
-================================
-Pluggable-Authentication Utility
-================================
-
-The Pluggable-Authentication Utility (PAU) provides a framework for
-authenticating principals and associating information with them. It uses
-plugins and subscribers to get its work done.
-
-For a pluggable-authentication utility to be used, it should be
-registered as a utility providing the
-`zope.app.security.interfaces.IAuthentication` interface.
-
-Authentication
---------------
-
-The primary job of PAU is to authenticate principals. It uses two types of
-plug-ins in its work:
-
-  - Credentials Plugins
-
-  - Authenticator Plugins
-
-Credentials plugins are responsible for extracting user credentials from a
-request. A credentials plugin may in some cases issue a 'challenge' to obtain
-credentials. For example, a 'session' credentials plugin reads credentials
-from a session (the "extraction"). If it cannot find credentials, it will
-redirect the user to a login form in order to provide them (the "challenge").
-
-Authenticator plugins are responsible for authenticating the credentials
-extracted by a credentials plugin. They are also typically able to create
-principal objects for credentials they successfully authenticate.
-
-Given a request object, the PAU returns a principal object, if it can. The PAU
-does this by first iterateing through its credentials plugins to obtain a
-set of credentials. If it gets credentials, it iterates through its
-authenticator plugins to authenticate them.
-
-If an authenticator succeeds in authenticating a set of credentials, the PAU
-uses the authenticator to create a principal corresponding to the credentials.
-The authenticator notifies subscribers if an authenticated principal is
-created. Subscribers are responsible for adding data, especially groups, to
-the principal. Typically, if a subscriber adds data, it should also add
-corresponding interface declarations.
-
-Simple Credentials Plugin
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To illustrate, we'll create a simple credentials plugin::
-
-  >>> from zope import interface
-  >>> from zope.app.authentication import interfaces
-
-  >>> class MyCredentialsPlugin(object):
-  ...
-  ...     interface.implements(interfaces.ICredentialsPlugin)
-  ...
-  ...     def extractCredentials(self, request):
-  ...         return request.get('credentials')
-  ...
-  ...     def challenge(self, request):
-  ...         pass # challenge is a no-op for this plugin
-  ...
-  ...     def logout(self, request):
-  ...         pass # logout is a no-op for this plugin
-
-As a plugin, MyCredentialsPlugin needs to be registered as a named utility::
-
-  >>> myCredentialsPlugin = MyCredentialsPlugin()
-  >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin')
-
-Simple Authenticator Plugin
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Next we'll create a simple authenticator plugin. For our plugin, we'll need
-an implementation of IPrincipalInfo::
-
-  >>> class PrincipalInfo(object):
-  ...
-  ...     interface.implements(interfaces.IPrincipalInfo)
-  ...
-  ...     def __init__(self, id, title, description):
-  ...         self.id = id
-  ...         self.title = title
-  ...         self.description = description
-  ...
-  ...     def __repr__(self):
-  ...         return 'PrincipalInfo(%r)' % self.id
-
-Our authenticator uses this type when it creates a principal info::
-
-  >>> class MyAuthenticatorPlugin(object):
-  ...
-  ...     interface.implements(interfaces.IAuthenticatorPlugin)
-  ...
-  ...     def authenticateCredentials(self, credentials):
-  ...         if credentials == 'secretcode':
-  ...             return PrincipalInfo('bob', 'Bob', '')
-  ...
-  ...     def principalInfo(self, id):
-  ...         pass # plugin not currently supporting search
-
-As with the credentials plugin, the authenticator plugin must be registered
-as a named utility::
-
-  >>> myAuthenticatorPlugin = MyAuthenticatorPlugin()
-  >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin')
-
-Principal Factories
-~~~~~~~~~~~~~~~~~~~
-
-While authenticator plugins provide principal info, they are not responsible
-for creating principals. This function is performed by factory adapters. For
-these tests we'll borrow some factories from the principal folder::
-
-  >>> from zope.app.authentication import principalfolder
-  >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory)
-  >>> provideAdapter(principalfolder.FoundPrincipalFactory)
-
-For more information on these factories, see their docstrings.
-
-Configuring a PAU
-~~~~~~~~~~~~~~~~~
-
-Finally, we'll create the PAU itself::
-
-  >>> from zope.app import authentication
-  >>> pau = authentication.PluggableAuthentication('xyz_')
-
-and configure it with the two plugins::
-
-  >>> pau.credentialsPlugins = ('My Credentials Plugin', )
-  >>> pau.authenticatorPlugins = ('My Authenticator Plugin', )
-
-Using the PAU to Authenticate
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We can now use the PAU to authenticate a sample request::
-
-  >>> from zope.publisher.browser import TestRequest
-  >>> print pau.authenticate(TestRequest())
-  None
-
-In this case, we cannot authenticate an empty request. In the same way, we
-will not be able to authenticate a request with the wrong credentials::
-
-  >>> print pau.authenticate(TestRequest(credentials='let me in!'))
-  None
-
-However, if we provide the proper credentials::
-
-  >>> request = TestRequest(credentials='secretcode')
-  >>> principal = pau.authenticate(request)
-  >>> principal
-  Principal('xyz_bob')
-
-we get an authenticated principal.
-
-Authenticated Principal Creates Events
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We can verify that the appropriate event was published::
-
-  >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
-  >>> event.principal is principal
-  True
-  >>> event.info
-  PrincipalInfo('bob')
-  >>> event.request is request
-  True
-
-The info object has the id, title, and description of the principal.  The info
-object is also generated by the authenticator plugin, so the plugin may
-itself have provided additional information on the info object::
-
-  >>> event.info.title
-  'Bob'
-  >>> event.info.id # does not include pau prefix
-  'bob'
-  >>> event.info.description
-  ''
-
-It is also decorated with two other attributes, credentialsPlugin and
-authenticatorPlugin: these are the plugins used to extract credentials for and
-authenticate this principal.  These attributes can be useful for subscribers
-that want to react to the plugins used.  For instance, subscribers can
-determine that a given credential plugin does or does not support logout, and
-provide information usable to show or hide logout user interface::
-
-  >>> event.info.credentialsPlugin is myCredentialsPlugin
-  True
-  >>> event.info.authenticatorPlugin is myAuthenticatorPlugin
-  True
-
-Normally, we provide subscribers to these events that add additional
-information to the principal. For example, we'll add one that sets
-the title::
-
-  >>> def add_info(event):
-  ...     event.principal.title = event.info.title
-  >>> subscribe([interfaces.IAuthenticatedPrincipalCreated], None, add_info)
-
-Now, if we authenticate a principal, its title is set::
-
-  >>> principal = pau.authenticate(request)
-  >>> principal.title
-  'Bob'
-
-Multiple Authenticator Plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The PAU works with multiple authenticator plugins. It uses each plugin, in the
-order specified in the PAU's authenticatorPlugins attribute, to authenticate
-a set of credentials.
-
-To illustrate, we'll create another authenticator::
-
-  >>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin):
-  ...
-  ...     def authenticateCredentials(self, credentials):
-  ...         if credentials == 'secretcode':
-  ...             return PrincipalInfo('black', 'Black Spy', '')
-  ...         elif credentials == 'hiddenkey':
-  ...             return PrincipalInfo('white', 'White Spy', '')
-
-  >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2')
-
-If we put it before the original authenticator::
-
-  >>> pau.authenticatorPlugins = (
-  ...     'My Authenticator Plugin 2',
-  ...     'My Authenticator Plugin')
-
-Then it will be given the first opportunity to authenticate a request::
-
-  >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('xyz_black')
-
-If neither plugins can authenticate, pau returns None::
-
-  >>> print pau.authenticate(TestRequest(credentials='let me in!!'))
-  None
-
-When we change the order of the authenticator plugins::
-
-  >>> pau.authenticatorPlugins = (
-  ...     'My Authenticator Plugin',
-  ...     'My Authenticator Plugin 2')
-
-we see that our original plugin is now acting first::
-
-  >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('xyz_bob')
-
-The second plugin, however, gets a chance to authenticate if first does not::
-
-  >>> pau.authenticate(TestRequest(credentials='hiddenkey'))
-  Principal('xyz_white')
-
-Multiple Credentials Plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As with with authenticators, we can specify multiple credentials plugins. To
-illustrate, we'll create a credentials plugin that extracts credentials from
-a request form::
-
-  >>> class FormCredentialsPlugin:
-  ...
-  ...     interface.implements(interfaces.ICredentialsPlugin)
-  ...
-  ...     def extractCredentials(self, request):
-  ...         return request.form.get('my_credentials')
-  ...
-  ...     def challenge(self, request):
-  ...         pass
-  ...
-  ...     def logout(request):
-  ...         pass
-
-  >>> provideUtility(FormCredentialsPlugin(),
-  ...                name='Form Credentials Plugin')
-
-and insert the new credentials plugin before the existing plugin::
-
-  >>> pau.credentialsPlugins = (
-  ...     'Form Credentials Plugin',
-  ...     'My Credentials Plugin')
-
-The PAU will use each plugin in order to try and obtain credentials from a
-request::
-
-  >>> pau.authenticate(TestRequest(credentials='secretcode',
-  ...                              form={'my_credentials': 'hiddenkey'}))
-  Principal('xyz_white')
-
-In this case, the first credentials plugin succeeded in getting credentials
-from the form and the second authenticator was able to authenticate the
-credentials. Specifically, the PAU went through these steps:
-
- - Get credentials using 'Form Credentials Plugin'
-
- - Got 'hiddenkey' credentials using 'Form Credentials Plugin', try to
-   authenticate using 'My Authenticator Plugin'
-
- - Failed to authenticate 'hiddenkey' with 'My Authenticator Plugin', try
-   'My Authenticator Plugin 2'
-
- - Succeeded in authenticating with 'My Authenticator Plugin 2'
-
-Let's try a different scenario::
-
-  >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('xyz_bob')
-
-In this case, the PAU went through these steps::
-
-  - Get credentials using 'Form Credentials Plugin'
-
-  - Failed to get credentials using 'Form Credentials Plugin', try
-    'My Credentials Plugin'
-
-  - Got 'scecretcode' credentials using 'My Credentials Plugin', try to
-    authenticate using 'My Authenticator Plugin'
-
-  - Succeeded in authenticating with 'My Authenticator Plugin'
-
-Let's try a slightly more complex scenario::
-
-  >>> pau.authenticate(TestRequest(credentials='hiddenkey',
-  ...                              form={'my_credentials': 'bogusvalue'}))
-  Principal('xyz_white')
-
-This highlights PAU's ability to use multiple plugins for authentication:
-
-  - Get credentials using 'Form Credentials Plugin'
-
-  - Got 'bogusvalue' credentials using 'Form Credentials Plugin', try to
-    authenticate using 'My Authenticator Plugin'
-
-  - Failed to authenticate 'boguskey' with 'My Authenticator Plugin', try
-    'My Authenticator Plugin 2'
-
-  - Failed to authenticate 'boguskey' with 'My Authenticator Plugin 2' --
-    there are no more authenticators to try, so lets try the next credentials
-    plugin for some new credentials
-
-  - Get credentials using 'My Credentials Plugin'
-
-  - Got 'hiddenkey' credentials using 'My Credentials Plugin', try to
-    authenticate using 'My Authenticator Plugin'
-
-  - Failed to authenticate 'hiddenkey' using 'My Authenticator Plugin', try
-    'My Authenticator Plugin 2'
-
-  - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and
-    cheers!)
-
-
-Principal Searching
--------------------
-
-As a component that provides IAuthentication, a PAU lets you lookup a
-principal with a principal ID. The PAU looks up a principal by delegating to
-its authenticators. In our example, none of the authenticators implement this
-search capability, so when we look for a principal::
-
-  >>> print pau.getPrincipal('xyz_bob')
-  Traceback (most recent call last):
-  PrincipalLookupError: bob
-
-  >>> print pau.getPrincipal('white')
-  Traceback (most recent call last):
-  PrincipalLookupError: white
-
-  >>> print pau.getPrincipal('black')
-  Traceback (most recent call last):
-  PrincipalLookupError: black
-
-For a PAU to support search, it needs to be configured with one or more
-authenticator plugins that support search. To illustrate, we'll create a new
-authenticator::
-
-  >>> class SearchableAuthenticatorPlugin:
-  ...
-  ...     interface.implements(interfaces.IAuthenticatorPlugin)
-  ...
-  ...     def __init__(self):
-  ...         self.infos = {}
-  ...         self.ids = {}
-  ...
-  ...     def principalInfo(self, id):
-  ...         return self.infos.get(id)
-  ...
-  ...     def authenticateCredentials(self, credentials):
-  ...         id = self.ids.get(credentials)
-  ...         if id is not None:
-  ...             return self.infos[id]
-  ...
-  ...     def add(self, id, title, description, credentials):
-  ...         self.infos[id] = PrincipalInfo(id, title, description)
-  ...         self.ids[credentials] = id
-
-This class is typical of an authenticator plugin. It can both authenticate
-principals and find principals given a ID. While there are cases
-where an authenticator may opt to not perform one of these two functions, they
-are less typical.
-
-As with any plugin, we need to register it as a utility::
-
-  >>> searchable = SearchableAuthenticatorPlugin()
-  >>> provideUtility(searchable, name='Searchable Authentication Plugin')
-
-We'll now configure the PAU to use only the searchable authenticator::
-
-  >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',)
-
-and add some principals to the authenticator::
-
-  >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b')
-  >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack')
-
-Now when we ask the PAU to find a principal::
-
-  >>> pau.getPrincipal('xyz_bob')
-  Principal('xyz_bob')
-
-but only those it knows about::
-
-  >>> print pau.getPrincipal('black')
-  Traceback (most recent call last):
-  PrincipalLookupError: black
-
-Found Principal Creates Events
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As evident in the authenticator's 'createFoundPrincipal' method (see above),
-a FoundPrincipalCreatedEvent is published when the authenticator finds a
-principal on behalf of PAU's 'getPrincipal'::
-
-  >>> clearEvents()
-  >>> principal = pau.getPrincipal('xyz_white')
-  >>> principal
-  Principal('xyz_white')
-
-  >>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
-  >>> event.principal is principal
-  True
-  >>> event.info
-  PrincipalInfo('white')
-
-The info has an authenticatorPlugin, but no credentialsPlugin, since none was
-used::
-
-  >>> event.info.credentialsPlugin is None
-  True
-  >>> event.info.authenticatorPlugin is searchable
-  True
-
-As we have seen with authenticated principals, it is common to subscribe to
-principal created events to add information to the newly created principal.
-In this case, we need to subscribe to IFoundPrincipalCreated events::
-
-  >>> subscribe([interfaces.IFoundPrincipalCreated], None, add_info)
-
-Now when a principal is created as a result of a search, it's title and
-description will be set (by the add_info handler function).
-
-Multiple Authenticator Plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As with the other operations we've seen, the PAU uses multiple plugins to
-find a principal. If the first authenticator plugin can't find the requested
-principal, the next plugin is used, and so on.
-
-To illustrate, we'll create and register a second searchable authenticator::
-
-  >>> searchable2 = SearchableAuthenticatorPlugin()
-  >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2')
-
-and add a principal to it::
-
-  >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite')
-
-When we configure the PAU to use both searchable authenticators (note the
-order)::
-
-  >>> pau.authenticatorPlugins = (
-  ...     'Searchable Authentication Plugin 2',
-  ...     'Searchable Authentication Plugin')
-
-we see how the PAU uses both plugins::
-
-  >>> pau.getPrincipal('xyz_white')
-  Principal('xyz_white')
-
-  >>> pau.getPrincipal('xyz_black')
-  Principal('xyz_black')
-
-If more than one plugin know about the same principal ID, the first plugin is
-used and the remaining are not delegated to. To illustrate, we'll add
-another principal with the same ID as an existing principal::
-
-  >>> searchable2.add('white', 'White Rider', '', 'r1der')
-  >>> pau.getPrincipal('xyz_white').title
-  'White Rider'
-
-If we change the order of the plugins::
-
-  >>> pau.authenticatorPlugins = (
-  ...     'Searchable Authentication Plugin',
-  ...     'Searchable Authentication Plugin 2')
-
-we get a different principal for ID 'white'::
-
-  >>> pau.getPrincipal('xyz_white').title
-  'White Spy'
-
-
-Issuing a Challenge
--------------------
-
-Part of PAU's IAuthentication contract is to challenge the user for
-credentials when its 'unauthorized' method is called. The need for this
-functionality is driven by the following use case:
-
-  - A user attempts to perform an operation he is not authorized to perform.
-
-  - A handler responds to the unauthorized error by calling IAuthentication
-    'unauthorized'.
-
-  - The authentication component (in our case, a PAU) issues a challenge to
-    the user to collect new credentials (typically in the form of logging in
-    as a new user).
-
-The PAU handles the credentials challenge by delegating to its credentials
-plugins.
-
-Currently, the PAU is configured with the credentials plugins that don't
-perform any action when asked to challenge (see above the 'challenge' methods).
-
-To illustrate challenges, we'll subclass an existing credentials plugin and
-do something in its 'challenge'::
-
-  >>> class LoginFormCredentialsPlugin(FormCredentialsPlugin):
-  ...
-  ...     def __init__(self, loginForm):
-  ...         self.loginForm = loginForm
-  ...
-  ...     def challenge(self, request):
-  ...         request.response.redirect(self.loginForm)
-  ...         return True
-
-This plugin handles a challenge by redirecting the response to a login form.
-It returns True to signal to the PAU that it handled the challenge.
-
-We will now create and register a couple of these plugins::
-
-  >>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'),
-  ...                name='Simple Login Form Plugin')
-
-  >>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'),
-  ...                name='Advanced Login Form Plugin')
-
-and configure the PAU to use them::
-
-  >>> pau.credentialsPlugins = (
-  ...     'Simple Login Form Plugin',
-  ...     'Advanced Login Form Plugin')
-
-Now when we call 'unauthorized' on the PAU::
-
-  >>> request = TestRequest()
-  >>> pau.unauthorized(id=None, request=request)
-
-we see that the user is redirected to the simple login form::
-
-  >>> request.response.getStatus()
-  302
-  >>> request.response.getHeader('location')
-  'simplelogin.html'
-
-We can change the challenge policy by reordering the plugins::
-
-  >>> pau.credentialsPlugins = (
-  ...     'Advanced Login Form Plugin',
-  ...     'Simple Login Form Plugin')
-
-Now when we call 'unauthorized'::
-
-  >>> request = TestRequest()
-  >>> pau.unauthorized(id=None, request=request)
-
-the advanced plugin is used because it's first::
-
-  >>> request.response.getStatus()
-  302
-  >>> request.response.getHeader('location')
-  'advancedlogin.html'
-
-Challenge Protocols
-~~~~~~~~~~~~~~~~~~~
-
-Sometimes, we want multiple challengers to work together. For example, the
-HTTP specification allows multiple challenges to be issued in a response. A
-challenge plugin can provide a `challengeProtocol` attribute that effectively
-groups related plugins together for challenging. If a plugin returns `True`
-from its challenge and provides a non-None challengeProtocol, subsequent
-plugins in the credentialsPlugins list that have the same challenge protocol
-will also be used to challenge.
-
-Without a challengeProtocol, only the first plugin to succeed in a challenge
-will be used.
-
-Let's look at an example. We'll define a new plugin that specifies an
-'X-Challenge' protocol::
-
-  >>> class XChallengeCredentialsPlugin(FormCredentialsPlugin):
-  ...
-  ...     challengeProtocol = 'X-Challenge'
-  ...
-  ...     def __init__(self, challengeValue):
-  ...         self.challengeValue = challengeValue
-  ...
-  ...     def challenge(self, request):
-  ...         value = self.challengeValue
-  ...         existing = request.response.getHeader('X-Challenge', '')
-  ...         if existing:
-  ...             value += ' ' + existing
-  ...         request.response.setHeader('X-Challenge', value)
-  ...         return True
-
-and register a couple instances as utilities::
-
-  >>> provideUtility(XChallengeCredentialsPlugin('basic'),
-  ...                name='Basic X-Challenge Plugin')
-
-  >>> provideUtility(XChallengeCredentialsPlugin('advanced'),
-  ...                name='Advanced X-Challenge Plugin')
-
-When we use both plugins with the PAU::
-
-  >>> pau.credentialsPlugins = (
-  ...     'Basic X-Challenge Plugin',
-  ...     'Advanced X-Challenge Plugin')
-
-and call 'unauthorized'::
-
-  >>> request = TestRequest()
-  >>> pau.unauthorized(None, request)
-
-we see that both plugins participate in the challange, rather than just the
-first plugin::
-
-  >>> request.response.getHeader('X-Challenge')
-  'advanced basic'
-
-
-Pluggable-Authentication Prefixes
----------------------------------
-
-Principal ids are required to be unique system wide. Plugins will often provide
-options for providing id prefixes, so that different sets of plugins provide
-unique ids within a PAU. If there are multiple pluggable-authentication
-utilities in a system, it's a good idea to give each PAU a unique prefix, so
-that principal ids from different PAUs don't conflict. We can provide a prefix
-when a PAU is created::
-
-  >>> pau = authentication.PluggableAuthentication('mypau_')
-  >>> pau.credentialsPlugins = ('My Credentials Plugin', )
-  >>> pau.authenticatorPlugins = ('My Authenticator Plugin', )
-
-When we create a request and try to authenticate::
-
-  >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('mypau_bob')
-
-Note that now, our principal's id has the pluggable-authentication
-utility prefix.
-
-We can still lookup a principal, as long as we supply the prefix::
-
-  >> pau.getPrincipal('mypas_42')
-  Principal('mypas_42', "{'domain': 42}")
-
-  >> pau.getPrincipal('mypas_41')
-  OddPrincipal('mypas_41', "{'int': 41}")
-
-
-Searching
----------
-
-PAU implements ISourceQueriables::
-
-  >>> from zope.schema.interfaces import ISourceQueriables
-  >>> ISourceQueriables.providedBy(pau)
-  True
-
-This means a PAU can be used in a principal source vocabulary (Zope provides a
-sophisticated searching UI for principal sources).
-
-As we've seen, a PAU uses each of its authenticator plugins to locate a
-principal with a given ID. However, plugins may also provide the interface
-IQuerySchemaSearch to indicate they can be used in the PAU's principal search
-scheme.
-
-Currently, our list of authenticators::
-
-  >>> pau.authenticatorPlugins
-  ('My Authenticator Plugin',)
-
-does not include a queriable authenticator. PAU cannot therefore provide any
-queriables::
-
-  >>> list(pau.getQueriables())
-  []
-
-Before we illustrate how an authenticator is used by the PAU to search for
-principals, we need to setup an adapter used by PAU::
-
-  >>> provideAdapter(
-  ...     authentication.authentication.QuerySchemaSearchAdapter,
-  ...     provides=interfaces.IQueriableAuthenticator)
-
-This adapter delegates search responsibility to an authenticator, but prepends
-the PAU prefix to any principal IDs returned in a search.
-
-Next, we'll create a plugin that provides a search interface::
-
-  >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin):
-  ...
-  ...     interface.implements(interfaces.IQuerySchemaSearch)
-  ...
-  ...     schema = None
-  ...
-  ...     def search(self, query, start=None, batch_size=None):
-  ...         yield 'foo'
-  ...
-
-and install it as a plugin::
-
-  >>> plugin = QueriableAuthenticatorPlugin()
-  >>> provideUtility(plugin,
-  ...                provides=interfaces.IAuthenticatorPlugin,
-  ...                name='Queriable')
-  >>> pau.authenticatorPlugins += ('Queriable',)
-
-Now, the PAU provides a single queriable::
-
-  >>> list(pau.getQueriables()) # doctest: +ELLIPSIS
-  [('Queriable', ...QuerySchemaSearchAdapter object...)]
-
-We can use this queriable to search for our principal::
-
-  >>> queriable = list(pau.getQueriables())[0][1]
-  >>> list(queriable.search('not-used'))
-  ['mypau_foo']
-
-Note that the resulting principal ID includes the PAU prefix. Were we to search
-the plugin directly::
-
-  >>> list(plugin.search('not-used'))
-  ['foo']
-
-The result does not include the PAU prefix. The prepending of the prefix is
-handled by the PluggableAuthenticationQueriable.
-
-
-Queryiable plugins can provide the ILocation interface. In this case the
-QuerySchemaSearchAdapter's __parent__ is the same as the __parent__ of the
-plugin::
-
-  >>> import zope.location.interfaces
-  >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin):
-  ...
-  ...     interface.implements(zope.location.interfaces.ILocation)
-  ...
-  ...     __parent__ = __name__ = None
-  ...
-  >>> import zope.app.component.hooks
-  >>> site = zope.app.component.hooks.getSite()
-  >>> plugin = LocatedQueriableAuthenticatorPlugin()
-  >>> plugin.__parent__ = site
-  >>> plugin.__name__ = 'localname'
-  >>> provideUtility(plugin,
-  ...                provides=interfaces.IAuthenticatorPlugin,
-  ...                name='location-queriable')
-  >>> pau.authenticatorPlugins = ('location-queriable',)
-
-We have one queriable again::
-
-  >>> queriables = list(pau.getQueriables())
-  >>> queriables  # doctest: +ELLIPSIS
-  [('location-queriable', ...QuerySchemaSearchAdapter object...)]
-
-The queriable's __parent__ is the site as set above::
-
-  >>> queriable = queriables[0][1]
-  >>> queriable.__parent__ is site
-  True
-
-If the queriable provides ILocation but is not actually locatable (i.e. the
-parent is None) the pau itself becomes the parent::
-
-
-  >>> plugin = LocatedQueriableAuthenticatorPlugin()
-  >>> provideUtility(plugin,
-  ...                provides=interfaces.IAuthenticatorPlugin,
-  ...                name='location-queriable-wo-parent')
-  >>> pau.authenticatorPlugins = ('location-queriable-wo-parent',)
-
-We have one queriable again::
-
-  >>> queriables = list(pau.getQueriables())
-  >>> queriables  # doctest: +ELLIPSIS
-  [('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)]
-
-And the parent is the pau::
-
-  >>> queriable = queriables[0][1]
-  >>> queriable.__parent__  # doctest: +ELLIPSIS
-  <zope.app.authentication.authentication.PluggableAuthentication object ...>
-  >>> queriable.__parent__ is pau
-  True

Copied: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt (from rev 97573, zope.app.authentication/trunk/src/zope/app/authentication/README.txt)
===================================================================
--- zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt	                        (rev 0)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/README.txt	2009-03-06 13:11:03 UTC (rev 97574)
@@ -0,0 +1,822 @@
+================================
+Pluggable-Authentication Utility
+================================
+
+The Pluggable-Authentication Utility (PAU) provides a framework for
+authenticating principals and associating information with them. It uses
+plugins and subscribers to get its work done.
+
+For a pluggable-authentication utility to be used, it should be
+registered as a utility providing the
+`zope.app.security.interfaces.IAuthentication` interface.
+
+Authentication
+--------------
+
+The primary job of PAU is to authenticate principals. It uses two types of
+plug-ins in its work:
+
+  - Credentials Plugins
+
+  - Authenticator Plugins
+
+Credentials plugins are responsible for extracting user credentials from a
+request. A credentials plugin may in some cases issue a 'challenge' to obtain
+credentials. For example, a 'session' credentials plugin reads credentials
+from a session (the "extraction"). If it cannot find credentials, it will
+redirect the user to a login form in order to provide them (the "challenge").
+
+Authenticator plugins are responsible for authenticating the credentials
+extracted by a credentials plugin. They are also typically able to create
+principal objects for credentials they successfully authenticate.
+
+Given a request object, the PAU returns a principal object, if it can. The PAU
+does this by first iterateing through its credentials plugins to obtain a
+set of credentials. If it gets credentials, it iterates through its
+authenticator plugins to authenticate them.
+
+If an authenticator succeeds in authenticating a set of credentials, the PAU
+uses the authenticator to create a principal corresponding to the credentials.
+The authenticator notifies subscribers if an authenticated principal is
+created. Subscribers are responsible for adding data, especially groups, to
+the principal. Typically, if a subscriber adds data, it should also add
+corresponding interface declarations.
+
+Simple Credentials Plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To illustrate, we'll create a simple credentials plugin::
+
+  >>> from zope import interface
+  >>> from zope.app.authentication import interfaces
+
+  >>> class MyCredentialsPlugin(object):
+  ...
+  ...     interface.implements(interfaces.ICredentialsPlugin)
+  ...
+  ...     def extractCredentials(self, request):
+  ...         return request.get('credentials')
+  ...
+  ...     def challenge(self, request):
+  ...         pass # challenge is a no-op for this plugin
+  ...
+  ...     def logout(self, request):
+  ...         pass # logout is a no-op for this plugin
+
+As a plugin, MyCredentialsPlugin needs to be registered as a named utility::
+
+  >>> myCredentialsPlugin = MyCredentialsPlugin()
+  >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin')
+
+Simple Authenticator Plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Next we'll create a simple authenticator plugin. For our plugin, we'll need
+an implementation of IPrincipalInfo::
+
+  >>> class PrincipalInfo(object):
+  ...
+  ...     interface.implements(interfaces.IPrincipalInfo)
+  ...
+  ...     def __init__(self, id, title, description):
+  ...         self.id = id
+  ...         self.title = title
+  ...         self.description = description
+  ...
+  ...     def __repr__(self):
+  ...         return 'PrincipalInfo(%r)' % self.id
+
+Our authenticator uses this type when it creates a principal info::
+
+  >>> class MyAuthenticatorPlugin(object):
+  ...
+  ...     interface.implements(interfaces.IAuthenticatorPlugin)
+  ...
+  ...     def authenticateCredentials(self, credentials):
+  ...         if credentials == 'secretcode':
+  ...             return PrincipalInfo('bob', 'Bob', '')
+  ...
+  ...     def principalInfo(self, id):
+  ...         pass # plugin not currently supporting search
+
+As with the credentials plugin, the authenticator plugin must be registered
+as a named utility::
+
+  >>> myAuthenticatorPlugin = MyAuthenticatorPlugin()
+  >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin')
+
+Principal Factories
+~~~~~~~~~~~~~~~~~~~
+
+While authenticator plugins provide principal info, they are not responsible
+for creating principals. This function is performed by factory adapters. For
+these tests we'll borrow some factories from the principal folder::
+
+  >>> from zope.app.authentication import principalfolder
+  >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory)
+  >>> provideAdapter(principalfolder.FoundPrincipalFactory)
+
+For more information on these factories, see their docstrings.
+
+Configuring a PAU
+~~~~~~~~~~~~~~~~~
+
+Finally, we'll create the PAU itself::
+
+  >>> from zope.app import authentication
+  >>> pau = authentication.PluggableAuthentication('xyz_')
+
+and configure it with the two plugins::
+
+  >>> pau.credentialsPlugins = ('My Credentials Plugin', )
+  >>> pau.authenticatorPlugins = ('My Authenticator Plugin', )
+
+Using the PAU to Authenticate
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We can now use the PAU to authenticate a sample request::
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> print pau.authenticate(TestRequest())
+  None
+
+In this case, we cannot authenticate an empty request. In the same way, we
+will not be able to authenticate a request with the wrong credentials::
+
+  >>> print pau.authenticate(TestRequest(credentials='let me in!'))
+  None
+
+However, if we provide the proper credentials::
+
+  >>> request = TestRequest(credentials='secretcode')
+  >>> principal = pau.authenticate(request)
+  >>> principal
+  Principal('xyz_bob')
+
+we get an authenticated principal.
+
+Authenticated Principal Creates Events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We can verify that the appropriate event was published::
+
+  >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
+  >>> event.principal is principal
+  True
+  >>> event.info
+  PrincipalInfo('bob')
+  >>> event.request is request
+  True
+
+The info object has the id, title, and description of the principal.  The info
+object is also generated by the authenticator plugin, so the plugin may
+itself have provided additional information on the info object::
+
+  >>> event.info.title
+  'Bob'
+  >>> event.info.id # does not include pau prefix
+  'bob'
+  >>> event.info.description
+  ''
+
+It is also decorated with two other attributes, credentialsPlugin and
+authenticatorPlugin: these are the plugins used to extract credentials for and
+authenticate this principal.  These attributes can be useful for subscribers
+that want to react to the plugins used.  For instance, subscribers can
+determine that a given credential plugin does or does not support logout, and
+provide information usable to show or hide logout user interface::
+
+  >>> event.info.credentialsPlugin is myCredentialsPlugin
+  True
+  >>> event.info.authenticatorPlugin is myAuthenticatorPlugin
+  True
+
+Normally, we provide subscribers to these events that add additional
+information to the principal. For example, we'll add one that sets
+the title::
+
+  >>> def add_info(event):
+  ...     event.principal.title = event.info.title
+  >>> subscribe([interfaces.IAuthenticatedPrincipalCreated], None, add_info)
+
+Now, if we authenticate a principal, its title is set::
+
+  >>> principal = pau.authenticate(request)
+  >>> principal.title
+  'Bob'
+
+Multiple Authenticator Plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The PAU works with multiple authenticator plugins. It uses each plugin, in the
+order specified in the PAU's authenticatorPlugins attribute, to authenticate
+a set of credentials.
+
+To illustrate, we'll create another authenticator::
+
+  >>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin):
+  ...
+  ...     def authenticateCredentials(self, credentials):
+  ...         if credentials == 'secretcode':
+  ...             return PrincipalInfo('black', 'Black Spy', '')
+  ...         elif credentials == 'hiddenkey':
+  ...             return PrincipalInfo('white', 'White Spy', '')
+
+  >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2')
+
+If we put it before the original authenticator::
+
+  >>> pau.authenticatorPlugins = (
+  ...     'My Authenticator Plugin 2',
+  ...     'My Authenticator Plugin')
+
+Then it will be given the first opportunity to authenticate a request::
+
+  >>> pau.authenticate(TestRequest(credentials='secretcode'))
+  Principal('xyz_black')
+
+If neither plugins can authenticate, pau returns None::
+
+  >>> print pau.authenticate(TestRequest(credentials='let me in!!'))
+  None
+
+When we change the order of the authenticator plugins::
+
+  >>> pau.authenticatorPlugins = (
+  ...     'My Authenticator Plugin',
+  ...     'My Authenticator Plugin 2')
+
+we see that our original plugin is now acting first::
+
+  >>> pau.authenticate(TestRequest(credentials='secretcode'))
+  Principal('xyz_bob')
+
+The second plugin, however, gets a chance to authenticate if first does not::
+
+  >>> pau.authenticate(TestRequest(credentials='hiddenkey'))
+  Principal('xyz_white')
+
+Multiple Credentials Plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As with with authenticators, we can specify multiple credentials plugins. To
+illustrate, we'll create a credentials plugin that extracts credentials from
+a request form::
+
+  >>> class FormCredentialsPlugin:
+  ...
+  ...     interface.implements(interfaces.ICredentialsPlugin)
+  ...
+  ...     def extractCredentials(self, request):
+  ...         return request.form.get('my_credentials')
+  ...
+  ...     def challenge(self, request):
+  ...         pass
+  ...
+  ...     def logout(request):
+  ...         pass
+
+  >>> provideUtility(FormCredentialsPlugin(),
+  ...                name='Form Credentials Plugin')
+
+and insert the new credentials plugin before the existing plugin::
+
+  >>> pau.credentialsPlugins = (
+  ...     'Form Credentials Plugin',
+  ...     'My Credentials Plugin')
+
+The PAU will use each plugin in order to try and obtain credentials from a
+request::
+
+  >>> pau.authenticate(TestRequest(credentials='secretcode',
+  ...                              form={'my_credentials': 'hiddenkey'}))
+  Principal('xyz_white')
+
+In this case, the first credentials plugin succeeded in getting credentials
+from the form and the second authenticator was able to authenticate the
+credentials. Specifically, the PAU went through these steps:
+
+ - Get credentials using 'Form Credentials Plugin'
+
+ - Got 'hiddenkey' credentials using 'Form Credentials Plugin', try to
+   authenticate using 'My Authenticator Plugin'
+
+ - Failed to authenticate 'hiddenkey' with 'My Authenticator Plugin', try
+   'My Authenticator Plugin 2'
+
+ - Succeeded in authenticating with 'My Authenticator Plugin 2'
+
+Let's try a different scenario::
+
+  >>> pau.authenticate(TestRequest(credentials='secretcode'))
+  Principal('xyz_bob')
+
+In this case, the PAU went through these steps::
+
+  - Get credentials using 'Form Credentials Plugin'
+
+  - Failed to get credentials using 'Form Credentials Plugin', try
+    'My Credentials Plugin'
+
+  - Got 'scecretcode' credentials using 'My Credentials Plugin', try to
+    authenticate using 'My Authenticator Plugin'
+
+  - Succeeded in authenticating with 'My Authenticator Plugin'
+
+Let's try a slightly more complex scenario::
+
+  >>> pau.authenticate(TestRequest(credentials='hiddenkey',
+  ...                              form={'my_credentials': 'bogusvalue'}))
+  Principal('xyz_white')
+
+This highlights PAU's ability to use multiple plugins for authentication:
+
+  - Get credentials using 'Form Credentials Plugin'
+
+  - Got 'bogusvalue' credentials using 'Form Credentials Plugin', try to
+    authenticate using 'My Authenticator Plugin'
+
+  - Failed to authenticate 'boguskey' with 'My Authenticator Plugin', try
+    'My Authenticator Plugin 2'
+
+  - Failed to authenticate 'boguskey' with 'My Authenticator Plugin 2' --
+    there are no more authenticators to try, so lets try the next credentials
+    plugin for some new credentials
+
+  - Get credentials using 'My Credentials Plugin'
+
+  - Got 'hiddenkey' credentials using 'My Credentials Plugin', try to
+    authenticate using 'My Authenticator Plugin'
+
+  - Failed to authenticate 'hiddenkey' using 'My Authenticator Plugin', try
+    'My Authenticator Plugin 2'
+
+  - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and
+    cheers!)
+
+
+Principal Searching
+-------------------
+
+As a component that provides IAuthentication, a PAU lets you lookup a
+principal with a principal ID. The PAU looks up a principal by delegating to
+its authenticators. In our example, none of the authenticators implement this
+search capability, so when we look for a principal::
+
+  >>> print pau.getPrincipal('xyz_bob')
+  Traceback (most recent call last):
+  PrincipalLookupError: bob
+
+  >>> print pau.getPrincipal('white')
+  Traceback (most recent call last):
+  PrincipalLookupError: white
+
+  >>> print pau.getPrincipal('black')
+  Traceback (most recent call last):
+  PrincipalLookupError: black
+
+For a PAU to support search, it needs to be configured with one or more
+authenticator plugins that support search. To illustrate, we'll create a new
+authenticator::
+
+  >>> class SearchableAuthenticatorPlugin:
+  ...
+  ...     interface.implements(interfaces.IAuthenticatorPlugin)
+  ...
+  ...     def __init__(self):
+  ...         self.infos = {}
+  ...         self.ids = {}
+  ...
+  ...     def principalInfo(self, id):
+  ...         return self.infos.get(id)
+  ...
+  ...     def authenticateCredentials(self, credentials):
+  ...         id = self.ids.get(credentials)
+  ...         if id is not None:
+  ...             return self.infos[id]
+  ...
+  ...     def add(self, id, title, description, credentials):
+  ...         self.infos[id] = PrincipalInfo(id, title, description)
+  ...         self.ids[credentials] = id
+
+This class is typical of an authenticator plugin. It can both authenticate
+principals and find principals given a ID. While there are cases
+where an authenticator may opt to not perform one of these two functions, they
+are less typical.
+
+As with any plugin, we need to register it as a utility::
+
+  >>> searchable = SearchableAuthenticatorPlugin()
+  >>> provideUtility(searchable, name='Searchable Authentication Plugin')
+
+We'll now configure the PAU to use only the searchable authenticator::
+
+  >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',)
+
+and add some principals to the authenticator::
+
+  >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b')
+  >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack')
+
+Now when we ask the PAU to find a principal::
+
+  >>> pau.getPrincipal('xyz_bob')
+  Principal('xyz_bob')
+
+but only those it knows about::
+
+  >>> print pau.getPrincipal('black')
+  Traceback (most recent call last):
+  PrincipalLookupError: black
+
+Found Principal Creates Events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As evident in the authenticator's 'createFoundPrincipal' method (see above),
+a FoundPrincipalCreatedEvent is published when the authenticator finds a
+principal on behalf of PAU's 'getPrincipal'::
+
+  >>> clearEvents()
+  >>> principal = pau.getPrincipal('xyz_white')
+  >>> principal
+  Principal('xyz_white')
+
+  >>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
+  >>> event.principal is principal
+  True
+  >>> event.info
+  PrincipalInfo('white')
+
+The info has an authenticatorPlugin, but no credentialsPlugin, since none was
+used::
+
+  >>> event.info.credentialsPlugin is None
+  True
+  >>> event.info.authenticatorPlugin is searchable
+  True
+
+As we have seen with authenticated principals, it is common to subscribe to
+principal created events to add information to the newly created principal.
+In this case, we need to subscribe to IFoundPrincipalCreated events::
+
+  >>> subscribe([interfaces.IFoundPrincipalCreated], None, add_info)
+
+Now when a principal is created as a result of a search, it's title and
+description will be set (by the add_info handler function).
+
+Multiple Authenticator Plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As with the other operations we've seen, the PAU uses multiple plugins to
+find a principal. If the first authenticator plugin can't find the requested
+principal, the next plugin is used, and so on.
+
+To illustrate, we'll create and register a second searchable authenticator::
+
+  >>> searchable2 = SearchableAuthenticatorPlugin()
+  >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2')
+
+and add a principal to it::
+
+  >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite')
+
+When we configure the PAU to use both searchable authenticators (note the
+order)::
+
+  >>> pau.authenticatorPlugins = (
+  ...     'Searchable Authentication Plugin 2',
+  ...     'Searchable Authentication Plugin')
+
+we see how the PAU uses both plugins::
+
+  >>> pau.getPrincipal('xyz_white')
+  Principal('xyz_white')
+
+  >>> pau.getPrincipal('xyz_black')
+  Principal('xyz_black')
+
+If more than one plugin know about the same principal ID, the first plugin is
+used and the remaining are not delegated to. To illustrate, we'll add
+another principal with the same ID as an existing principal::
+
+  >>> searchable2.add('white', 'White Rider', '', 'r1der')
+  >>> pau.getPrincipal('xyz_white').title
+  'White Rider'
+
+If we change the order of the plugins::
+
+  >>> pau.authenticatorPlugins = (
+  ...     'Searchable Authentication Plugin',
+  ...     'Searchable Authentication Plugin 2')
+
+we get a different principal for ID 'white'::
+
+  >>> pau.getPrincipal('xyz_white').title
+  'White Spy'
+
+
+Issuing a Challenge
+-------------------
+
+Part of PAU's IAuthentication contract is to challenge the user for
+credentials when its 'unauthorized' method is called. The need for this
+functionality is driven by the following use case:
+
+  - A user attempts to perform an operation he is not authorized to perform.
+
+  - A handler responds to the unauthorized error by calling IAuthentication
+    'unauthorized'.
+
+  - The authentication component (in our case, a PAU) issues a challenge to
+    the user to collect new credentials (typically in the form of logging in
+    as a new user).
+
+The PAU handles the credentials challenge by delegating to its credentials
+plugins.
+
+Currently, the PAU is configured with the credentials plugins that don't
+perform any action when asked to challenge (see above the 'challenge' methods).
+
+To illustrate challenges, we'll subclass an existing credentials plugin and
+do something in its 'challenge'::
+
+  >>> class LoginFormCredentialsPlugin(FormCredentialsPlugin):
+  ...
+  ...     def __init__(self, loginForm):
+  ...         self.loginForm = loginForm
+  ...
+  ...     def challenge(self, request):
+  ...         request.response.redirect(self.loginForm)
+  ...         return True
+
+This plugin handles a challenge by redirecting the response to a login form.
+It returns True to signal to the PAU that it handled the challenge.
+
+We will now create and register a couple of these plugins::
+
+  >>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'),
+  ...                name='Simple Login Form Plugin')
+
+  >>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'),
+  ...                name='Advanced Login Form Plugin')
+
+and configure the PAU to use them::
+
+  >>> pau.credentialsPlugins = (
+  ...     'Simple Login Form Plugin',
+  ...     'Advanced Login Form Plugin')
+
+Now when we call 'unauthorized' on the PAU::
+
+  >>> request = TestRequest()
+  >>> pau.unauthorized(id=None, request=request)
+
+we see that the user is redirected to the simple login form::
+
+  >>> request.response.getStatus()
+  302
+  >>> request.response.getHeader('location')
+  'simplelogin.html'
+
+We can change the challenge policy by reordering the plugins::
+
+  >>> pau.credentialsPlugins = (
+  ...     'Advanced Login Form Plugin',
+  ...     'Simple Login Form Plugin')
+
+Now when we call 'unauthorized'::
+
+  >>> request = TestRequest()
+  >>> pau.unauthorized(id=None, request=request)
+
+the advanced plugin is used because it's first::
+
+  >>> request.response.getStatus()
+  302
+  >>> request.response.getHeader('location')
+  'advancedlogin.html'
+
+Challenge Protocols
+~~~~~~~~~~~~~~~~~~~
+
+Sometimes, we want multiple challengers to work together. For example, the
+HTTP specification allows multiple challenges to be issued in a response. A
+challenge plugin can provide a `challengeProtocol` attribute that effectively
+groups related plugins together for challenging. If a plugin returns `True`
+from its challenge and provides a non-None challengeProtocol, subsequent
+plugins in the credentialsPlugins list that have the same challenge protocol
+will also be used to challenge.
+
+Without a challengeProtocol, only the first plugin to succeed in a challenge
+will be used.
+
+Let's look at an example. We'll define a new plugin that specifies an
+'X-Challenge' protocol::
+
+  >>> class XChallengeCredentialsPlugin(FormCredentialsPlugin):
+  ...
+  ...     challengeProtocol = 'X-Challenge'
+  ...
+  ...     def __init__(self, challengeValue):
+  ...         self.challengeValue = challengeValue
+  ...
+  ...     def challenge(self, request):
+  ...         value = self.challengeValue
+  ...         existing = request.response.getHeader('X-Challenge', '')
+  ...         if existing:
+  ...             value += ' ' + existing
+  ...         request.response.setHeader('X-Challenge', value)
+  ...         return True
+
+and register a couple instances as utilities::
+
+  >>> provideUtility(XChallengeCredentialsPlugin('basic'),
+  ...                name='Basic X-Challenge Plugin')
+
+  >>> provideUtility(XChallengeCredentialsPlugin('advanced'),
+  ...                name='Advanced X-Challenge Plugin')
+
+When we use both plugins with the PAU::
+
+  >>> pau.credentialsPlugins = (
+  ...     'Basic X-Challenge Plugin',
+  ...     'Advanced X-Challenge Plugin')
+
+and call 'unauthorized'::
+
+  >>> request = TestRequest()
+  >>> pau.unauthorized(None, request)
+
+we see that both plugins participate in the challange, rather than just the
+first plugin::
+
+  >>> request.response.getHeader('X-Challenge')
+  'advanced basic'
+
+
+Pluggable-Authentication Prefixes
+---------------------------------
+
+Principal ids are required to be unique system wide. Plugins will often provide
+options for providing id prefixes, so that different sets of plugins provide
+unique ids within a PAU. If there are multiple pluggable-authentication
+utilities in a system, it's a good idea to give each PAU a unique prefix, so
+that principal ids from different PAUs don't conflict. We can provide a prefix
+when a PAU is created::
+
+  >>> pau = authentication.PluggableAuthentication('mypau_')
+  >>> pau.credentialsPlugins = ('My Credentials Plugin', )
+  >>> pau.authenticatorPlugins = ('My Authenticator Plugin', )
+
+When we create a request and try to authenticate::
+
+  >>> pau.authenticate(TestRequest(credentials='secretcode'))
+  Principal('mypau_bob')
+
+Note that now, our principal's id has the pluggable-authentication
+utility prefix.
+
+We can still lookup a principal, as long as we supply the prefix::
+
+  >> pau.getPrincipal('mypas_42')
+  Principal('mypas_42', "{'domain': 42}")
+
+  >> pau.getPrincipal('mypas_41')
+  OddPrincipal('mypas_41', "{'int': 41}")
+
+
+Searching
+---------
+
+PAU implements ISourceQueriables::
+
+  >>> from zope.schema.interfaces import ISourceQueriables
+  >>> ISourceQueriables.providedBy(pau)
+  True
+
+This means a PAU can be used in a principal source vocabulary (Zope provides a
+sophisticated searching UI for principal sources).
+
+As we've seen, a PAU uses each of its authenticator plugins to locate a
+principal with a given ID. However, plugins may also provide the interface
+IQuerySchemaSearch to indicate they can be used in the PAU's principal search
+scheme.
+
+Currently, our list of authenticators::
+
+  >>> pau.authenticatorPlugins
+  ('My Authenticator Plugin',)
+
+does not include a queriable authenticator. PAU cannot therefore provide any
+queriables::
+
+  >>> list(pau.getQueriables())
+  []
+
+Before we illustrate how an authenticator is used by the PAU to search for
+principals, we need to setup an adapter used by PAU::
+
+  >>> provideAdapter(
+  ...     authentication.authentication.QuerySchemaSearchAdapter,
+  ...     provides=interfaces.IQueriableAuthenticator)
+
+This adapter delegates search responsibility to an authenticator, but prepends
+the PAU prefix to any principal IDs returned in a search.
+
+Next, we'll create a plugin that provides a search interface::
+
+  >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin):
+  ...
+  ...     interface.implements(interfaces.IQuerySchemaSearch)
+  ...
+  ...     schema = None
+  ...
+  ...     def search(self, query, start=None, batch_size=None):
+  ...         yield 'foo'
+  ...
+
+and install it as a plugin::
+
+  >>> plugin = QueriableAuthenticatorPlugin()
+  >>> provideUtility(plugin,
+  ...                provides=interfaces.IAuthenticatorPlugin,
+  ...                name='Queriable')
+  >>> pau.authenticatorPlugins += ('Queriable',)
+
+Now, the PAU provides a single queriable::
+
+  >>> list(pau.getQueriables()) # doctest: +ELLIPSIS
+  [('Queriable', ...QuerySchemaSearchAdapter object...)]
+
+We can use this queriable to search for our principal::
+
+  >>> queriable = list(pau.getQueriables())[0][1]
+  >>> list(queriable.search('not-used'))
+  ['mypau_foo']
+
+Note that the resulting principal ID includes the PAU prefix. Were we to search
+the plugin directly::
+
+  >>> list(plugin.search('not-used'))
+  ['foo']
+
+The result does not include the PAU prefix. The prepending of the prefix is
+handled by the PluggableAuthenticationQueriable.
+
+
+Queryiable plugins can provide the ILocation interface. In this case the
+QuerySchemaSearchAdapter's __parent__ is the same as the __parent__ of the
+plugin::
+
+  >>> import zope.location.interfaces
+  >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin):
+  ...
+  ...     interface.implements(zope.location.interfaces.ILocation)
+  ...
+  ...     __parent__ = __name__ = None
+  ...
+  >>> import zope.site.hooks
+  >>> site = zope.site.hooks.getSite()
+  >>> plugin = LocatedQueriableAuthenticatorPlugin()
+  >>> plugin.__parent__ = site
+  >>> plugin.__name__ = 'localname'
+  >>> provideUtility(plugin,
+  ...                provides=interfaces.IAuthenticatorPlugin,
+  ...                name='location-queriable')
+  >>> pau.authenticatorPlugins = ('location-queriable',)
+
+We have one queriable again::
+
+  >>> queriables = list(pau.getQueriables())
+  >>> queriables  # doctest: +ELLIPSIS
+  [('location-queriable', ...QuerySchemaSearchAdapter object...)]
+
+The queriable's __parent__ is the site as set above::
+
+  >>> queriable = queriables[0][1]
+  >>> queriable.__parent__ is site
+  True
+
+If the queriable provides ILocation but is not actually locatable (i.e. the
+parent is None) the pau itself becomes the parent::
+
+
+  >>> plugin = LocatedQueriableAuthenticatorPlugin()
+  >>> provideUtility(plugin,
+  ...                provides=interfaces.IAuthenticatorPlugin,
+  ...                name='location-queriable-wo-parent')
+  >>> pau.authenticatorPlugins = ('location-queriable-wo-parent',)
+
+We have one queriable again::
+
+  >>> queriables = list(pau.getQueriables())
+  >>> queriables  # doctest: +ELLIPSIS
+  [('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)]
+
+And the parent is the pau::
+
+  >>> queriable = queriables[0][1]
+  >>> queriable.__parent__  # doctest: +ELLIPSIS
+  <zope.app.authentication.authentication.PluggableAuthentication object ...>
+  >>> queriable.__parent__ is pau
+  True

Deleted: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py
===================================================================
--- zope.app.authentication/trunk/src/zope/app/authentication/authentication.py	2009-03-06 13:03:45 UTC (rev 97572)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py	2009-03-06 13:11:03 UTC (rev 97574)
@@ -1,186 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Pluggable Authentication Utility implementation
-
-$Id$
-"""
-import zope.interface
-from zope import component
-from zope.schema.interfaces import ISourceQueriables
-from zope.location.interfaces import ILocation
-
-from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
-from zope.app.component import queryNextUtility
-import zope.container.btree
-
-from zope.app.authentication import interfaces
-
-class PluggableAuthentication(zope.container.btree.BTreeContainer):
-
-    zope.interface.implements(
-        IAuthentication,
-        interfaces.IPluggableAuthentication,
-        ISourceQueriables)
-
-    authenticatorPlugins = ()
-    credentialsPlugins = ()
-
-    def __init__(self, prefix=''):
-        super(PluggableAuthentication, self).__init__()
-        self.prefix = prefix
-
-    @property
-    def registrationManager(self):
-        import warnings
-        warnings.warn(
-            "The registration manager is deprecated and will go away in "
-            "Zope 3.5.  Use the simpler registration api (registerUtility) "
-            "instead.",
-            DeprecationWarning, 2)
-        return self.__parent__.registrationManager
-
-    def _plugins(self, names, interface):
-        for name in names:
-            plugin = self.get(name)
-            if not interface.providedBy(plugin):
-                plugin = component.queryUtility(interface, name, context=self)
-            if plugin is not None:
-                yield name, plugin
-
-    def getAuthenticatorPlugins(self):
-        return self._plugins(
-            self.authenticatorPlugins, interfaces.IAuthenticatorPlugin)
-
-    def getCredentialsPlugins(self):
-        return self._plugins(
-            self.credentialsPlugins, interfaces.ICredentialsPlugin)
-
-    def authenticate(self, request):
-        authenticatorPlugins = [p for n, p in self.getAuthenticatorPlugins()]
-        for name, credplugin in self.getCredentialsPlugins():
-            credentials = credplugin.extractCredentials(request)
-            for authplugin in authenticatorPlugins:
-                if authplugin is None:
-                    continue
-                info = authplugin.authenticateCredentials(credentials)
-                if info is None:
-                    continue
-                info.credentialsPlugin = credplugin
-                info.authenticatorPlugin = authplugin
-                principal = component.getMultiAdapter((info, request),
-                    interfaces.IAuthenticatedPrincipalFactory)(self)
-                principal.id = self.prefix + info.id
-                return principal
-        return None
-
-    def getPrincipal(self, id):
-        if not id.startswith(self.prefix):
-            next = queryNextUtility(self, IAuthentication)
-            if next is None:
-                raise PrincipalLookupError(id)
-            return next.getPrincipal(id)
-        id = id[len(self.prefix):]
-        for name, authplugin in self.getAuthenticatorPlugins():
-            info = authplugin.principalInfo(id)
-            if info is None:
-                continue
-            info.credentialsPlugin = None
-            info.authenticatorPlugin = authplugin
-            principal = interfaces.IFoundPrincipalFactory(info)(self)
-            principal.id = self.prefix + info.id
-            return principal
-        next = queryNextUtility(self, IAuthentication)
-        if next is not None:
-            return next.getPrincipal(self.prefix + id)
-        raise PrincipalLookupError(id)
-
-    def getQueriables(self):
-        for name, authplugin in self.getAuthenticatorPlugins():
-            queriable = component.queryMultiAdapter((authplugin, self),
-                interfaces.IQueriableAuthenticator)
-            if queriable is not None:
-                yield name, queriable
-
-    def unauthenticatedPrincipal(self):
-        return None
-
-    def unauthorized(self, id, request):
-        challengeProtocol = None
-
-        for name, credplugin in self.getCredentialsPlugins():
-            protocol = getattr(credplugin, 'challengeProtocol', None)
-            if challengeProtocol is None or protocol == challengeProtocol:
-                if credplugin.challenge(request):
-                    if protocol is None:
-                        return
-                    elif challengeProtocol is None:
-                        challengeProtocol = protocol
-
-        if challengeProtocol is None:
-            next = queryNextUtility(self, IAuthentication)
-            if next is not None:
-                next.unauthorized(id, request)
-
-    def logout(self, request):
-        challengeProtocol = None
-
-        for name, credplugin in self.getCredentialsPlugins():
-            protocol = getattr(credplugin, 'challengeProtocol', None)
-            if challengeProtocol is None or protocol == challengeProtocol:
-                if credplugin.logout(request):
-                    if protocol is None:
-                        return
-                    elif challengeProtocol is None:
-                        challengeProtocol = protocol
-
-        if challengeProtocol is None:
-            next = queryNextUtility(self, IAuthentication)
-            if next is not None:
-                next.logout(request)
-
-
-class QuerySchemaSearchAdapter(object):
-    """Performs schema-based principal searches on behalf of a PAU.
-
-    Delegates the search to the adapted authenticator (which also provides
-    IQuerySchemaSearch) and prepends the PAU prefix to the resulting principal
-    IDs.
-    """
-    component.adapts(
-        interfaces.IQuerySchemaSearch,
-        interfaces.IPluggableAuthentication)
-
-    zope.interface.implements(
-        interfaces.IQueriableAuthenticator,
-        interfaces.IQuerySchemaSearch,
-        ILocation)
-
-    def __init__(self, authplugin, pau):
-        if (ILocation.providedBy(authplugin) and
-            authplugin.__parent__ is not None):
-            # Checking explicitly for the parent, because providing ILocation
-            # basically means that the object *could* be located. It doesn't
-            # say the object must be located.
-            self.__parent__ = authplugin.__parent__
-            self.__name__ = authplugin.__name__
-        else:
-            self.__parent__ = pau
-            self.__name__ = ""
-        self.authplugin = authplugin
-        self.pau = pau
-        self.schema = authplugin.schema
-
-    def search(self, query, start=None, batch_size=None):
-        for id in self.authplugin.search(query, start, batch_size):
-            yield self.pau.prefix + id

Copied: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py (from rev 97573, zope.app.authentication/trunk/src/zope/app/authentication/authentication.py)
===================================================================
--- zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py	                        (rev 0)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/authentication.py	2009-03-06 13:11:03 UTC (rev 97574)
@@ -0,0 +1,186 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Pluggable Authentication Utility implementation
+
+$Id$
+"""
+import zope.interface
+from zope import component
+from zope.schema.interfaces import ISourceQueriables
+from zope.location.interfaces import ILocation
+from zope.site.next import queryNextUtility
+
+from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
+import zope.container.btree
+
+from zope.app.authentication import interfaces
+
+class PluggableAuthentication(zope.container.btree.BTreeContainer):
+
+    zope.interface.implements(
+        IAuthentication,
+        interfaces.IPluggableAuthentication,
+        ISourceQueriables)
+
+    authenticatorPlugins = ()
+    credentialsPlugins = ()
+
+    def __init__(self, prefix=''):
+        super(PluggableAuthentication, self).__init__()
+        self.prefix = prefix
+
+    @property
+    def registrationManager(self):
+        import warnings
+        warnings.warn(
+            "The registration manager is deprecated and will go away in "
+            "Zope 3.5.  Use the simpler registration api (registerUtility) "
+            "instead.",
+            DeprecationWarning, 2)
+        return self.__parent__.registrationManager
+
+    def _plugins(self, names, interface):
+        for name in names:
+            plugin = self.get(name)
+            if not interface.providedBy(plugin):
+                plugin = component.queryUtility(interface, name, context=self)
+            if plugin is not None:
+                yield name, plugin
+
+    def getAuthenticatorPlugins(self):
+        return self._plugins(
+            self.authenticatorPlugins, interfaces.IAuthenticatorPlugin)
+
+    def getCredentialsPlugins(self):
+        return self._plugins(
+            self.credentialsPlugins, interfaces.ICredentialsPlugin)
+
+    def authenticate(self, request):
+        authenticatorPlugins = [p for n, p in self.getAuthenticatorPlugins()]
+        for name, credplugin in self.getCredentialsPlugins():
+            credentials = credplugin.extractCredentials(request)
+            for authplugin in authenticatorPlugins:
+                if authplugin is None:
+                    continue
+                info = authplugin.authenticateCredentials(credentials)
+                if info is None:
+                    continue
+                info.credentialsPlugin = credplugin
+                info.authenticatorPlugin = authplugin
+                principal = component.getMultiAdapter((info, request),
+                    interfaces.IAuthenticatedPrincipalFactory)(self)
+                principal.id = self.prefix + info.id
+                return principal
+        return None
+
+    def getPrincipal(self, id):
+        if not id.startswith(self.prefix):
+            next = queryNextUtility(self, IAuthentication)
+            if next is None:
+                raise PrincipalLookupError(id)
+            return next.getPrincipal(id)
+        id = id[len(self.prefix):]
+        for name, authplugin in self.getAuthenticatorPlugins():
+            info = authplugin.principalInfo(id)
+            if info is None:
+                continue
+            info.credentialsPlugin = None
+            info.authenticatorPlugin = authplugin
+            principal = interfaces.IFoundPrincipalFactory(info)(self)
+            principal.id = self.prefix + info.id
+            return principal
+        next = queryNextUtility(self, IAuthentication)
+        if next is not None:
+            return next.getPrincipal(self.prefix + id)
+        raise PrincipalLookupError(id)
+
+    def getQueriables(self):
+        for name, authplugin in self.getAuthenticatorPlugins():
+            queriable = component.queryMultiAdapter((authplugin, self),
+                interfaces.IQueriableAuthenticator)
+            if queriable is not None:
+                yield name, queriable
+
+    def unauthenticatedPrincipal(self):
+        return None
+
+    def unauthorized(self, id, request):
+        challengeProtocol = None
+
+        for name, credplugin in self.getCredentialsPlugins():
+            protocol = getattr(credplugin, 'challengeProtocol', None)
+            if challengeProtocol is None or protocol == challengeProtocol:
+                if credplugin.challenge(request):
+                    if protocol is None:
+                        return
+                    elif challengeProtocol is None:
+                        challengeProtocol = protocol
+
+        if challengeProtocol is None:
+            next = queryNextUtility(self, IAuthentication)
+            if next is not None:
+                next.unauthorized(id, request)
+
+    def logout(self, request):
+        challengeProtocol = None
+
+        for name, credplugin in self.getCredentialsPlugins():
+            protocol = getattr(credplugin, 'challengeProtocol', None)
+            if challengeProtocol is None or protocol == challengeProtocol:
+                if credplugin.logout(request):
+                    if protocol is None:
+                        return
+                    elif challengeProtocol is None:
+                        challengeProtocol = protocol
+
+        if challengeProtocol is None:
+            next = queryNextUtility(self, IAuthentication)
+            if next is not None:
+                next.logout(request)
+
+
+class QuerySchemaSearchAdapter(object):
+    """Performs schema-based principal searches on behalf of a PAU.
+
+    Delegates the search to the adapted authenticator (which also provides
+    IQuerySchemaSearch) and prepends the PAU prefix to the resulting principal
+    IDs.
+    """
+    component.adapts(
+        interfaces.IQuerySchemaSearch,
+        interfaces.IPluggableAuthentication)
+
+    zope.interface.implements(
+        interfaces.IQueriableAuthenticator,
+        interfaces.IQuerySchemaSearch,
+        ILocation)
+
+    def __init__(self, authplugin, pau):
+        if (ILocation.providedBy(authplugin) and
+            authplugin.__parent__ is not None):
+            # Checking explicitly for the parent, because providing ILocation
+            # basically means that the object *could* be located. It doesn't
+            # say the object must be located.
+            self.__parent__ = authplugin.__parent__
+            self.__name__ = authplugin.__name__
+        else:
+            self.__parent__ = pau
+            self.__name__ = ""
+        self.authplugin = authplugin
+        self.pau = pau
+        self.schema = authplugin.schema
+
+    def search(self, query, start=None, batch_size=None):
+        for id in self.authplugin.search(query, start, batch_size):
+            yield self.pau.prefix + id

Deleted: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt
===================================================================
--- zope.app.authentication/trunk/src/zope/app/authentication/browser/groupfolder.txt	2009-03-06 13:03:45 UTC (rev 97572)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt	2009-03-06 13:11:03 UTC (rev 97574)
@@ -1,659 +0,0 @@
-Using Group Folders
-===================
-
-Group folders are used to define groups.  Before you can define
-groups, you have to create a group folder and configure it in a
-pluggable authentication utility. The group folder has to be
-registered with a pluggable authentication utility before defining any
-groups.  This is because the groups folder needs to use the pluggable
-authentication utility to find all of the groups containing a given
-group so that it can check for group cycles. Not all of a group's
-groups need to be defined in it's group folder. Other groups folders
-or group-defining plugins could define groups for a group.
-
-Let's walk through an example.
-
-First, We need to create and register a pluggable authentication utility.
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/@@contents.html HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 98
-  ... Content-Type: application/x-www-form-urlencoded
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication
-  ...
-  ... type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication&new_value=PAU""")
-  HTTP/1.1 303 See Other
-  ...
-
-  >>> print http(r"""
-  ... GET /++etc++site/default/PAU/@@registration.html HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication
-  ... """)
-  HTTP/1.1 200 OK
-  ...
-
-Register PAU.
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 687
-  ... Content-Type: multipart/form-data; boundary=---------------------------5559795404609280911441883437
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/addRegistration.html
-  ...
-  ... -----------------------------5559795404609280911441883437
-  ... Content-Disposition: form-data; name="field.comment"
-  ...
-  ... 
-  ... -----------------------------5559795404609280911441883437
-  ... Content-Disposition: form-data; name="field.actions.register"
-  ...
-  ... Register
-  ... -----------------------------5559795404609280911441883437--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-Add a Principal folder plugin `users` to PAU.
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/+/AddPrincipalFolder.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 429
-  ... Content-Type: multipart/form-data; boundary=---------------------------95449631112274213651507932125
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/+/AddPrincipalFolder.html=
-  ...
-  ... -----------------------------95449631112274213651507932125
-  ... Content-Disposition: form-data; name="field.prefix"
-  ...
-  ... users
-  ... -----------------------------95449631112274213651507932125
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------95449631112274213651507932125
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ... users
-  ... -----------------------------95449631112274213651507932125--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-Next we will add some users.
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... bob
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Bob
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... bill
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Bill
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... betty
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Betty
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... sally
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Sally
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... george
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... George
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... mike
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Mike
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 780
-  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
-  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.login"
-  ...
-  ... mary
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.passwordManagerName"
-  ...
-  ... Plain Text
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.password"
-  ...
-  ... 123
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Mary
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------5110544421083023415453147877
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ...
-  ... -----------------------------5110544421083023415453147877--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-Next, We'll add out group folder plugin in PAU.
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/+/AddGroupFolder.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 427
-  ... Content-Type: multipart/form-data; boundary=---------------------------4150524541658557772058105275
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/+/AddGroupFolder.html=
-  ...
-  ... -----------------------------4150524541658557772058105275
-  ... Content-Disposition: form-data; name="field.prefix"
-  ...
-  ... groups
-  ... -----------------------------4150524541658557772058105275
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------4150524541658557772058105275
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ... groups
-  ... -----------------------------4150524541658557772058105275--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-Next we'll select the credentials and authenticators for the PAU:
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 1313
-  ... Content-Type: multipart/form-data; boundary=---------------------------2026736768606413562109112352
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/@@configure.html
-  ...
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.credentialsPlugins.to"
-  ...
-  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
-  ...
-  ...
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
-  ...
-  ... dXNlcnM=
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
-  ...
-  ... Z3JvdXBz
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
-  ...
-  ...
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Change
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.credentialsPlugins"
-  ...
-  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
-  ...
-  ... dXNlcnM=
-  ... -----------------------------2026736768606413562109112352
-  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
-  ...
-  ... Z3JvdXBz
-  ... -----------------------------2026736768606413562109112352--
-  ... """)
-  HTTP/1.1 200 OK
-  ...
-
-
-
-Now, we can define some groups.  Let's start with a group named "Admin":
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 550
-  ... Content-Type: multipart/form-data; boundary=---------------------------20619400354342370301249668954
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/+/AddGroupInformation.html=
-  ...
-  ... -----------------------------20619400354342370301249668954
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Admin
-  ... -----------------------------20619400354342370301249668954
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------20619400354342370301249668954
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------20619400354342370301249668954
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ... admin
-  ... -----------------------------20619400354342370301249668954--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-
-That includes Betty, Mary and Mike:
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/groups/admin/@@edit.html HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 1509
-  ... Content-Type: multipart/form-data; boundary=---------------------------6981402699601872602121555350
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/admin/@@edit.html
-  ...
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Admin
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals.displayed"
-  ...
-  ... y
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search"
-  ...
-  ...
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals:list"
-  ...
-  ... dXNlcnMz
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals:list"
-  ...
-  ... dXNlcnM3
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals:list"
-  ...
-  ... dXNlcnM2
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.apply"
-  ...
-  ... Apply
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search"
-  ...
-  ...
-  ... -----------------------------6981402699601872602121555350
-  ... Content-Disposition: form-data; name="field.principals.MQ__.query.searchstring"
-  ...
-  ...
-  ... -----------------------------6981402699601872602121555350--
-  ... """)
-  HTTP/1.1 200 OK
-  ...
-
-
-and a group "Power Users"
-
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 561
-  ... Content-Type: multipart/form-data; boundary=---------------------------168380148515549442351132560943
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/+/AddGroupInformation.html=
-  ...
-  ... -----------------------------168380148515549442351132560943
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Power Users
-  ... -----------------------------168380148515549442351132560943
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------168380148515549442351132560943
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Add
-  ... -----------------------------168380148515549442351132560943
-  ... Content-Disposition: form-data; name="add_input_name"
-  ...
-  ... power
-  ... -----------------------------168380148515549442351132560943--
-  ... """)
-  HTTP/1.1 303 See Other
-  ...
-
-with Bill and Betty as members:
-
-  >>> print http(r"""
-  ... POST /++etc++site/default/PAU/groups/power/@@edit.html HTTP/1.1
-  ... Authorization: Basic bWdyOm1ncnB3
-  ... Content-Length: 1729
-  ... Content-Type: multipart/form-data; boundary=---------------------------181944013812647128322134918391
-  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/power/@@edit.html
-  ...
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.title"
-  ...
-  ... Power Users
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.description"
-  ...
-  ...
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.principals:list"
-  ...
-  ... dXNlcnMz
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.principals:list"
-  ...
-  ... dXNlcnMy
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.principals.displayed"
-  ...
-  ... y
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search"
-  ...
-  ...
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search"
-  ...
-  ...
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="field.principals.MQ__.query.searchstring"
-  ...
-  ...
-  ... -----------------------------181944013812647128322134918391
-  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ...
-  ... Change
-  ... -----------------------------181944013812647128322134918391--
-  ... """)
-  HTTP/1.1 200 OK
-  ...
-
-Now, with these groups set up, we should see these groups on the
-affected principals.  First, we'll make the root folder the
-thread-local site:
-
-  >>> from zope.app.component.hooks import setSite
-  >>> setSite(getRootFolder())
-
-and we'll get the pluggable authentication utility:
-
-  >>> from zope.component import getUtility
-  >>> from zope.app.security.interfaces import IAuthentication
-  >>> principals = getUtility(IAuthentication)
-
-Finally we'll get Betty and see that she is in the admin and
-power-user groups:
-
-  >>> betty = principals.getPrincipal(u'users3')
-  >>> betty.groups.sort()
-  >>> betty.groups
-  [u'groupspower', 'zope.Authenticated', 'zope.Everybody']
-
-
-And we'll get Bill, and see that he is only in the power-user group:
-
-  >>> bill = principals.getPrincipal(u'users2')
-  >>> bill.groups
-  ['zope.Everybody', 'zope.Authenticated', u'groupspower']

Copied: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt (from rev 97573, zope.app.authentication/trunk/src/zope/app/authentication/browser/groupfolder.txt)
===================================================================
--- zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt	                        (rev 0)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/browser/groupfolder.txt	2009-03-06 13:11:03 UTC (rev 97574)
@@ -0,0 +1,659 @@
+Using Group Folders
+===================
+
+Group folders are used to define groups.  Before you can define
+groups, you have to create a group folder and configure it in a
+pluggable authentication utility. The group folder has to be
+registered with a pluggable authentication utility before defining any
+groups.  This is because the groups folder needs to use the pluggable
+authentication utility to find all of the groups containing a given
+group so that it can check for group cycles. Not all of a group's
+groups need to be defined in it's group folder. Other groups folders
+or group-defining plugins could define groups for a group.
+
+Let's walk through an example.
+
+First, We need to create and register a pluggable authentication utility.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/@@contents.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 98
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication
+  ...
+  ... type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication&new_value=PAU""")
+  HTTP/1.1 303 See Other
+  ...
+
+  >>> print http(r"""
+  ... GET /++etc++site/default/PAU/@@registration.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+Register PAU.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 687
+  ... Content-Type: multipart/form-data; boundary=---------------------------5559795404609280911441883437
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/addRegistration.html
+  ...
+  ... -----------------------------5559795404609280911441883437
+  ... Content-Disposition: form-data; name="field.comment"
+  ...
+  ... 
+  ... -----------------------------5559795404609280911441883437
+  ... Content-Disposition: form-data; name="field.actions.register"
+  ...
+  ... Register
+  ... -----------------------------5559795404609280911441883437--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Add a Principal folder plugin `users` to PAU.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/+/AddPrincipalFolder.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 429
+  ... Content-Type: multipart/form-data; boundary=---------------------------95449631112274213651507932125
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/+/AddPrincipalFolder.html=
+  ...
+  ... -----------------------------95449631112274213651507932125
+  ... Content-Disposition: form-data; name="field.prefix"
+  ...
+  ... users
+  ... -----------------------------95449631112274213651507932125
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------95449631112274213651507932125
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... users
+  ... -----------------------------95449631112274213651507932125--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Next we will add some users.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... bob
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Bob
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... bill
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Bill
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... betty
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Betty
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... sally
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Sally
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... george
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... George
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... mike
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Mike
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 780
+  ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... mary
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... 123
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Mary
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Next, We'll add out group folder plugin in PAU.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/+/AddGroupFolder.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 427
+  ... Content-Type: multipart/form-data; boundary=---------------------------4150524541658557772058105275
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/+/AddGroupFolder.html=
+  ...
+  ... -----------------------------4150524541658557772058105275
+  ... Content-Disposition: form-data; name="field.prefix"
+  ...
+  ... groups
+  ... -----------------------------4150524541658557772058105275
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------4150524541658557772058105275
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... groups
+  ... -----------------------------4150524541658557772058105275--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+Next we'll select the credentials and authenticators for the PAU:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 1313
+  ... Content-Type: multipart/form-data; boundary=---------------------------2026736768606413562109112352
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/@@configure.html
+  ...
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.credentialsPlugins.to"
+  ...
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
+  ...
+  ...
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ...
+  ... dXNlcnM=
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ...
+  ... Z3JvdXBz
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
+  ...
+  ...
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Change
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.credentialsPlugins"
+  ...
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ...
+  ... dXNlcnM=
+  ... -----------------------------2026736768606413562109112352
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ...
+  ... Z3JvdXBz
+  ... -----------------------------2026736768606413562109112352--
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+
+
+Now, we can define some groups.  Let's start with a group named "Admin":
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 550
+  ... Content-Type: multipart/form-data; boundary=---------------------------20619400354342370301249668954
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/+/AddGroupInformation.html=
+  ...
+  ... -----------------------------20619400354342370301249668954
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Admin
+  ... -----------------------------20619400354342370301249668954
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------20619400354342370301249668954
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------20619400354342370301249668954
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... admin
+  ... -----------------------------20619400354342370301249668954--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+That includes Betty, Mary and Mike:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/groups/admin/@@edit.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 1509
+  ... Content-Type: multipart/form-data; boundary=---------------------------6981402699601872602121555350
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/admin/@@edit.html
+  ...
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Admin
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals.displayed"
+  ...
+  ... y
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search"
+  ...
+  ...
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ...
+  ... dXNlcnMz
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ...
+  ... dXNlcnM3
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ...
+  ... dXNlcnM2
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.apply"
+  ...
+  ... Apply
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search"
+  ...
+  ...
+  ... -----------------------------6981402699601872602121555350
+  ... Content-Disposition: form-data; name="field.principals.MQ__.query.searchstring"
+  ...
+  ...
+  ... -----------------------------6981402699601872602121555350--
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+
+and a group "Power Users"
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 561
+  ... Content-Type: multipart/form-data; boundary=---------------------------168380148515549442351132560943
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/+/AddGroupInformation.html=
+  ...
+  ... -----------------------------168380148515549442351132560943
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Power Users
+  ... -----------------------------168380148515549442351132560943
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------168380148515549442351132560943
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------168380148515549442351132560943
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... power
+  ... -----------------------------168380148515549442351132560943--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+with Bill and Betty as members:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/groups/power/@@edit.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 1729
+  ... Content-Type: multipart/form-data; boundary=---------------------------181944013812647128322134918391
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/power/@@edit.html
+  ...
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Power Users
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ...
+  ... dXNlcnMz
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ...
+  ... dXNlcnMy
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.principals.displayed"
+  ...
+  ... y
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search"
+  ...
+  ...
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search"
+  ...
+  ...
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="field.principals.MQ__.query.searchstring"
+  ...
+  ...
+  ... -----------------------------181944013812647128322134918391
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Change
+  ... -----------------------------181944013812647128322134918391--
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+Now, with these groups set up, we should see these groups on the
+affected principals.  First, we'll make the root folder the
+thread-local site:
+
+  >>> from zope.site.hooks import setSite
+  >>> setSite(getRootFolder())
+
+and we'll get the pluggable authentication utility:
+
+  >>> from zope.component import getUtility
+  >>> from zope.app.security.interfaces import IAuthentication
+  >>> principals = getUtility(IAuthentication)
+
+Finally we'll get Betty and see that she is in the admin and
+power-user groups:
+
+  >>> betty = principals.getPrincipal(u'users3')
+  >>> betty.groups.sort()
+  >>> betty.groups
+  [u'groupspower', 'zope.Authenticated', 'zope.Everybody']
+
+
+And we'll get Bill, and see that he is only in the power-user group:
+
+  >>> bill = principals.getPrincipal(u'users2')
+  >>> bill.groups
+  ['zope.Everybody', 'zope.Authenticated', u'groupspower']

Deleted: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py
===================================================================
--- zope.app.authentication/trunk/src/zope/app/authentication/session.py	2009-03-06 13:03:45 UTC (rev 97572)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py	2009-03-06 13:11:03 UTC (rev 97574)
@@ -1,301 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-""" Implementations of the session-based and cookie-based extractor and
-    challenge plugins.
-
-$Id$
-"""
-__docformat__ = 'restructuredtext'
-
-import transaction
-from persistent import Persistent
-from urllib import urlencode
-
-from zope.interface import implements, Interface
-from zope.schema import TextLine
-from zope.publisher.interfaces.http import IHTTPRequest
-from zope.session.interfaces import ISession
-from zope.traversing.browser.absoluteurl import absoluteURL
-
-from zope.app.component import hooks
-from zope.container.contained import Contained
-from zope.app.authentication.interfaces import ICredentialsPlugin
-
-class ISessionCredentials(Interface):
-    """ Interface for storing and accessing credentials in a session.
-
-        We use a real class with interface here to prevent unauthorized
-        access to the credentials.
-    """
-
-    def __init__(login, password):
-        pass
-
-    def getLogin():
-        """Return login name."""
-
-    def getPassword():
-        """Return password."""
-
-
-class SessionCredentials(object):
-    """Credentials class for use with sessions.
-
-    A session credential is created with a login and a password:
-
-      >>> cred = SessionCredentials('scott', 'tiger')
-
-    Logins are read using getLogin:
-      >>> cred.getLogin()
-      'scott'
-
-    and passwords with getPassword:
-
-      >>> cred.getPassword()
-      'tiger'
-
-    """
-    implements(ISessionCredentials)
-
-    def __init__(self, login, password):
-        self.login = login
-        self.password = password
-
-    def getLogin(self): return self.login
-
-    def getPassword(self): return self.password
-
-    def __str__(self): return self.getLogin() + ':' + self.getPassword()
-
-
-class IBrowserFormChallenger(Interface):
-    """A challenger that uses a browser form to collect user credentials."""
-
-    loginpagename = TextLine(
-        title=u'Loginpagename',
-        description=u"""Name of the login form used by challenger.
-
-        The form must provide 'login' and 'password' input fields.
-        """,
-        default=u'loginForm.html')
-
-    loginfield = TextLine(
-        title=u'Loginfield',
-        description=u"Field of the login page in which is looked for the login user name.",
-        default=u"login")
-
-    passwordfield = TextLine(
-        title=u'Passwordfield',
-        description=u"Field of the login page in which is looked for the password.",
-        default=u"password")
-
-
-class SessionCredentialsPlugin(Persistent, Contained):
-    """A credentials plugin that uses Zope sessions to get/store credentials.
-
-    To illustrate how a session plugin works, we'll first setup some session
-    machinery:
-
-      >>> from zope.session.session import RAMSessionDataContainer
-      >>> from tests import sessionSetUp
-      >>> sessionSetUp(RAMSessionDataContainer)
-
-    This lets us retrieve the same session info from any test request, which
-    simulates what happens when a user submits a session ID as a cookie.
-
-    We also need a session plugin:
-
-      >>> plugin = SessionCredentialsPlugin()
-
-    A session plugin uses an ISession component to store the last set of
-    credentials it gets from a request. Credentials can be retrieved from
-    subsequent requests using the session-stored credentials.
-
-    Our test environment is initially configured without credentials:
-
-      >>> from tests import sessionSetUp
-      >>> from zope.publisher.browser import TestRequest
-      >>> request = TestRequest()
-      >>> print plugin.extractCredentials(request)
-      None
-
-    We must explicitly provide credentials once so the plugin can store
-    them in a session:
-
-      >>> request = TestRequest(login='scott', password='tiger')
-      >>> plugin.extractCredentials(request)
-      {'login': 'scott', 'password': 'tiger'}
-
-    Subsequent requests now have access to the credentials even if they're
-    not explicitly in the request:
-
-      >>> plugin.extractCredentials(TestRequest())
-      {'login': 'scott', 'password': 'tiger'}
-
-    We can always provide new credentials explicitly in the request:
-
-      >>> plugin.extractCredentials(TestRequest(
-      ...     login='harry', password='hirsch'))
-      {'login': 'harry', 'password': 'hirsch'}
-
-    and these will be used on subsequent requests:
-
-      >>> plugin.extractCredentials(TestRequest())
-      {'login': 'harry', 'password': 'hirsch'}
-
-    We can also change the fields from which the credentials are extracted:
-
-      >>> plugin.loginfield = "my_new_login_field"
-      >>> plugin.passwordfield = "my_new_password_field"
-
-    Now we build a request that uses the new fields:
-
-      >>> request = TestRequest(my_new_login_field='luke', my_new_password_field='the_force')
-
-    The plugin now extracts the credentials information from these new fields:
-
-      >>> plugin.extractCredentials(request)
-      {'login': 'luke', 'password': 'the_force'}
-
-    Finally, we clear the session credentials using the logout method:
-
-      >>> plugin.logout(TestRequest())
-      True
-      >>> print plugin.extractCredentials(TestRequest())
-      None
-
-    """
-    implements(ICredentialsPlugin, IBrowserFormChallenger)
-
-    loginpagename = 'loginForm.html'
-    loginfield = 'login'
-    passwordfield = 'password'
-
-    def extractCredentials(self, request):
-        """Extracts credentials from a session if they exist."""
-        if not IHTTPRequest.providedBy(request):
-            return None
-        session = ISession(request)
-        sessionData = session.get(
-            'zope.app.authentication.browserplugins')
-        login = request.get(self.loginfield, None)
-        password = request.get(self.passwordfield, None)
-        credentials = None
-
-        if login and password:
-            credentials = SessionCredentials(login, password)
-        elif not sessionData:
-            return None
-        sessionData = session[
-            'zope.app.authentication.browserplugins']
-        if credentials:
-            sessionData['credentials'] = credentials
-        else:
-            credentials = sessionData.get('credentials', None)
-        if not credentials:
-            return None
-        return {'login': credentials.getLogin(),
-                'password': credentials.getPassword()}
-
-    def challenge(self, request):
-        """Challenges by redirecting to a login form.
-
-        To illustrate, we'll create a test request:
-
-          >>> from zope.publisher.browser import TestRequest
-          >>> request = TestRequest()
-
-        and confirm its response's initial status and 'location' header:
-
-          >>> request.response.getStatus()
-          599
-          >>> request.response.getHeader('location')
-
-        When we issue a challenge using a session plugin:
-
-          >>> plugin = SessionCredentialsPlugin()
-          >>> plugin.challenge(request)
-          True
-
-        we get a redirect:
-
-          >>> request.response.getStatus()
-          302
-          >>> request.response.getHeader('location')
-          'http://127.0.0.1/@@loginForm.html?camefrom=%2F'
-
-        The plugin redirects to the page defined by the loginpagename
-        attribute:
-
-          >>> plugin.loginpagename = 'mylogin.html'
-          >>> plugin.challenge(request)
-          True
-          >>> request.response.getHeader('location')
-          'http://127.0.0.1/@@mylogin.html?camefrom=%2F'
-
-        It also provides the request URL as a 'camefrom' GET style parameter.
-        To illustrate, we'll pretend we've traversed a couple names:
-
-          >>> env = {
-          ...     'REQUEST_URI': '/foo/bar/folder/page%201.html?q=value',
-          ...     'QUERY_STRING': 'q=value'
-          ...     }
-          >>> request = TestRequest(environ=env)
-          >>> request._traversed_names = [u'foo', u'bar']
-          >>> request._traversal_stack = [u'page 1.html', u'folder']
-          >>> request['REQUEST_URI']
-          '/foo/bar/folder/page%201.html?q=value'
-
-        When we challenge:
-
-          >>> plugin.challenge(request)
-          True
-
-        We see the 'camefrom' points to the requested URL:
-
-          >>> request.response.getHeader('location') # doctest: +ELLIPSIS
-          '.../@@mylogin.html?camefrom=%2Ffoo%2Fbar%2Ffolder%2Fpage+1.html%3Fq%3Dvalue'
-
-        This can be used by the login form to redirect the user back to the
-        originating URL upon successful authentication.
-        """
-        if not IHTTPRequest.providedBy(request):
-            return False
-
-        site = hooks.getSite()
-        # We need the traversal stack to complete the 'camefrom' parameter
-        stack = request.getTraversalStack()
-        stack.reverse()
-        # Better to add the query string, if present
-        query = request.get('QUERY_STRING')
-
-        camefrom = '/'.join([request.getURL(path_only=True)] + stack)
-        if query:
-            camefrom = camefrom + '?' + query
-        url = '%s/@@%s?%s' % (absoluteURL(site, request),
-                              self.loginpagename,
-                              urlencode({'camefrom': camefrom}))
-        request.response.redirect(url)
-        return True
-
-    def logout(self, request):
-        """Performs logout by clearing session data credentials."""
-        if not IHTTPRequest.providedBy(request):
-            return False
-
-        sessionData = ISession(request)[
-            'zope.app.authentication.browserplugins']
-        sessionData['credentials'] = None
-        transaction.commit()
-        return True

Copied: zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py (from rev 97573, zope.app.authentication/trunk/src/zope/app/authentication/session.py)
===================================================================
--- zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py	                        (rev 0)
+++ zope.app.authentication/tags/3.5.0/src/zope/app/authentication/session.py	2009-03-06 13:11:03 UTC (rev 97574)
@@ -0,0 +1,301 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Implementations of the session-based and cookie-based extractor and
+    challenge plugins.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import transaction
+from persistent import Persistent
+from urllib import urlencode
+
+from zope.interface import implements, Interface
+from zope.schema import TextLine
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.session.interfaces import ISession
+from zope.traversing.browser.absoluteurl import absoluteURL
+
+from zope.site import hooks
+from zope.container.contained import Contained
+from zope.app.authentication.interfaces import ICredentialsPlugin
+
+class ISessionCredentials(Interface):
+    """ Interface for storing and accessing credentials in a session.
+
+        We use a real class with interface here to prevent unauthorized
+        access to the credentials.
+    """
+
+    def __init__(login, password):
+        pass
+
+    def getLogin():
+        """Return login name."""
+
+    def getPassword():
+        """Return password."""
+
+
+class SessionCredentials(object):
+    """Credentials class for use with sessions.
+
+    A session credential is created with a login and a password:
+
+      >>> cred = SessionCredentials('scott', 'tiger')
+
+    Logins are read using getLogin:
+      >>> cred.getLogin()
+      'scott'
+
+    and passwords with getPassword:
+
+      >>> cred.getPassword()
+      'tiger'
+
+    """
+    implements(ISessionCredentials)
+
+    def __init__(self, login, password):
+        self.login = login
+        self.password = password
+
+    def getLogin(self): return self.login
+
+    def getPassword(self): return self.password
+
+    def __str__(self): return self.getLogin() + ':' + self.getPassword()
+
+
+class IBrowserFormChallenger(Interface):
+    """A challenger that uses a browser form to collect user credentials."""
+
+    loginpagename = TextLine(
+        title=u'Loginpagename',
+        description=u"""Name of the login form used by challenger.
+
+        The form must provide 'login' and 'password' input fields.
+        """,
+        default=u'loginForm.html')
+
+    loginfield = TextLine(
+        title=u'Loginfield',
+        description=u"Field of the login page in which is looked for the login user name.",
+        default=u"login")
+
+    passwordfield = TextLine(
+        title=u'Passwordfield',
+        description=u"Field of the login page in which is looked for the password.",
+        default=u"password")
+
+
+class SessionCredentialsPlugin(Persistent, Contained):
+    """A credentials plugin that uses Zope sessions to get/store credentials.
+
+    To illustrate how a session plugin works, we'll first setup some session
+    machinery:
+
+      >>> from zope.session.session import RAMSessionDataContainer
+      >>> from tests import sessionSetUp
+      >>> sessionSetUp(RAMSessionDataContainer)
+
+    This lets us retrieve the same session info from any test request, which
+    simulates what happens when a user submits a session ID as a cookie.
+
+    We also need a session plugin:
+
+      >>> plugin = SessionCredentialsPlugin()
+
+    A session plugin uses an ISession component to store the last set of
+    credentials it gets from a request. Credentials can be retrieved from
+    subsequent requests using the session-stored credentials.
+
+    Our test environment is initially configured without credentials:
+
+      >>> from tests import sessionSetUp
+      >>> from zope.publisher.browser import TestRequest
+      >>> request = TestRequest()
+      >>> print plugin.extractCredentials(request)
+      None
+
+    We must explicitly provide credentials once so the plugin can store
+    them in a session:
+
+      >>> request = TestRequest(login='scott', password='tiger')
+      >>> plugin.extractCredentials(request)
+      {'login': 'scott', 'password': 'tiger'}
+
+    Subsequent requests now have access to the credentials even if they're
+    not explicitly in the request:
+
+      >>> plugin.extractCredentials(TestRequest())
+      {'login': 'scott', 'password': 'tiger'}
+
+    We can always provide new credentials explicitly in the request:
+
+      >>> plugin.extractCredentials(TestRequest(
+      ...     login='harry', password='hirsch'))
+      {'login': 'harry', 'password': 'hirsch'}
+
+    and these will be used on subsequent requests:
+
+      >>> plugin.extractCredentials(TestRequest())
+      {'login': 'harry', 'password': 'hirsch'}
+
+    We can also change the fields from which the credentials are extracted:
+
+      >>> plugin.loginfield = "my_new_login_field"
+      >>> plugin.passwordfield = "my_new_password_field"
+
+    Now we build a request that uses the new fields:
+
+      >>> request = TestRequest(my_new_login_field='luke', my_new_password_field='the_force')
+
+    The plugin now extracts the credentials information from these new fields:
+
+      >>> plugin.extractCredentials(request)
+      {'login': 'luke', 'password': 'the_force'}
+
+    Finally, we clear the session credentials using the logout method:
+
+      >>> plugin.logout(TestRequest())
+      True
+      >>> print plugin.extractCredentials(TestRequest())
+      None
+
+    """
+    implements(ICredentialsPlugin, IBrowserFormChallenger)
+
+    loginpagename = 'loginForm.html'
+    loginfield = 'login'
+    passwordfield = 'password'
+
+    def extractCredentials(self, request):
+        """Extracts credentials from a session if they exist."""
+        if not IHTTPRequest.providedBy(request):
+            return None
+        session = ISession(request)
+        sessionData = session.get(
+            'zope.app.authentication.browserplugins')
+        login = request.get(self.loginfield, None)
+        password = request.get(self.passwordfield, None)
+        credentials = None
+
+        if login and password:
+            credentials = SessionCredentials(login, password)
+        elif not sessionData:
+            return None
+        sessionData = session[
+            'zope.app.authentication.browserplugins']
+        if credentials:
+            sessionData['credentials'] = credentials
+        else:
+            credentials = sessionData.get('credentials', None)
+        if not credentials:
+            return None
+        return {'login': credentials.getLogin(),
+                'password': credentials.getPassword()}
+
+    def challenge(self, request):
+        """Challenges by redirecting to a login form.
+
+        To illustrate, we'll create a test request:
+
+          >>> from zope.publisher.browser import TestRequest
+          >>> request = TestRequest()
+
+        and confirm its response's initial status and 'location' header:
+
+          >>> request.response.getStatus()
+          599
+          >>> request.response.getHeader('location')
+
+        When we issue a challenge using a session plugin:
+
+          >>> plugin = SessionCredentialsPlugin()
+          >>> plugin.challenge(request)
+          True
+
+        we get a redirect:
+
+          >>> request.response.getStatus()
+          302
+          >>> request.response.getHeader('location')
+          'http://127.0.0.1/@@loginForm.html?camefrom=%2F'
+
+        The plugin redirects to the page defined by the loginpagename
+        attribute:
+
+          >>> plugin.loginpagename = 'mylogin.html'
+          >>> plugin.challenge(request)
+          True
+          >>> request.response.getHeader('location')
+          'http://127.0.0.1/@@mylogin.html?camefrom=%2F'
+
+        It also provides the request URL as a 'camefrom' GET style parameter.
+        To illustrate, we'll pretend we've traversed a couple names:
+
+          >>> env = {
+          ...     'REQUEST_URI': '/foo/bar/folder/page%201.html?q=value',
+          ...     'QUERY_STRING': 'q=value'
+          ...     }
+          >>> request = TestRequest(environ=env)
+          >>> request._traversed_names = [u'foo', u'bar']
+          >>> request._traversal_stack = [u'page 1.html', u'folder']
+          >>> request['REQUEST_URI']
+          '/foo/bar/folder/page%201.html?q=value'
+
+        When we challenge:
+
+          >>> plugin.challenge(request)
+          True
+
+        We see the 'camefrom' points to the requested URL:
+
+          >>> request.response.getHeader('location') # doctest: +ELLIPSIS
+          '.../@@mylogin.html?camefrom=%2Ffoo%2Fbar%2Ffolder%2Fpage+1.html%3Fq%3Dvalue'
+
+        This can be used by the login form to redirect the user back to the
+        originating URL upon successful authentication.
+        """
+        if not IHTTPRequest.providedBy(request):
+            return False
+
+        site = hooks.getSite()
+        # We need the traversal stack to complete the 'camefrom' parameter
+        stack = request.getTraversalStack()
+        stack.reverse()
+        # Better to add the query string, if present
+        query = request.get('QUERY_STRING')
+
+        camefrom = '/'.join([request.getURL(path_only=True)] + stack)
+        if query:
+            camefrom = camefrom + '?' + query
+        url = '%s/@@%s?%s' % (absoluteURL(site, request),
+                              self.loginpagename,
+                              urlencode({'camefrom': camefrom}))
+        request.response.redirect(url)
+        return True
+
+    def logout(self, request):
+        """Performs logout by clearing session data credentials."""
+        if not IHTTPRequest.providedBy(request):
+            return False
+
+        sessionData = ISession(request)[
+            'zope.app.authentication.browserplugins']
+        sessionData['credentials'] = None
+        transaction.commit()
+        return True



More information about the Checkins mailing list