[Checkins] SVN: Sandbox/kevingill2/ Demonstration implementation of the changes for proposal:

Kevin Gill kevin at movieextras.ie
Tue Apr 15 11:15:29 EDT 2008


Log message for revision 85384:
  Demonstration implementation of the changes for proposal:
  Session Credentials API Enhancements.
  

Changed:
  A   Sandbox/kevingill2/README.txt
  A   Sandbox/kevingill2/zope.app.authentication/
  A   Sandbox/kevingill2/zope.app.authentication/zope/
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/README.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/SETUP.cfg
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/__init__.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/authentication.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/__init__.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.pt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/adding.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/configure.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/group_searching_with_empty_string.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/httpplugins.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/issue663.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/loginform.pt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/pau_prefix_and_searching.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/register.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/session.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/special-groups.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/tests.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/configure.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftesting.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/generic.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/i18n.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/idpicker.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/interfaces.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/placelesssetup.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.zcml
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/testing.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/tests.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.py
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.txt
  A   Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/zope.app.authentication-configure.zcml

-=-
Added: Sandbox/kevingill2/README.txt
===================================================================
--- Sandbox/kevingill2/README.txt	                        (rev 0)
+++ Sandbox/kevingill2/README.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,16 @@
+Zope.App.Authentication
+=======================
+
+This is a test implementation for the proposal :
+
+    Session Credentials API Enhancements
+
+The proposal is visible here:
+
+    http://wiki.zope.org/zope3/SessionCredentialsAPIEnhancements
+
+Discussion of the proposal should be here:
+
+    https://blueprints.launchpad.net/zope3/+spec/session-credentials-enhancements
+
+    or in the Zope-dev mailing list


Property changes on: Sandbox/kevingill2/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/README.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/README.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/README.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -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.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


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/SETUP.cfg
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/SETUP.cfg	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/SETUP.cfg	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,5 @@
+# Tell zpkg how to install the ZCML slugs.
+
+<data-files zopeskel/etc/package-includes>
+  *-configure.zcml
+</data-files>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/SETUP.cfg
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/__init__.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/__init__.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/__init__.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,21 @@
+##############################################################################
+#
+# 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
+
+$Id: __init__.py 70540 2006-10-05 08:55:10Z flox $
+"""
+
+import interfaces
+from zope.app.authentication.authentication import PluggableAuthentication
+


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/authentication.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/authentication.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/authentication.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,196 @@
+##############################################################################
+#
+# 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: authentication.py 77044 2007-06-25 14:32:12Z zagy $
+"""
+import logging
+
+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.app.container.btree
+
+from zope.app.authentication import interfaces
+
+class PluggableAuthentication(zope.app.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
+                try:
+                    info = authplugin.authenticateCredentials(credentials)
+                    if info is None:
+                        continue
+                except interfaces.PasswordManagerMismatchError:
+                    eventlog = logging.getLogger('eventlog')
+                    eventlog.warn("""Password Manager Mismatch. - Clearing Credentials password manager for %s"""
+                        % credplugin)
+                    credplugin.clearPasswordManager(request)
+                    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


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/authentication.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/__init__.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/__init__.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/__init__.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# 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 Views
+
+$Id: __init__.py 28948 2005-01-24 20:16:09Z jim $
+"""


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.pt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.pt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.pt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,34 @@
+<html metal:use-macro="context/@@standard_macros/page"
+    i18n:domain="zope">
+<head>
+  <title metal:fill-slot="title" i18n:translate="">
+    Sign in
+  </title>
+</head>
+<body><div metal:fill-slot="body">
+
+<h1>Active Session Credentials</h1>
+
+<p>Note: credentials are also saved for failed logins</p>
+
+<table class="listing">
+<thead>
+    <tr>
+        <th>Domain</th>
+        <th>Login Name</th>
+        <th>IP Address</th>
+        <th>Connect Time</th>
+        <th>Idle Time (seconds)</th>
+    </tr>
+</thead>
+    <tr tal:repeat="row view/SessionRecords">
+        <td tal:content="row/domain"></td>
+        <td tal:content="row/login"></td>
+        <td tal:content="row/ip"></td>
+        <td tal:content="row/extractTime"></td>
+        <td tal:content="row/idleTime"></td>
+    </tr>
+</table>
+
+</div></body></html>
+


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,35 @@
+# Display a list of active sessions
+
+from datetime import datetime
+from time import localtime, time
+
+from zope.publisher.browser import BrowserPage
+from zope.app.authentication.helpers import getSessionRecords
+
+from zope.app.pagetemplate import ViewPageTemplateFile
+
+def formattimestamp(ts):
+    lt = localtime(ts)
+    dt = datetime(lt[0], lt[1], lt[2], lt[3], lt[4])
+    return dt.strftime('%Y-%m-%d %H:%M')
+
+class ActiveSessions(BrowserPage):
+
+    __call__ = ViewPageTemplateFile('activesessions.pt')
+
+    def SessionRecords(self):
+        data = []
+        timeNow = time()
+        for row in getSessionRecords(self.context):
+            idleTime = 'not used'
+            if row['accessTime']:
+                idleTime = int(timeNow - row['accessTime'])
+            data.append( {
+                'domain': row['domain'],
+                'login': row['login'],
+                'ip': row['ip'],
+                'extractTime': formattimestamp(row['extractTime']),
+                'idleTime': idleTime,
+            })
+        return data
+


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/activesessions.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/adding.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/adding.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/adding.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Adding that redirects to plugins.html.
+
+$Id: adding.py 69216 2006-07-19 22:05:20Z jim $
+"""
+
+from zope.app import zapi
+
+import zope.app.container.browser.adding
+
+class Adding(zope.app.container.browser.adding.Adding):
+    
+    def nextURL(self):
+        return zapi.absoluteURL(self.context, self.request
+                                ) + '/@@contents.html'


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/adding.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/configure.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/configure.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/configure.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,91 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="zope"
+    >
+
+  <addform
+      schema="..interfaces.IPluggableAuthentication"
+      label="Add Pluggable Authentication"
+      content_factory="..authentication.PluggableAuthentication"
+      fields="prefix"
+      keyword_arguments="prefix"
+      name="AddPluggableAuthentication.html"
+      permission="zope.ManageServices"
+      >
+
+    <widget
+        field="prefix"
+        class="zope.app.form.browser.TextWidget"
+        required="False"
+        convert_missing_value="False"
+        />
+
+  </addform>
+
+  <addMenuItem
+      class="..authentication.PluggableAuthentication"
+      view="AddPluggableAuthentication.html"
+      title="Pluggable Authentication Utility"
+      description="New-style pluggable authentication utility"
+      permission="zope.ManageServices"
+      />
+
+  <page
+      for="..interfaces.IPluggableAuthentication"
+      name="addRegistration.html"
+      permission="zope.ManageSite"
+      class=".register.AddAuthenticationRegistration" 
+      />
+
+  <editform
+      schema="..interfaces.IPluggableAuthentication"
+      label="Edit Pluggable Authentication Utility"
+      name="configure.html"
+      fields="prefix credentialsPlugins authenticatorPlugins"
+      menu="zmi_views" title="Configure"
+      permission="zope.ManageServices"
+      />
+
+  <page
+      name="contents.html"
+      for="..interfaces.IPluggableAuthentication"
+      menu="zmi_views" title="Plugins"
+      permission="zope.ManageSite"
+      class="zope.app.container.browser.contents.Contents"
+      attribute="contents"
+      />
+
+  <view
+      name="+"
+      menu="zmi_actions" title="Add"
+      for="..interfaces.IPluggableAuthentication"
+      permission="zope.ManageSite"
+      class=".adding.Adding"
+      >
+    <page
+        name="index.html"
+        attribute="index"
+        />
+    <page
+        name="action.html"
+        attribute="action"
+        />
+  </view>
+
+  <menuItem
+      menu="zmi_views"
+      for="..interfaces.IPluggableAuthentication"
+      title="Contents"
+      action=""
+      filter="python:False"
+      />
+
+  <zope:adapter
+      for="..interfaces.IQuerySchemaSearch
+           zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.app.form.browser.interfaces.ISourceQueryView"
+      factory=".schemasearch.QuerySchemaSearchView"
+      />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/group_searching_with_empty_string.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/group_searching_with_empty_string.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/group_searching_with_empty_string.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,190 @@
+We can search group folder with an empty string.
+
+We'll add 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
+  ... 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
+  ...
+
+
+And register it:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 699
+  ... Content-Type: multipart/form-data; boundary=---------------------------191720529414243436931796477300
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/addRegistration.html
+  ... 
+  ... -----------------------------191720529414243436931796477300
+  ... Content-Disposition: form-data; name="field.comment"
+  ... 
+  ... 
+  ... -----------------------------191720529414243436931796477300
+  ... Content-Disposition: form-data; name="field.actions.register"
+  ... 
+  ... Register
+  ... -----------------------------191720529414243436931796477300--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+Next, we'll add the group folder:
+
+  >>> 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
+  ...
+
+
+And add some groups:
+
+
+  >>> 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=---------------------------12719796373012316301953477158
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/+/AddGroupInformation.html=
+  ... 
+  ... -----------------------------12719796373012316301953477158
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Test1
+  ... -----------------------------12719796373012316301953477158
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------12719796373012316301953477158
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------12719796373012316301953477158
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... Test1
+  ... -----------------------------12719796373012316301953477158--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+  >>> 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=---------------------------10816732208483809451400699513
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/+/AddGroupInformation.html=
+  ... 
+  ... -----------------------------10816732208483809451400699513
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Test2
+  ... -----------------------------10816732208483809451400699513
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------10816732208483809451400699513
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------10816732208483809451400699513
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... Test2
+  ... -----------------------------10816732208483809451400699513--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+Now we'll configure our pluggable-authentication utility to use the
+group folder:
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 1040
+  ... Content-Type: multipart/form-data; boundary=---------------------------1786480431902757372789659730
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/@@configure.html
+  ... 
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="field.credentialsPlugins.to"
+  ... 
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
+  ... 
+  ... 
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ... 
+  ... Z3JvdXBz
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
+  ... 
+  ... 
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Change
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="field.credentialsPlugins"
+  ... 
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------1786480431902757372789659730
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ... 
+  ... Z3JvdXBz
+  ... -----------------------------1786480431902757372789659730--
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+
+Now, if we search for a group, but don't supply a string:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 166
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal.displayed=y&"""
+  ... "field.principal.MC5ncm91cHM_.field.search=&"
+  ... "field.principal.MC5ncm91cHM_.search=Search&"
+  ... "field.principal.MQ__.searchstring=")
+  HTTP/1.1 200 OK
+  ...Test1...Test2...
+
+We get both of our groups in the result.


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/group_searching_with_empty_string.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,658 @@
+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.app import zapi
+  >>> principals = zapi.principals()
+
+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']


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,55 @@
+<configure 
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="zope"
+    >
+
+<editform
+    schema="..groupfolder.IGroupInformation"
+    label="Change group information"
+    name="edit.html"
+    menu="zmi_views" title="Edit"
+    permission="zope.ManageServices"
+    />
+
+<addform
+    schema="..groupfolder.IGroupInformation"
+    content_factory="..groupfolder.GroupInformation"
+    label="Add group information"
+    name="AddGroupInformation.html"
+    permission="zope.ManageServices"
+    fields="title description"
+    />
+
+<addMenuItem
+    title="Group"
+    description="A principals group"
+    class="..groupfolder.GroupInformation"
+    permission="zope.ManageServices"
+    view="AddGroupInformation.html"
+    />
+
+<addform
+    schema="..groupfolder.IGroupFolder"
+    content_factory="..groupfolder.GroupFolder"
+    arguments="prefix"
+    label="Add group folder"
+    name="AddGroupFolder.html"
+    permission="zope.ManageServices"
+    />
+
+<addMenuItem
+    title="Group Folder"
+    description="A Group folder"
+    class="..groupfolder.GroupFolder"
+    permission="zope.ManageServices"
+    view="AddGroupFolder.html"
+    />
+
+<containerViews
+    for="..groupfolder.IGroupFolder"
+    contents="zope.ManageServices"
+    index="zope.ManageServices"
+    add="zope.ManageServices"
+    />
+
+</configure> 


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/groupfolder.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/httpplugins.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/httpplugins.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/httpplugins.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,20 @@
+<configure 
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="zope"
+    >
+
+  <addMenuItem
+      title="HTTP Basic-Auth Plugin"
+      class="..httpplugins.HTTPBasicAuthCredentialsPlugin"
+      permission="zope.ManageServices"
+      />
+
+  <editform
+      schema="..httpplugins.IHTTPBasicAuthRealm"
+      label="Realm"
+      name="edit.html"
+      permission="zope.ManageServices"
+      menu="zmi_views" title="Edit"
+      />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/httpplugins.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/issue663.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/issue663.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/issue663.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,54 @@
+http://www.zope.org/Collectors/Zope3-dev/663
+============================================
+
+Two plugins(basic-auth and session credentials) link
+on PAU add menu are broken and can't add them.
+
+For IPluggableAuthentication, "plugins.html" is a correct
+view name but "contents.html" is used.
+
+because menu implementation supporsing that all view
+uses "zope.app.container.browser.contents.Contents" are
+named "contents.html".
+
+In Zope3.2, PluggableAuthentication inherits
+SiteManagementFolder that provides "contents.html" view.
+
+    >>> from zope.testbrowser.testing import Browser
+    >>> browser = Browser()
+
+Create a pau
+
+    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+    >>> browser.open('http://localhost/@@contents.html')
+    >>> browser.getLink('Pluggable Authentication Utility').click()
+    >>> browser.getControl(name='add_input_name').value = 'auth'
+    >>> browser.getControl('Add').click()
+    >>> browser.getLink('auth').click()
+
+Go to the plugins view
+
+    >>> browser.getLink('Plugins').click()
+
+Add aa basic auth plugin
+
+    >>> browser.getLink('HTTP Basic-Auth Plugin').click()
+    >>> browser.getControl(name='new_value').value = 'basic'
+    >>> browser.getControl('Apply').click()
+
+Add a session-credential plugin
+
+    >>> browser.getLink('Session Credentials Plugin').click()
+    >>> browser.getControl(name='new_value').value = 'session'
+    >>> browser.getControl('Apply').click()
+
+Make sure we can use them:
+
+    >>> browser.getLink('Configure').click()
+    >>> browser.getControl(name='field.credentialsPlugins.from').value = [
+    ...     'Wm9wZSBSZWFsbSBCYXNpYy1BdXRo']
+    >>> browser.getControl(name='field.credentialsPlugins.from').value = [
+    ...     'YmFzaWM=']
+    >>> browser.getControl(name='field.credentialsPlugins.from').value = [
+    ...     'U2Vzc2lvbiBDcmVkZW50aWFscw==']
+    >>> browser.getControl('Change').click()


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/issue663.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/loginform.pt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/loginform.pt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/loginform.pt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,63 @@
+<html metal:use-macro="context/@@standard_macros/page"
+    i18n:domain="zope">
+<head>
+  <title metal:fill-slot="title" i18n:translate="">
+    Sign in
+  </title>
+</head>
+<body><div metal:fill-slot="body" tal:define="principal python:request.principal.id">
+    <p i18n:translate="" tal:condition="python: principal == 'zope.anybody'">
+      Please provide Login Information</p>
+    <p i18n:translate="" tal:condition="python: principal != 'zope.anybody'">
+      You are not authorized to perform this action. However, you may login as a 
+      different user who is authorized.</p>
+    <form action="" method="post">
+        <div tal:omit-tag=""
+            tal:condition="python:principal != 'zope.anybody' and 'SUBMIT' in request">
+            <span tal:define="dummy python:request.response.redirect(request.get('camefrom', ''))" />
+        </div>
+<!-- To use domain logins, uncomment this section
+
+        <div class="row">
+            <div class="label"><label for="domain" i18n:translate="">Domain</label></div>
+            <div class="field">
+                <input type="text" name="domain" id="domain" />
+            </div>
+        </div>
+        <script language="javascript">
+            // Set the value of the domain from a cookie
+            function readCookie(name) {
+                var nameEQ = name + "=";
+                var ca = document.cookie.split(';');
+                for(var i=0;i < ca.length;i++) {
+                    var c = ca[i];
+                    while (c.charAt(0)==' ') c = c.substring(1,c.length);
+                    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+                    }
+                return null;
+                }
+            document.getElementById('domain').value = readCookie('login.domain');
+        </script>
+-->
+        <div class="row">
+            <div class="label"><label for="login" i18n:translate="">User Name</label></div>
+            <div class="field">
+                <input type="text" name="login" id="login" />
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label"><label for="password" i18n:translate="">Password</label></div>
+            <div class="field">
+                <input type="password" name="password" id="password" />
+            </div>
+        </div>
+    
+        <div class="row">
+            <input class="form-element" type="submit" 
+                    name="SUBMIT" value="Log in" i18n:attributes="value login-button" />
+        </div>
+        <input type="hidden" name="camefrom" tal:attributes="value request/camefrom | nothing">
+    </form>
+</div></body></html>
+


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/loginform.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/pau_prefix_and_searching.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/pau_prefix_and_searching.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/pau_prefix_and_searching.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,263 @@
+================================
+Using a PAU Prefix and Searching
+================================
+
+This test confirms that both principals and groups can be searched for in
+PAUs that have prefixes.
+
+First we'll create a PAU with a prefix of `pau1_` and and register:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/+/AddPluggableAuthentication.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 372
+  ... Content-Type: multipart/form-data; boundary=---------------------------318183180122653
+  ...
+  ... -----------------------------318183180122653
+  ... Content-Disposition: form-data; name="field.prefix"
+  ...
+  ... pau1_
+  ... -----------------------------318183180122653
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------318183180122653
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... PAU1
+  ... -----------------------------318183180122653--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU1/addRegistration.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 591
+  ... Content-Type: multipart/form-data; boundary=---------------------------516441125097
+  ...
+  ... -----------------------------516441125097
+  ... Content-Disposition: form-data; name="field.comment"
+  ...
+  ... 
+  ... -----------------------------516441125097
+  ... Content-Disposition: form-data; name="field.actions.register"
+  ...
+  ... Register
+  ... -----------------------------516441125097--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Next we'll create and register a principal folder:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU1/+/AddPrincipalFolder.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 374
+  ... Content-Type: multipart/form-data; boundary=---------------------------266241536215161
+  ...
+  ... -----------------------------266241536215161
+  ... Content-Disposition: form-data; name="field.prefix"
+  ...
+  ... users_
+  ... -----------------------------266241536215161
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------266241536215161
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... Users
+  ... -----------------------------266241536215161--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+and add a principal that we'll later search for:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU1/Users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 686
+  ... Content-Type: multipart/form-data; boundary=---------------------------300171485226567
+  ...
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="field.login"
+  ...
+  ... bob
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="field.passwordManagerName"
+  ...
+  ... Plain Text
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="field.password"
+  ...
+  ... bob
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Bob
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------300171485226567
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ...
+  ... -----------------------------300171485226567--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Next, we'll add and register a group folder:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU1/+/AddGroupFolder.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 372
+  ... Content-Type: multipart/form-data; boundary=---------------------------17420126702455
+  ...
+  ... -----------------------------17420126702455
+  ... Content-Disposition: form-data; name="field.prefix"
+  ...
+  ... groups_
+  ... -----------------------------17420126702455
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------17420126702455
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... Groups
+  ... -----------------------------17420126702455--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+and add a group to search for:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU1/Groups/+/AddGroupInformation.html%3D HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 485
+  ... Content-Type: multipart/form-data; boundary=---------------------------323081358415654
+  ...
+  ... -----------------------------323081358415654
+  ... Content-Disposition: form-data; name="field.title"
+  ...
+  ... Nice People
+  ... -----------------------------323081358415654
+  ... Content-Disposition: form-data; name="field.description"
+  ...
+  ...
+  ... -----------------------------323081358415654
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Add
+  ... -----------------------------323081358415654
+  ... Content-Disposition: form-data; name="add_input_name"
+  ...
+  ... nice
+  ... -----------------------------323081358415654--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Since we're only searching in this test, we won't bother to add anyone to the
+group.
+
+Before we search, we need to register the two authenticator plugins with the
+PAU:
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU1/@@configure.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 888
+  ... Content-Type: multipart/form-data; boundary=---------------------------610310492754
+  ...
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
+  ...
+  ...
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ...
+  ... R3JvdXBz
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ...
+  ... VXNlcnM=
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
+  ...
+  ...
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ...
+  ... Change
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ...
+  ... R3JvdXBz
+  ... -----------------------------610310492754
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ...
+  ... VXNlcnM=
+  ... -----------------------------610310492754--
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+Now we'll use the 'grant' interface of the root folder to search for all of
+the available groups:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 191
+  ... Content-Type: application/x-www-form-urlencoded
+  ... 
+  ... field.principal.displayed=y&"""
+  ... "field.principal.MC5Hcm91cHM_.field.search=&"
+  ... "field.principal.MC5Hcm91cHM_.search=Search&"
+  ... "field.principal.MC5Vc2Vycw__.field.search=&"
+  ... "field.principal.MQ__.searchstring=")
+  HTTP/1.1 200 OK
+  ...
+  <select name="field.principal.MC5Hcm91cHM_.selection">
+  <option value="cGF1MV9ncm91cHNfbmljZQ__">Nice People</option>
+  </select>
+  ...
+
+Note in the results that the dropdown box (i.e. the select element) has the
+single group 'Nice People' that we added earlier.
+
+Next, we'll use the same 'grant' interface to search for all of the available
+principals:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 255
+  ... Content-Type: application/x-www-form-urlencoded
+  ...
+  ... field.principal.displayed=y&"""
+  ... "field.principal.MC5Hcm91cHM_.field.search=&"
+  ... "field.principal.MC5Hcm91cHM_.selection=cGF1MV9ncm91cHNfbmljZQ__&"
+  ... "field.principal.MC5Vc2Vycw__.field.search=&"
+  ... "field.principal.MC5Vc2Vycw__.search=Search&"
+  ... "field.principal.MQ__.searchstring=")
+  HTTP/1.1 200 OK
+  ...
+  <select name="field.principal.MC5Vc2Vycw__.selection">
+  <option value="cGF1MV91c2Vyc18x">Bob</option>
+  </select>
+  ...
+
+Note here the dropdown contains Bob, the principal we added earlier.


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/pau_prefix_and_searching.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,308 @@
+Using Principal Folders
+=======================
+
+Principal folders are Pluggable-Authentication plugins that manage
+principal information, especially authentication credentials.  To use
+a principal folder, you need add a principal folder plugin to the PAU
+and to configure the PAU to use plugin.
+
+Let's look at an example, in which we'll define a new manager named
+Bob.  Initially, attempts to log in as Bob fail:
+
+  >>> print http(r"""
+  ... GET /manage HTTP/1.1
+  ... Authorization: Basic Ym9iOjEyMw==
+  ... """)
+  HTTP/1.1 401 Unauthorized
+  ...
+
+To allow Bob to log in, we'll start by adding a principal folder to PAU:
+
+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""",
+  ... handle_errors=False)
+  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--
+  ... """, handle_errors=False)
+  HTTP/1.1 303 See Other
+  ...
+
+Add a Principal folder plugin 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
+  ...
+
+We specify a prefix, `users.`.  This is used to make sure that ids
+used by this plugin don't conflict with ids of other plugins.  We also
+name ths plugin `users`.  This is the name we'll use when we configure
+the pluggable authentiaction service.
+
+Next we'll view the contents page of the principal folder:
+
+  >>> print http(r"""
+  ... GET /++etc++site/default/PAU/users/@@contents.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/users/addRegistration.html
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+
+And we'll add a principal, Bob:
+
+
+  >>> 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"
+  ... 
+  ... SHA1
+  ... -----------------------------5110544421083023415453147877
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... bob
+  ... -----------------------------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"
+  ... 
+  ... bob
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+Note that we didn't pick a name.  The name, together with the folder
+prefix. If we don't choose a name, a numeric id is chosen.
+
+
+Now we have a principal folder with a principal. 
+
+Configure PAU, with registered principal folder plugin and 
+select any one credentials.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 1038
+  ... Content-Type: multipart/form-data; boundary=---------------------------6519411471194050603270010787
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/@@configure.html
+  ... 
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.credentialsPlugins.to"
+  ... 
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
+  ... 
+  ... 
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ... 
+  ... dXNlcnM=
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
+  ... 
+  ... 
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Change
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.credentialsPlugins"
+  ... 
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ... 
+  ... dXNlcnM=
+  ... -----------------------------6519411471194050603270010787--
+  ... """, handle_errors=False)
+  HTTP/1.1 200 OK
+  ... 
+
+Now, with this in place, Bob can log in, but he isn't allowed to
+access the management interface. When he attempts to do so, the PAU 
+issues a challenge to let bob login as a different user
+
+  >>> print http(r"""
+  ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40login.html HTTP/1.1
+  ... Content-Length: 94
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU
+  ... Referer: http://localhost:8081/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40login.html
+  ... 
+  ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40login.html""")
+  HTTP/1.1 303 See Other
+  ...
+
+When he attempts to do so, the PAU issues a challenge to let bob login 
+as a different user
+
+  >>> print http(r"""
+  ... GET /+ HTTP/1.1
+  ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+We go to the granting interface and search for and find a principal named Bob:
+  >>> print http(r"""
+  ... GET /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU
+  ... Referer: http://localhost:8081/@@contents.html
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 210
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MC51c2Vycw__.selection=dXNlcnNib2I_&field.principal.MC51c2Vycw__.apply=Apply&field.principal.MQ__.query.searchstring=""")
+  HTTP/1.1 200 OK
+  ...
+
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 210
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MC51c2Vycw__.selection=dXNlcnNib2I_&field.principal.MC51c2Vycw__.apply=Apply&field.principal.MQ__.query.searchstring=""")
+  HTTP/1.1 200 OK
+  ...
+
+
+We select Bob and grant him the Manager role:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 5316
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal=dXNlcnMuMQ__"""
+  ... """&field.principal.displayed=y"""
+  ... """&field.principal.MC51c2Vycw__.query.field.search=bob"""
+  ... """&field.principal.MA__.query.searchstring="""
+  ... """&GRANT_SUBMIT=Change"""
+  ... """&field.dXNlcnMuMQ__.role.zope.Manager=allow"""
+  ... """&field.dXNlcnMuMQ__.role.zope.Manager-empty-marker=1""")
+  HTTP/1.1 200 OK
+  ...
+
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 2598
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal=dXNlcnNib2I_&field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MQ__.query.searchstring=&GRANT_SUBMIT=Change&field.dXNlcnNib2I_.role.bugtracker.Admin=unset&field.dXNlcnNib2I_.role.bugtracker.Editor=unset&field.dXNlcnNib2I_.role.bugtracker.User=unset&field.dXNlcnNib2I_.role.zope.Anonymous=unset&field.dXNlcnNib2I_.role.zope.Manager=allow&field.dXNlcnNib2I_.role.zope.Member=unset&field.dXNlcnNib2I_.role.zwiki.Admin=unset&field.dXNlcnNib2I_.role.zwiki.Editor=unset&field.dXNlcnNib2I_.role.zwiki.User=unset&field.dXNlcnNib2I_.permission.bugtracker.AddBug=unset&field.dXNlcnNib2I_.permission.bugtracker.AddAttachment=unset&field.dXNlcnNib2I_.permission.bugtracker.AddComment=unset&field.dXNlcnNib2I_.permission.zwiki.AddWikiPage=unset&field.dXNlcnNib2I_.permission.zwiki.CommentWikiPage=unset&field.dXNlcnNib2I_.permission.zwiki.DeleteWikiPage=unset&field.dXNlcnNib2I_.permission.bugtracker.EditBug=unset&field.dXNlcnNib2I_.permission.zwiki.EditWikiPage=unset&field.dXNlcnNib2I_.permission.bugtracker.ManageBugTracker=unset&field.dXNlcnNib2I_.permission.zwiki.ReparentWikiPage=unset&field.dXNlcnNib2I_.permission.bugtracker.ViewBug=unset&field.dXNlcnNib2I_.permission.bugtracker.ViewBugTracker=unset&field.dXNlcnNib2I_.permission.zwiki.ViewWikiPage=unset&field.dXNlcnNib2I_.permission.zope.AddImages=unset&field.dXNlcnNib2I_.permission.zope.AddSQLScripts=unset&field.dXNlcnNib2I_.permission.zope.Security=unset&field.dXNlcnNib2I_.permission.zope.workflow.CreateProcessInstances=unset&field.dXNlcnNib2I_.permission.zope.ManageApplication=unset&field.dXNlcnNib2I_.permission.zope.ManageCode=unset&field.dXNlcnNib2I_.permission.zope.ManageContent=unset&field.dXNlcnNib2I_.permission.zope.ManagePrincipals=unset&field.dXNlcnNib2I_.permission.zope.ManageBindings=unset&field.dXNlcnNib2I_.permission.zope.ManageServices=unset&field.dXNlcnNib2I_.permission.zope.ManageSite=unset&field.dXNlcnNib2I_.permission.zope.workflow.ManageProcessDefinitions=unset&field.dXNlcnNib2I_.permission.zope.SendMail=unset&field.dXNlcnNib2I_.permission.zope.UndoAllTransactions=unset&field.dXNlcnNib2I_.permission.zope.UndoOwnTransactions=unset&field.dXNlcnNib2I_.permission.zope.workflow.UseProcessInstances=unset&field.dXNlcnNib2I_.permission.zope.View=unset&field.dXNlcnNib2I_.permission.zope.app.apidoc.UseAPIDoc=unset&field.dXNlcnNib2I_.permission.zope.app.dublincore.change=unset&field.dXNlcnNib2I_.permission.zope.app.dublincore.view=unset&field.dXNlcnNib2I_.permission.zope.app.introspector.Introspect=unset&field.dXNlcnNib2I_.permission.zope.app.rdb.Use=unset""")
+  HTTP/1.1 200 OK
+  ...
+
+
+At which point, Bob can access the management interface:
+
+  >>> print http(r"""
+  ... GET /@@contents.html HTTP/1.1
+  ... Authorization: Basic Ym9iOjEyMw==
+  ... """)
+  HTTP/1.1 200 OK
+  ...


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,65 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser"
+    >
+
+  <addform
+      schema="..principalfolder.IInternalPrincipalContainer"
+      label="Add Principal Folder"
+      content_factory="..principalfolder.PrincipalFolder"
+      keyword_arguments="prefix"
+      name="AddPrincipalFolder.html"
+      permission="zope.ManageServices"
+      />
+
+  <addMenuItem
+      title="Principal Folder"
+      description="A Pluggable Persistent Authentication Plugin"
+      class="..principalfolder.PrincipalFolder"
+      permission="zope.ManageServices"
+      view="AddPrincipalFolder.html"
+      />
+
+  <addform
+      schema="..principalfolder.IInternalPrincipal"
+      label="Add Principal Information"
+      content_factory="..principalfolder.InternalPrincipal"
+      arguments="login password title"
+      keyword_arguments="passwordManagerName description"
+      fields="login passwordManagerName password title description"
+      name="AddPrincipalInformation.html"
+      permission="zope.ManageServices"
+      />
+
+  <addMenuItem
+      title="Principal Information"
+      class="..principalfolder.InternalPrincipal"
+      permission="zope.ManageServices"
+      view="AddPrincipalInformation.html"
+      />
+
+  <editform
+      schema="..principalfolder.IInternalPrincipal"
+      label="Change Internal Principal"
+      name="edit.html"
+      fields="login passwordManagerName password title description"
+      permission="zope.ManageServices"
+      menu="zmi_views" title="Edit"
+      />
+
+  <containerViews
+      for="..principalfolder.IInternalPrincipalContainer"
+      add="zope.ManageServices"
+      contents="zope.ManageServices"
+      index="zope.ManageServices"
+      />
+
+  <schemadisplay
+      schema="..principalfolder.IInternalPrincipalContainer"
+      label="Principal Folder Prefix"
+      name="prefix.html"
+      fields="prefix"
+      permission="zope.ManageServices"
+      menu="zmi_views" title="Prefix"
+      />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/principalfolder.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/register.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/register.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/register.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Improved registration UI for registering pluggable authentication utilities
+
+$Id: register.py 73548 2007-03-25 09:05:22Z dobe $
+"""
+
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+import zope.app.component.browser.registration
+import zope.app.security.interfaces
+
+class AddAuthenticationRegistration(
+    zope.app.component.browser.registration.AddUtilityRegistration,
+    ):
+    label = _("Register a pluggable authentication utility")
+    name = ''
+    provided = zope.app.security.interfaces.IAuthentication


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/register.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,110 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Search interface for queriables.
+
+$Id: schemasearch.py 73548 2007-03-25 09:05:22Z dobe $
+"""
+__docformat__ = "reStructuredText"
+
+from zope.interface import implements
+from zope.i18n import translate
+from zope.schema import getFieldsInOrder
+from zope.app.zapi import getName, getPath
+from zope.app.form.utility import setUpWidgets, getWidgetsData
+from zope.app.form.interfaces import IInputWidget
+from zope.app.form.browser.interfaces import ISourceQueryView
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+
+
+search_label = _('search-button', 'Search')
+source_label = _(u"Source path")
+source_title = _(u"Path to the source utility")
+
+class QuerySchemaSearchView(object):
+    implements(ISourceQueryView)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def render(self, name):
+        schema = self.context.schema
+        sourcename = getName(self.context)
+        sourcepath = getPath(self.context)
+        setUpWidgets(self, schema, IInputWidget, prefix=name+'.field')
+        html = []
+
+        # add sub title for source search field
+        html.append('<h4>%s</h4>' % sourcename)
+
+        # start row for path display field
+        html.append('<div class="row">')
+
+        # for each source add path of source
+        html.append('  <div class="label">')
+        label = translate(source_label, context=self.request)
+        title = translate(source_title, context=self.request)
+        html.append('    <label for="%s" title="%s">' % (sourcename, title))
+        html.append('      %s' % label)
+        html.append('    </label>')
+        html.append('  </div>')
+        html.append('  <div class="field">')
+        html.append('      %s' % sourcepath)
+        html.append('  </div>')
+        html.append('</div>')
+
+        # start row for search fields
+        html.append('<div class="row">')
+
+        for field_name, field in getFieldsInOrder(schema):
+            widget = getattr(self, field_name+'_widget')
+
+            # for each field add label...
+            html.append('  <div class="label">')
+            html.append('    <label for="%s" title="%s">'
+                        % (widget.name, widget.hint))
+            html.append('      %s' % widget.label)
+            html.append('    </label>')
+            html.append('  </div>')
+
+            # ...and field widget
+            html.append('  <div class="field">')
+            html.append('    %s' % widget())
+
+            if widget.error():
+                html.append('    <div class="error">')
+                html.append('      %s' % widget.error())
+                html.append('    </div>')
+            html.append('  </div>')
+        # end row
+        html.append('</div>')
+
+        # add search button for search fields
+        html.append('<div class="row">')
+        html.append('  <div class="field">')
+        html.append('    <input type="submit" name="%s" value="%s" />'
+                     % (name+'.search',
+                        translate(search_label, context=self.request)))
+        html.append('  </div>')
+        html.append('</div>')
+
+        return '\n'.join(html)
+
+    def results(self, name):
+        if not (name+'.search' in self.request):
+            return None
+        schema = self.context.schema
+        setUpWidgets(self, schema, IInputWidget, prefix=name+'.field')
+        data = getWidgetsData(self, schema)
+        return self.context.search(data)


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,104 @@
+The Query View for Schema Search Plugins
+========================================
+
+Placefull setup for making the search plugin IPhysicallyLocatable::
+
+  >>> from zope.app.testing import ztapi
+  >>> from zope.schema.interfaces import ITextLine
+  >>> from zope.app.form.browser import TextWidget
+  >>> from zope.app.form.interfaces import IInputWidget
+  >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+  >>> site = placefulSetUp(True)
+  >>> ztapi.browserView(ITextLine, '', TextWidget, providing=IInputWidget)
+
+If a plugin supports `IQuerySchemaSearch`::
+
+  >>> from zope.interface import Interface
+  >>> import zope.schema
+  >>> class ISearchCriteria(Interface):
+  ...     search = zope.schema.TextLine(title=u"Search String")
+
+  >>> from zope.interface import implements
+  >>> class MySearchPlugin:
+  ...     __name__ = 'searchplugin'
+  ...     __parent__ = site
+  ...     schema = ISearchCriteria
+  ...     data = ['foo', 'bar', 'blah']
+  ...
+  ...     def get(self, id):
+  ...         if id in self.data:
+  ...             return {}
+  ...
+  ...     def search(self, query, start=None, batch_size=None):
+  ...         search = query.get('search')
+  ...         if search is not None:
+  ...             i = 0
+  ...             n = 0
+  ...             for value in self.data:
+  ...                 if search in value:
+  ...                     if not ((start is not None and i < start)
+  ...                             or
+  ...                             (batch_size is not None and n > batch_size)):
+  ...                         yield value
+
+then we can get a view::
+
+  >>> from zope.app.authentication.browser.schemasearch \
+  ...     import QuerySchemaSearchView 
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = TestRequest()
+  >>> view = QuerySchemaSearchView(MySearchPlugin(), request)
+
+This allows us to render a search form::
+
+  >>> print view.render('test') # doctest: +NORMALIZE_WHITESPACE
+  <h4>searchplugin</h4>
+  <div class="row">
+    <div class="label">
+      <label for="searchplugin" title="Path to the source utility">
+        Source path
+      </label>
+    </div>
+    <div class="field">
+        /searchplugin
+    </div>
+  </div>
+  <div class="row">
+    <div class="label">
+      <label for="test.field.search" title="">
+        Search String
+      </label>
+    </div>
+    <div class="field">
+      <input class="textType" id="test.field.search" name="test.field.search"
+         size="20" type="text" value=""  />
+    </div>
+  </div>
+  <div class="row">
+    <div class="field">
+      <input type="submit" name="test.search" value="Search" />
+    </div>
+  </div>
+
+If we ask for results::
+
+  >>> view.results('test')
+
+We don't get any, since we did not provide any. But if we give input::
+
+  >>> request.form['test.field.search'] = 'a'
+
+we still don't get any::
+
+  >>> view.results('test')
+
+because we did not press the button. So let's press the button::
+
+  >>> request.form['test.search'] = 'Search'
+
+so that we now get results (!)::
+
+  >>> list(view.results('test'))
+  ['bar', 'blah']
+
+  >>> placefulTearDown()


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/schemasearch.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/session.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/session.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/session.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,34 @@
+<configure 
+    xmlns="http://namespaces.zope.org/browser"
+    >
+
+  <addMenuItem
+      title="Session Credentials Plugin"
+      class="..session.SessionCredentialsPlugin"
+      permission="zope.ManageServices"
+      />
+
+  <editform
+      schema="..session.IBrowserFormChallenger"
+      label="Browser Form Challenger"
+      name="edit.html"
+      permission="zope.ManageServices"
+      menu="zmi_views" title="Edit"
+      />
+
+  <page
+      name="loginForm.html" 
+      for="*"
+      template="loginform.pt"
+      permission="zope.Public" 
+      />
+
+  <page
+      for = "..interfaces.ISessionCredentialsPlugin"
+      name = "activesessions.html"
+      class=".activesessions.ActiveSessions"
+      permission="zope.ManageServices"
+      menu="zmi_views" title="Active Sessions"
+      />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/session.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/special-groups.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/special-groups.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/special-groups.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,376 @@
+Granting to unauthenticated
+===========================
+
+There are 3 special groups:
+
+- Everybody, that everybody belongs to,
+
+- Unauthenticated, that unauthenticated users belong to, and
+
+- Authenticating, that authenticated users belong to.
+
+Here's an example:
+
+First, we'll set up a pluggable authentication utility containing a
+principal folder, which we'll create first.
+
+
+
+Create pluggable authentication utility and register it.
+
+  >>> 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"""
+  ... 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 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
+  ...
+
+Add a principal to it:
+
+  >>> 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"
+  ... 
+  ... bob
+  ... -----------------------------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"
+  ... 
+  ... bob
+  ... -----------------------------5110544421083023415453147877--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+Configure PAU, with registered principal folder plugin.
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 1038
+  ... Content-Type: multipart/form-data; boundary=---------------------------6519411471194050603270010787
+  ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs
+  ... Referer: http://localhost:8081/++etc++site/default/PAU/@@configure.html
+  ... 
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.credentialsPlugins.to"
+  ... 
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
+  ... 
+  ... 
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
+  ... 
+  ... dXNlcnM=
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
+  ... 
+  ... 
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Change
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.credentialsPlugins"
+  ... 
+  ... U2Vzc2lvbiBDcmVkZW50aWFscw==
+  ... -----------------------------6519411471194050603270010787
+  ... Content-Disposition: form-data; name="field.authenticatorPlugins"
+  ... 
+  ... dXNlcnM=
+  ... -----------------------------6519411471194050603270010787--
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+Normally, the anonymous role has view, we'll deny it:
+
+  >>> print http(r"""
+  ... POST /++etc++site/AllRolePermissions.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... 
+  ... role_id=zope.Anonymous""" 
+  ... """&Deny%3Alist=zope.View""" 
+  ... """&Deny%3Alist=zope.app.dublincore.view""" 
+  ... """&SUBMIT_ROLE=Save+Changes""")
+  HTTP/1.1 200 OK
+  ...
+
+Now, if we try to access the main page as an anonymous user, 
+we'll be unauthorized:
+
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+  ...
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+We'll even be unauthorized if we try to access it as bob:
+
+  >>> print http(r"""
+  ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html HTTP/1.1
+  ... Content-Length: 94
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a60902=cxcKJetHJjB2Px2umkzvTjeVI1E3aOpirHSjOYlxUPF.VX9DNjybrE
+  ... Referer: http://localhost:8081/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html
+  ... 
+  ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html""")
+  ...
+  HTTP/1.1 303 See Other
+  ...
+
+
+No, let's grant view to the authenticated group:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... 
+  ... field.principal=em9wZS5BdXRoZW50aWNhdGVk&field.principal.displayed=y"""
+  ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.View=allow"""
+  ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.app.dublincore.view=allow"""
+  ... """&GRANT_SUBMIT=Change""")
+  HTTP/1.1 200 OK
+  ...
+
+Now, with this, we can access the main page as bob, but not as an
+anonymous user:
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... Authorization: Basic bob:123
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+  HTTP/1.1 200 OK 
+  ...
+
+###401 Unauthorized
+
+
+Now, we'll grant to unauthenticated:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal=em9wZS5Bbnlib2R5"""
+  ... """&field.em9wZS5Bbnlib2R5.permission.zope.View=allow"""
+  ... """&field.em9wZS5Bbnlib2R5.permission.zope.app.dublincore.view=allow"""
+  ... """&GRANT_SUBMIT=Change""")
+  HTTP/1.1 200 OK
+  ...
+
+With this, we can access the page as either bob or anonymous:
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... Authorization: Basic bob:123
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+
+Now, we'll remove the authenticated group grant:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... 
+  ... field.principal=em9wZS5BdXRoZW50aWNhdGVk"""
+  ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.View=unset"""
+  ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.app.dublincore.view=unset"""
+  ... """&GRANT_SUBMIT=Change""")
+  HTTP/1.1 200 OK
+  ...
+
+And anonymous people will be able to access the page, but bob won't be able to:
+
+  >>> print http(r"""
+  ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html HTTP/1.1
+  ... Content-Length: 94
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a60902=cxcKJetHJjB2Px2umkzvTjeVI1E3aOpirHSjOYlxUPF.VX9DNjybrE
+  ... Referer: http://localhost:8081/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html
+  ... 
+  ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html""")
+  ...
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+
+Now, we'll remove the unauthenticated group grant:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Referer: http://localhost:8081/@@grant.html
+  ... 
+  ... field.principal=em9wZS5Bbnlib2R5"""
+  ... """&field.em9wZS5Bbnlib2R5.permission.zope.View=unset"""
+  ... """&field.em9wZS5Bbnlib2R5.permission.zope.app.dublincore.view=unset"""
+  ... """&GRANT_SUBMIT=Change""")
+  HTTP/1.1 200 OK
+  ...
+
+  >>> print http(r"""
+  ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html HTTP/1.1
+  ... Content-Length: 94
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Cookie: zope3_cs_6a60902=cxcKJetHJjB2Px2umkzvTjeVI1E3aOpirHSjOYlxUPF.VX9DNjybrE
+  ... Referer: http://localhost:8081/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html
+  ... 
+  ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%3A8081%2F%40%40index.html""")
+  ...
+  HTTP/1.1 303 See Other
+  ...
+
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+
+
+
+Finally, we'll grant to everybody:
+
+  >>> print http(r"""
+  ... POST /@@grant.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... 
+  ... field.principal=em9wZS5FdmVyeWJvZHk_"""
+  ... """&field.em9wZS5FdmVyeWJvZHk_.permission.zope.View=allow"""
+  ... """&field.em9wZS5FdmVyeWJvZHk_.permission.zope.app.dublincore.view=allow"""
+  ... """&GRANT_SUBMIT=Change""", handle_errors = False)
+  HTTP/1.1 200 OK
+  ...
+
+and both bob nor anonymous can access:
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... Authorization: Basic bob:123
+  ... """)
+  HTTP/1.1 200 OK
+  ...
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+  HTTP/1.1 200 OK
+  ...


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/special-groups.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/tests.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/tests.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/tests.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,161 @@
+##############################################################################
+#
+# 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 Service Tests
+
+$Id: tests.py 75772 2007-05-15 18:21:29Z hdima $
+"""
+
+__docformat__ = "reStructuredText"
+
+import re
+import unittest
+from zope.testing import renormalizing, doctest
+from zope.app.testing.setup import placefulSetUp, placefulTearDown
+import transaction
+from zope.interface import directlyProvides
+from zope.exceptions.interfaces import UserError
+from zope.app.testing import functional
+from zope.app.authentication.principalfolder import PrincipalFolder
+from zope.app.authentication.principalfolder import Principal
+from zope.app.authentication.principalfolder import IInternalPrincipal
+from zope.app.authentication.testing import AppAuthenticationLayer
+
+
+def schemaSearchSetUp(self):
+    placefulSetUp(site=True)
+
+def schemaSearchTearDown(self):
+    placefulTearDown()
+
+class FunkTest(functional.BrowserTestCase):
+
+    def test_copypaste_duplicated_id_object(self):
+
+        root = self.getRootFolder()
+
+        # Create a principal Folder
+        root['pf'] = PrincipalFolder()
+        pf = root['pf']
+
+        # Create a principal with p1 as login
+        principal = Principal('p1')
+        principal.login = 'p1'
+        directlyProvides(principal, IInternalPrincipal)
+
+        pf['p1'] = principal
+
+        transaction.commit()
+        self.assertEqual(len(pf.keys()), 1)
+        #raise str([x for x in pf.keys()])
+
+        response = self.publish('/pf/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'ids': [u'p1'],
+                                      'container_copy_button': u'Copy'})
+        self.assertEqual(response.getStatus(), 302)
+
+
+        # Try to paste the file
+        try:
+            response = self.publish('/pf/@@contents.html',
+                                    basic='mgr:mgrpw',
+                                    form={'container_paste_button': ''})
+        except UserError, e:
+            self.assertEqual(
+                str(e),
+                "The given name(s) [u'p1'] is / are already being used")
+        else:
+            # test failed !
+            self.asserEqual(1, 0)
+
+    def test_cutpaste_duplicated_id_object(self):
+
+        root = self.getRootFolder()
+
+        # Create a principal Folder
+        root['pf'] = PrincipalFolder()
+        pf = root['pf']
+
+        # Create a principal with p1 as login
+        principal = Principal('p1')
+        principal.login = 'p1'
+        directlyProvides(principal, IInternalPrincipal)
+
+        pf['p1'] = principal
+
+        transaction.commit()
+        self.assertEqual(len(pf.keys()), 1)
+        #raise str([x for x in pf.keys()])
+
+        response = self.publish('/pf/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'ids': [u'p1'],
+                                      'container_cut_button': u'Cut'})
+        self.assertEqual(response.getStatus(), 302)
+
+
+        # Try to paste the file
+        try:
+            response = self.publish('/pf/@@contents.html',
+                                    basic='mgr:mgrpw',
+                                    form={'container_paste_button': ''})
+        except UserError, e:
+            self.assertEqual(
+                str(e),
+                "The given name(s) [u'p1'] is / are already being used")
+        else:
+            # test failed !
+            self.asserEqual(1, 0)
+
+
+checker = renormalizing.RENormalizing([
+    (re.compile(r"HTTP/1\.1 200 .*"), "HTTP/1.1 200 OK"),
+    (re.compile(r"HTTP/1\.1 303 .*"), "HTTP/1.1 303 See Other"),
+    (re.compile(r"HTTP/1\.1 401 .*"), "HTTP/1.1 401 Unauthorized"),
+    ])
+
+
+def test_suite():
+    FunkTest.layer = AppAuthenticationLayer
+    principalfolder = functional.FunctionalDocFileSuite(
+        'principalfolder.txt', checker=checker)
+    principalfolder.layer = AppAuthenticationLayer
+    groupfolder = functional.FunctionalDocFileSuite(
+        'groupfolder.txt', checker=checker)
+    groupfolder.layer = AppAuthenticationLayer
+    pau_prefix_and_searching = functional.FunctionalDocFileSuite(
+        'pau_prefix_and_searching.txt', checker=checker)
+    pau_prefix_and_searching.layer = AppAuthenticationLayer
+    group_searching_with_empty_string = functional.FunctionalDocFileSuite(
+        'group_searching_with_empty_string.txt', checker=checker)
+    group_searching_with_empty_string.layer = AppAuthenticationLayer
+    special_groups = functional.FunctionalDocFileSuite(
+        'special-groups.txt', checker=checker)
+    special_groups.layer = AppAuthenticationLayer
+    issue663 = functional.FunctionalDocFileSuite('issue663.txt')
+    issue663.layer = AppAuthenticationLayer
+    return unittest.TestSuite((
+        principalfolder,
+        groupfolder,
+        pau_prefix_and_searching,
+        group_searching_with_empty_string,
+        special_groups,
+        unittest.makeSuite(FunkTest),
+        issue663,
+        doctest.DocFileSuite('schemasearch.txt'),
+        ))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/browser/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/configure.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/configure.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/configure.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,69 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    i18n_domain="zope"
+    >
+
+  <class class=".PluggableAuthentication">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageSite"
+        interface=".interfaces.IPluggableAuthentication"
+        set_schema=".interfaces.IPluggableAuthentication"
+        />
+    <require
+        permission="zope.ManageServices"
+        attributes="registrationManager"
+        />
+  </class>
+
+  <adapter
+      for=".interfaces.IQuerySchemaSearch
+           .interfaces.IPluggableAuthentication"
+      provides=".interfaces.IQueriableAuthenticator"
+      factory=".authentication.QuerySchemaSearchAdapter"
+      />
+
+  <utility
+      component=".vocabulary.credentialsPlugins"
+      name="CredentialsPlugins"
+      />
+
+  <utility
+      component=".vocabulary.authenticatorPlugins"
+      name="AuthenticatorPlugins"
+      />
+
+  <utility
+      name="No Challenge if Authenticated"
+      factory=".generic.NoChallengeCredentialsPlugin"
+      provides=".interfaces.ICredentialsPlugin"
+      />
+
+  <!-- Registering documentation with API doc -->
+  <configure
+      xmlns:apidoc="http://namespaces.zope.org/apidoc"
+      xmlns:zcml="http://namespaces.zope.org/zcml"
+      zcml:condition="have apidoc">
+
+    <apidoc:bookchapter
+        id="authentication"
+        title="Pluggable Authentication"
+        doc_path="README.txt"
+        parent="security"
+        />
+
+  </configure>
+
+  <include file="password.zcml" />
+  <include file="session.zcml" />
+  <include file="httpplugins.zcml" />
+  <include file="principalfolder.zcml" />
+  <include file="groupfolder.zcml" />
+  <include file="ftpplugins.zcml" />
+
+  <include package=".browser" />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftesting.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftesting.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftesting.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,70 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   i18n_domain="zope"
+   package="zope.app.authentication"
+   >
+
+  <!-- This file is the equivalent of site.zcml and it is -->
+  <!-- used for functional testing setup -->
+
+  <include package="zope.app.securitypolicy" file="meta.zcml" />
+
+  <include package="zope.app.zcmlfiles" />
+  <include package="zope.formlib" />
+  <include package="zope.app.authentication" />
+  <include package="zope.app.session" />
+  <include package="zope.app.securitypolicy" />
+
+  <securityPolicy
+    component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+
+  <role id="zope.Anonymous" title="Everybody"
+                 description="All users have this role implicitly" />
+  <role id="zope.Manager" title="Site Manager" />
+
+  <!-- Replace the following directive if you don't want public access -->
+  <grant permission="zope.View"
+                  role="zope.Anonymous" />
+
+  <grantAll role="zope.Manager" />
+
+  <include package="zope.app.securitypolicy.tests" file="functional.zcml" />
+
+  <!-- Principals -->
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User" />
+
+  <unauthenticatedGroup
+    id="zope.Anybody"
+    title="Unauthenticated Users"
+    />
+
+  <authenticatedGroup
+    id="zope.Authenticated"
+    title="Authenticated Users"
+    />
+
+  <everybodyGroup
+    id="zope.Everybody"
+    title="All Users"
+    />
+
+  <!-- Principal that tests generally run as -->
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw" />
+
+  <!-- Bootstrap principal used to make local grant to the principal above -->
+  <principal
+      id="zope.globalmgr"
+      title="Manager"
+      login="globalmgr"
+      password="globalmgrpw" />
+
+  <grant role="zope.Manager" principal="zope.globalmgr" />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,65 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""PAS plugins related to FTP
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.interface import implements
+from zope.publisher.interfaces.ftp import IFTPRequest
+
+from zope.app.authentication import interfaces
+
+class FTPCredentialsPlugin(object):
+
+    implements(interfaces.ICredentialsPlugin)
+
+    def extractCredentials(self, request):
+        """Extracts the FTP credentials from a request.
+
+        First we need to create a FTP request that contains some credentials.
+        Note the path is a required in the envirnoment.
+
+          >>> from zope.publisher.ftp import FTPRequest
+          >>> from StringIO import StringIO
+          >>> request = FTPRequest(StringIO(''),
+          ...                      {'credentials': ('bob', '123'),
+          ...                       'path': '/a/b/c'})
+
+        Now we create the plugin and get the credentials.
+
+          >>> plugin = FTPCredentialsPlugin()
+          >>> plugin.extractCredentials(request)
+          {'login': u'bob', 'password': u'123'}
+
+        This only works for FTPRequests.
+
+          >>> from zope.publisher.base import TestRequest
+          >>> print plugin.extractCredentials(TestRequest('/'))
+          None
+
+        """
+        if not IFTPRequest.providedBy(request):
+            return None
+
+        if request._auth:
+            login, password = request._auth
+            return {'login': login.decode('utf-8'),
+                    'password': password.decode('utf-8')}
+        return None
+
+    def challenge(self, request):
+        return False
+
+    def logout(self, request):
+        return False


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,12 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope"
+    >
+
+  <utility
+      name="FTP Credentials"
+      provides=".interfaces.ICredentialsPlugin"
+      factory=".ftpplugins.FTPCredentialsPlugin"
+      />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/ftpplugins.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/generic.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/generic.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/generic.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,95 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Generic PAS Plugins
+
+$Id: generic.py 70031 2006-09-07 14:02:13Z flox $
+"""
+__docformat__ = "reStructuredText"
+from zope.interface import implements
+
+from zope.app.security.interfaces import IUnauthenticatedPrincipal
+
+from zope.app.authentication import interfaces
+
+
+class NoChallengeCredentialsPlugin(object):
+    """A plugin that doesn't challenge if the principal is authenticated.
+
+    There are two reasonable ways to handle an unauthorized error for an
+    authenticated principal:
+
+      - Inform the user of the unauthorized error
+
+      - Let the user login with a different set of credentials
+
+    Since either approach is reasonable, we need to give the site manager
+    some way of specifying one of the two policies.
+
+    By default, a user will be challenged for a new set of credentials if
+    unauthorized. A site manager can insert this plugin in the front of the
+    plugin list to prevent that challenge from occurring. This will
+    typically result in an 'Unauthorized' message to the user.
+
+    The 'challenge' behavior of the plugin is simple. To illustrate, we'll
+    create a plugin:
+
+      >>> challenger = NoChallengeCredentialsPlugin()
+
+    and a test request with an authenticated principal:
+
+      >>> from zope.publisher.browser import TestRequest
+      >>> request = TestRequest()
+      >>> IUnauthenticatedPrincipal.providedBy(request.principal)
+      False
+
+    When we challenge using the plugin:
+
+      >>> challenger.challenge(request)
+      True
+
+    we get a value that signals the PAU that this plugin successfully
+    challenged the user (even though it actually did nothing). The PAU
+    will stop trying to challenge and the user will not get a chance to
+    provide different credentials. The result is typically an error message.
+
+    On the other hand, if the user is unauthenticated:
+
+      >>> class Principal(object):
+      ...     implements(IUnauthenticatedPrincipal)
+      >>> request.setPrincipal(Principal())
+      >>> IUnauthenticatedPrincipal.providedBy(request.principal)
+      True
+
+    the plugin challenge will return None:
+
+      >>> print challenger.challenge(request)
+      None
+
+    signaling the PAU that it should try the next plugin for a challenge. If
+    the PAU is configured properly, the user will receive a challenge and be
+    allowed to provide different credentials.
+    """
+    implements(interfaces.ICredentialsPlugin)
+
+    def extractCredentials(self, request):
+        return None
+
+    def challenge(self, request):
+        if not IUnauthenticatedPrincipal.providedBy(request.principal):
+            return True
+        return None
+
+    def logout(self, request):
+        return False
+


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/generic.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,362 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Zope Groups Folder implementation
+
+$Id: groupfolder.py 73548 2007-03-25 09:05:22Z dobe $
+
+"""
+import BTrees.OOBTree
+import persistent
+
+from zope import interface, event, schema, component
+from zope.interface import alsoProvides
+from zope.security.interfaces import (
+    IGroup, IGroupAwarePrincipal, IMemberAwareGroup)
+
+from zope.app import zapi
+from zope.app.container.btree import BTreeContainer
+import zope.app.container.constraints
+import zope.app.container.interfaces
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+import zope.app.security.vocabulary
+from zope.app.security.interfaces import IAuthenticatedGroup, IEveryoneGroup
+from zope.app.authentication import principalfolder, interfaces
+
+
+class IGroupInformation(interface.Interface):
+
+    title = schema.TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the permission."),
+        required=True)
+
+    description = schema.Text(
+        title=_("Description"),
+        description=_("Provides a description for the permission."),
+        required=False)
+
+    principals = schema.List(
+        title=_("Principals"),
+        value_type=schema.Choice(
+            source=zope.app.security.vocabulary.PrincipalSource()),
+        description=_(
+        "List of ids of principals which belong to the group"),
+        required=False)
+
+
+class IGroupFolder(zope.app.container.interfaces.IContainer):
+
+    zope.app.container.constraints.contains(IGroupInformation)
+
+    prefix = schema.TextLine(
+        title=_("Group ID prefix"),
+        description=_("Prefix added to IDs of groups in this folder"),
+        readonly=True,
+        )
+
+    def getGroupsForPrincipal(principalid):
+        """Get groups the given principal belongs to"""
+
+    def getPrincipalsForGroup(groupid):
+        """Get principals which belong to the group"""
+
+
+class IGroupContained(zope.app.container.interfaces.IContained):
+
+    zope.app.container.constraints.containers(IGroupFolder)
+
+class IGroupSearchCriteria(interface.Interface):
+
+    search = schema.TextLine(
+        title=_("Group Search String"),
+        required=False,
+        missing_value=u'',
+        )
+
+class IGroupPrincipalInfo(interfaces.IPrincipalInfo):
+    members = interface.Attribute('an iterable of members of the group')
+
+class GroupInfo(object):
+    """An implementation of IPrincipalInfo used by the group folder.
+
+    A group info is created with id, title, and description:
+
+      >>> class DemoGroupInformation(object):
+      ...     interface.implements(IGroupInformation)
+      ...     def __init__(self, title, description, principals):
+      ...         self.title = title
+      ...         self.description = description
+      ...         self.principals = principals
+      ...
+      >>> i = DemoGroupInformation(
+      ...     'Managers', 'Taskmasters', ('joe', 'jane'))
+      ...
+      >>> info = GroupInfo('groups.managers', i)
+      >>> info
+      GroupInfo('groups.managers')
+      >>> info.id
+      'groups.managers'
+      >>> info.title
+      'Managers'
+      >>> info.description
+      'Taskmasters'
+      >>> info.members
+      ('joe', 'jane')
+      >>> info.members = ('joe', 'jane', 'jaime')
+      >>> info.members
+      ('joe', 'jane', 'jaime')
+
+    """
+    interface.implements(IGroupPrincipalInfo)
+
+    def __init__(self, id, information):
+        self.id = id
+        self._information = information
+
+    @property
+    def title(self):
+        return self._information.title
+
+    @property
+    def description(self):
+        return self._information.description
+
+    @apply
+    def members():
+        def get(self):
+            return self._information.principals
+        def set(self, value):
+            self._information.principals = value
+        return property(get, set)
+
+    def __repr__(self):
+        return 'GroupInfo(%r)' % self.id
+
+
+class GroupFolder(BTreeContainer):
+
+    interface.implements(
+        interfaces.IAuthenticatorPlugin,
+        interfaces.IQuerySchemaSearch,
+        IGroupFolder)
+
+    schema = IGroupSearchCriteria
+
+    def __init__(self, prefix=u''):
+        self.prefix=prefix
+        super(BTreeContainer,self).__init__()
+        # __inversemapping is used to map principals to groups
+        self.__inverseMapping = BTrees.OOBTree.OOBTree()
+
+    def __setitem__(self, name, value):
+        BTreeContainer.__setitem__(self, name, value)
+        group_id = self._groupid(value)
+        self._addPrincipalsToGroup(value.principals, group_id)
+        if value.principals:
+            event.notify(
+                interfaces.PrincipalsAddedToGroup(
+                    value.principals, self.__parent__.prefix + group_id))
+        group = principalfolder.Principal(self.prefix + name)
+        event.notify(interfaces.GroupAdded(group))
+
+    def __delitem__(self, name):
+        value = self[name]
+        group_id = self._groupid(value)
+        self._removePrincipalsFromGroup(value.principals, group_id)
+        if value.principals:
+            event.notify(
+                interfaces.PrincipalsRemovedFromGroup(
+                    value.principals, self.__parent__.prefix + group_id))
+        BTreeContainer.__delitem__(self, name)
+
+    def _groupid(self, group):
+        return self.prefix+group.__name__
+
+    def _addPrincipalsToGroup(self, principal_ids, group_id):
+        for principal_id in principal_ids:
+            self.__inverseMapping[principal_id] = (
+                self.__inverseMapping.get(principal_id, ())
+                + (group_id,))
+
+    def _removePrincipalsFromGroup(self, principal_ids, group_id):
+        for principal_id in principal_ids:
+            groups = self.__inverseMapping.get(principal_id)
+            if groups is None:
+                return
+            new = tuple([id for id in groups if id != group_id])
+            if new:
+                self.__inverseMapping[principal_id] = new
+            else:
+                del self.__inverseMapping[principal_id]
+
+    def getGroupsForPrincipal(self, principalid):
+        """Get groups the given principal belongs to"""
+        return self.__inverseMapping.get(principalid, ())
+
+    def getPrincipalsForGroup(self, groupid):
+        """Get principals which belong to the group"""
+        return self[groupid].principals
+
+    def search(self, query, start=None, batch_size=None):
+        """ Search for groups"""
+        search = query.get('search')
+        if search is not None:
+            n = 0
+            search = search.lower()
+            for i, (id, groupinfo) in enumerate(self.items()):
+                if (search in groupinfo.title.lower() or
+                    (groupinfo.description and 
+                     search in groupinfo.description.lower())):
+                    if not ((start is not None and i < start)
+                            or
+                            (batch_size is not None and n >= batch_size)):
+                        n += 1
+                        yield self.prefix + id
+
+    def authenticateCredentials(self, credentials):
+        # user folders don't authenticate
+        pass
+
+    def principalInfo(self, id):
+        if id.startswith(self.prefix):
+            id = id[len(self.prefix):]
+            info = self.get(id)
+            if info is not None:
+                return GroupInfo(
+                    self.prefix+id, info)
+
+class GroupCycle(Exception):
+    """There is a cyclic relationship among groups
+    """
+
+class InvalidPrincipalIds(Exception):
+    """A user has a group id for a group that can't be found
+    """
+
+class InvalidGroupId(Exception):
+    """A user has a group id for a group that can't be found
+    """
+
+def nocycles(principal_ids, seen, getPrincipal):
+    for principal_id in principal_ids:
+        if principal_id in seen:
+            raise GroupCycle(principal_id, seen)
+        seen.append(principal_id)
+        principal = getPrincipal(principal_id)
+        nocycles(principal.groups, seen, getPrincipal)
+        seen.pop()
+
+class GroupInformation(persistent.Persistent):
+
+    interface.implements(IGroupInformation, IGroupContained)
+
+    __parent__ = __name__ = None
+
+    _principals = ()
+
+    def __init__(self, title='', description=''):
+        self.title = title
+        self.description = description
+
+    def setPrincipals(self, prinlist, check=True):
+        # method is not a part of the interface
+        parent = self.__parent__
+        old = self._principals
+        self._principals = tuple(prinlist)
+
+        if parent is not None:
+            oldset = set(old)
+            new = set(prinlist)
+            group_id = parent._groupid(self)
+            removed = oldset - new
+            added = new - oldset
+            try:
+                parent._removePrincipalsFromGroup(removed, group_id)
+            except AttributeError:
+                removed = None
+
+            try:
+                parent._addPrincipalsToGroup(added, group_id)
+            except AttributeError:
+                added = None
+
+            if check:
+                try:
+                    nocycles(new, [], zapi.principals().getPrincipal)
+                except GroupCycle:
+                    # abort
+                    self.setPrincipals(old, False)
+                    raise
+            # now that we've gotten past the checks, fire the events.
+            if removed:
+                event.notify(
+                    interfaces.PrincipalsRemovedFromGroup(
+                        removed, self.__parent__.__parent__.prefix + group_id))
+            if added:
+                event.notify(
+                    interfaces.PrincipalsAddedToGroup(
+                        added, self.__parent__.__parent__.prefix + group_id))
+
+    principals = property(lambda self: self._principals, setPrincipals)
+
+
+def specialGroups(event):
+    principal = event.principal
+    if (IGroup.providedBy(principal) or
+        not IGroupAwarePrincipal.providedBy(principal)):
+        return
+
+    everyone = component.queryUtility(IEveryoneGroup)
+    if everyone is not None:
+        principal.groups.append(everyone.id)
+
+    auth = component.queryUtility(IAuthenticatedGroup)
+    if auth is not None:
+        principal.groups.append(auth.id)
+
+
+def setGroupsForPrincipal(event):
+    """Set group information when a principal is created"""
+
+    principal = event.principal
+    if not IGroupAwarePrincipal.providedBy(principal):
+        return
+
+    authentication = event.authentication
+
+    for name, plugin in authentication.getAuthenticatorPlugins():
+        if not IGroupFolder.providedBy(plugin):
+            continue
+        groupfolder = plugin
+        principal.groups.extend(
+            [authentication.prefix + id
+             for id in groupfolder.getGroupsForPrincipal(principal.id)
+             ])
+        id = principal.id
+        prefix = authentication.prefix + groupfolder.prefix
+        if id.startswith(prefix) and id[len(prefix):] in groupfolder:
+            alsoProvides(principal, IGroup)
+
+ at component.adapter(interfaces.IFoundPrincipalCreated)
+def setMemberSubscriber(event):
+    """adds `getMembers`, `setMembers` to groups made from IGroupPrincipalInfo.
+    """
+    info = event.info
+    if IGroupPrincipalInfo.providedBy(info):
+        principal = event.principal
+        principal.getMembers = lambda : info.members
+        def setMembers(value):
+            info.members = value
+        principal.setMembers = setMembers
+        alsoProvides(principal, IMemberAwareGroup)


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,427 @@
+=============
+Group Folders
+=============
+
+Group folders provide support for groups information stored in the ZODB.  They
+are persistent, and must be contained within the PAUs that use them.
+
+Like other principals, groups are created when they are needed.
+
+Group folders contain group-information objects that contain group information.
+We create group information using the `GroupInformation` class:
+
+  >>> import zope.app.authentication.groupfolder
+  >>> g1 = zope.app.authentication.groupfolder.GroupInformation("Group 1")
+
+  >>> groups = zope.app.authentication.groupfolder.GroupFolder('group.')
+  >>> groups['g1'] = g1
+
+Note that when group-info is added, a GroupAdded event is generated:
+
+  >>> from zope.app.authentication import interfaces
+  >>> from zope.component.eventtesting import getEvents
+  >>> getEvents(interfaces.IGroupAdded)
+  [<GroupAdded 'group.g1'>]
+
+Groups are defined with respect to an authentication service.  Groups
+must be accessible via an authentication service and can contain
+principals accessible via an authentication service.
+
+To illustrate the group interaction with the authentication service,
+we'll create a sample authentication service:
+
+  >>> from zope import interface
+  >>> from zope.app.security.interfaces import IAuthentication
+  >>> from zope.app.security.interfaces import PrincipalLookupError
+  >>> from zope.security.interfaces import IGroupAwarePrincipal
+  >>> from zope.app.authentication.groupfolder import setGroupsForPrincipal
+
+  >>> class Principal:
+  ...     interface.implements(IGroupAwarePrincipal)
+  ...     def __init__(self, id, title='', description=''):
+  ...         self.id, self.title, self.description = id, title, description
+  ...         self.groups = []
+
+  >>> class PrincipalCreatedEvent:
+  ...     def __init__(self, authentication, principal):
+  ...         self.authentication = authentication
+  ...         self.principal = principal
+
+  >>> from zope.app.authentication import principalfolder
+
+  >>> class Principals:
+  ...
+  ...     interface.implements(IAuthentication)
+  ...
+  ...     def __init__(self, groups, prefix='auth.'):
+  ...         self.prefix = prefix
+  ...         self.principals = {
+  ...            'p1': principalfolder.PrincipalInfo('p1', '', '', ''),
+  ...            'p2': principalfolder.PrincipalInfo('p2', '', '', ''),
+  ...            'p3': principalfolder.PrincipalInfo('p3', '', '', ''),
+  ...            'p4': principalfolder.PrincipalInfo('p4', '', '', ''),
+  ...            }
+  ...         self.groups = groups
+  ...         groups.__parent__ = self
+  ...
+  ...     def getAuthenticatorPlugins(self):
+  ...         return [('principals', self.principals), ('groups', self.groups)]
+  ...
+  ...     def getPrincipal(self, id):
+  ...         if not id.startswith(self.prefix):
+  ...             raise PrincipalLookupError(id)
+  ...         id = id[len(self.prefix):]
+  ...         info = self.principals.get(id)
+  ...         if info is None:
+  ...             info = self.groups.principalInfo(id)
+  ...             if info is None:
+  ...                raise PrincipalLookupError(id)
+  ...         principal = Principal(self.prefix+info.id, 
+  ...                               info.title, info.description)
+  ...         setGroupsForPrincipal(PrincipalCreatedEvent(self, principal))
+  ...         return principal
+
+This class doesn't really implement the full `IAuthentication` interface, but
+it implements the `getPrincipal` method used by groups. It works very much
+like the pluggable authentication utility.  It creates principals on demand. It
+calls `setGroupsForPrincipal`, which is normally called as an event subscriber,
+when principals are created. In order for `setGroupsForPrincipal` to find out
+group folder, we have to register it as a utility:
+
+  >>> from zope.app.testing import ztapi
+  >>> from zope.app.authentication.interfaces import IAuthenticatorPlugin
+  >>> ztapi.provideUtility(IAuthenticatorPlugin, groups)
+
+We will create and register a new principals utility:
+
+  >>> principals = Principals(groups)
+  >>> ztapi.provideUtility(IAuthentication, principals)
+
+Now we can set the principals on the group:
+
+  >>> g1.principals = ['auth.p1', 'auth.p2']
+  >>> g1.principals
+  ('auth.p1', 'auth.p2')
+
+Adding principals fires an event.
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup ['auth.p1', 'auth.p2'] u'auth.group.g1'>
+
+We can now look up groups for the principals:
+
+  >>> groups.getGroupsForPrincipal('auth.p1')
+  (u'group.g1',)
+
+Note that the group id is a concatenation of the group-folder prefix
+and the name of the group-information object within the folder.
+
+If we delete a group:
+
+  >>> del groups['g1']
+
+then the groups folder loses the group information for that group's
+principals:
+
+  >>> groups.getGroupsForPrincipal('auth.p1')
+  ()
+
+but the principal information on the group is unchanged:
+
+  >>> g1.principals
+  ('auth.p1', 'auth.p2')
+
+It also fires an event showing that the principals are removed from the group
+(g1 is group information, not a zope.security.interfaces.IGroup).
+
+  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+  <PrincipalsRemovedFromGroup ['auth.p1', 'auth.p2'] u'auth.group.g1'>
+
+Adding the group sets the folder principal information.  Let's use a
+different group name:
+
+  >>> groups['G1'] = g1
+
+  >>> groups.getGroupsForPrincipal('auth.p1')
+  (u'group.G1',)
+
+Here we see that the new name is reflected in the group information.
+
+An event is fired, as usual.
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup ['auth.p1', 'auth.p2'] u'auth.group.G1'>
+
+In terms of member events (principals added and removed from groups), we have
+now seen that events are fired when a group information object is added and
+when it is removed from a group folder; and we have seen that events are fired
+when a principal is added to an already-registered group.  Events are also
+fired when a principal is removed from an already-registered group.  Let's
+quickly see some more examples.
+
+  >>> g1.principals = ('auth.p1', 'auth.p3', 'auth.p4')
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup ['auth.p3', 'auth.p4'] u'auth.group.G1'>
+  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+  <PrincipalsRemovedFromGroup ['auth.p2'] u'auth.group.G1'>
+  >>> g1.principals = ('auth.p1', 'auth.p2')
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup ['auth.p2'] u'auth.group.G1'>
+  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+  <PrincipalsRemovedFromGroup ['auth.p3', 'auth.p4'] u'auth.group.G1'>
+
+Groups can contain groups:
+
+  >>> g2 = zope.app.authentication.groupfolder.GroupInformation("Group Two")
+  >>> groups['G2'] = g2
+  >>> g2.principals = ['auth.group.G1']
+
+  >>> groups.getGroupsForPrincipal('auth.group.G1')
+  (u'group.G2',)
+
+  >>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  >>> old
+  <PrincipalsAddedToGroup ['auth.group.G1'] u'auth.group.G2'>
+
+Groups cannot contain cycles:
+
+  >>> g1.principals = ('auth.p1', 'auth.p2', 'auth.group.G2')
+  ... # doctest: +NORMALIZE_WHITESPACE
+  Traceback (most recent call last):
+  ...
+  GroupCycle: (u'auth.group.G1', 
+               ['auth.p2', u'auth.group.G1', u'auth.group.G2'])
+
+Trying to do so does not fire an event.
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old
+  True
+
+They need not be hierarchical:
+
+  >>> ga = zope.app.authentication.groupfolder.GroupInformation("Group A")
+  >>> groups['GA'] = ga
+
+  >>> gb = zope.app.authentication.groupfolder.GroupInformation("Group B")
+  >>> groups['GB'] = gb
+  >>> gb.principals = ['auth.group.GA']
+
+  >>> gc = zope.app.authentication.groupfolder.GroupInformation("Group C")
+  >>> groups['GC'] = gc
+  >>> gc.principals = ['auth.group.GA']
+
+  >>> gd = zope.app.authentication.groupfolder.GroupInformation("Group D")
+  >>> groups['GD'] = gd
+  >>> gd.principals = ['auth.group.GA', 'auth.group.GB']
+
+  >>> ga.principals = ['auth.p1']
+
+Group folders provide a very simple search interface.  They perform
+simple string searches on group titles and descriptions.
+
+  >>> list(groups.search({'search': 'grou'})) # doctest: +NORMALIZE_WHITESPACE
+  [u'group.G1', u'group.G2',
+   u'group.GA', u'group.GB', u'group.GC', u'group.GD']
+
+  >>> list(groups.search({'search': 'two'}))
+  [u'group.G2']
+
+They also support batching:
+
+  >>> list(groups.search({'search': 'grou'}, 2, 3))
+  [u'group.GA', u'group.GB', u'group.GC']
+
+
+If you don't supply a search key, no results will be returned:
+
+  >>> list(groups.search({}))
+  []
+
+Identifying groups
+------------------
+The function, `setGroupsForPrincipal`, is a subscriber to
+principal-creation events.  It adds any group-folder-defined groups to
+users in those groups:
+
+  >>> principal = principals.getPrincipal('auth.p1')
+
+  >>> principal.groups
+  [u'auth.group.G1', u'auth.group.GA']
+
+Of course, this applies to groups too:
+
+  >>> principal = principals.getPrincipal('auth.group.G1')
+  >>> principal.id
+  'auth.group.G1'
+
+  >>> principal.groups
+  [u'auth.group.G2']
+
+In addition to setting principal groups, the `setGroupsForPrincipal`
+function also declares the `IGroup` interface on groups:
+
+  >>> [iface.__name__ for iface in interface.providedBy(principal)]
+  ['IGroup', 'IGroupAwarePrincipal']
+
+  >>> [iface.__name__
+  ...  for iface in interface.providedBy(principals.getPrincipal('auth.p1'))]
+  ['IGroupAwarePrincipal']
+
+Special groups
+--------------
+Two special groups, Authenticated, and Everyone may apply to users
+created by the pluggable-authentication utility.  There is a
+subscriber, specialGroups, that will set these groups on any non-group
+principals if IAuthenticatedGroup, or IEveryoneGroup utilities are
+provided.
+
+Lets define a group-aware principal:
+
+  >>> import zope.security.interfaces
+  >>> class GroupAwarePrincipal(Principal):
+  ...     interface.implements(zope.security.interfaces.IGroupAwarePrincipal)
+  ...     def __init__(self, id):
+  ...         Principal.__init__(self, id)
+  ...         self.groups = []
+
+If we notify the subscriber with this principal, nothing will happen
+because the groups haven't been defined:
+
+  >>> prin = GroupAwarePrincipal('x')
+  >>> event = interfaces.FoundPrincipalCreated(42, prin, {})
+  >>> zope.app.authentication.groupfolder.specialGroups(event)
+  >>> prin.groups
+  []
+
+Now, if we define the Everybody group:
+
+  >>> import zope.app.security.interfaces
+  >>> class EverybodyGroup(Principal):
+  ...     interface.implements(zope.app.security.interfaces.IEveryoneGroup)
+
+  >>> everybody = EverybodyGroup('all')
+  >>> ztapi.provideUtility(zope.app.security.interfaces.IEveryoneGroup,
+  ...                      everybody)
+
+Then the group will be added to the principal:
+
+  >>> zope.app.authentication.groupfolder.specialGroups(event)
+  >>> prin.groups
+  ['all']
+
+Similarly for the authenticated group:
+
+  >>> class AuthenticatedGroup(Principal):
+  ...     interface.implements(
+  ...         zope.app.security.interfaces.IAuthenticatedGroup)
+
+  >>> authenticated = AuthenticatedGroup('auth')
+  >>> ztapi.provideUtility(zope.app.security.interfaces.IAuthenticatedGroup,
+  ...                      authenticated)
+
+Then the group will be added to the principal:
+
+  >>> prin.groups = []
+  >>> zope.app.authentication.groupfolder.specialGroups(event)
+  >>> prin.groups.sort()
+  >>> prin.groups
+  ['all', 'auth']
+
+These groups are only added to non-group principals:
+
+  >>> prin.groups = []
+  >>> interface.directlyProvides(prin, zope.security.interfaces.IGroup)
+  >>> zope.app.authentication.groupfolder.specialGroups(event)
+  >>> prin.groups
+  []
+
+And they are only added to group aware principals:
+
+  >>> class SolitaryPrincipal:
+  ...     interface.implements(zope.security.interfaces.IPrincipal)
+  ...     id = title = description = ''
+
+  >>> event = interfaces.FoundPrincipalCreated(42, SolitaryPrincipal(), {})
+  >>> zope.app.authentication.groupfolder.specialGroups(event)
+  >>> prin.groups
+  []
+
+Member-aware groups
+-------------------
+The groupfolder includes a subscriber that gives group principals the
+zope.security.interfaces.IGroupAware interface and an implementation thereof.
+This allows groups to be able to get and set their members.
+
+Given an info object and a group...
+
+    >>> class DemoGroupInformation(object):
+    ...     interface.implements(
+    ...         zope.app.authentication.groupfolder.IGroupInformation)
+    ...     def __init__(self, title, description, principals):
+    ...         self.title = title
+    ...         self.description = description
+    ...         self.principals = principals
+    ...
+    >>> i = DemoGroupInformation(
+    ...     'Managers', 'Taskmasters', ('joe', 'jane'))
+    ...
+    >>> info = zope.app.authentication.groupfolder.GroupInfo(
+    ...     'groups.managers', i)
+    >>> class DummyGroup(object):
+    ...     interface.implements(IGroupAwarePrincipal)
+    ...     def __init__(self, id, title=u'', description=u''):
+    ...         self.id = id
+    ...         self.title = title
+    ...         self.description = description
+    ...         self.groups = []
+    ...
+    >>> principal = DummyGroup('foo')
+    >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal)
+    False
+
+...when you call the subscriber, it adds the two pseudo-methods to the
+principal and makes the principal provide the IMemberAwareGroup interface.
+
+    >>> zope.app.authentication.groupfolder.setMemberSubscriber(
+    ...     interfaces.FoundPrincipalCreated(
+    ...         'dummy auth (ignored)', principal, info))
+    >>> principal.getMembers()
+    ('joe', 'jane')
+    >>> principal.setMembers(('joe', 'jane', 'jaimie'))
+    >>> principal.getMembers()
+    ('joe', 'jane', 'jaimie')
+    >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal)
+    True
+
+The two methods work with the value on the IGroupInformation object.
+
+    >>> i.principals == principal.getMembers()
+    True
+
+Limitation
+==========
+
+The current group-folder design has an important limitation!
+
+There is no point in assigning principals to a group 
+from a group folder unless the principal is from the same pluggable
+authentication utility.
+
+o If a principal is from a higher authentication utility, the user
+  will not get the group definition. Why? Because the principals
+  group assignments are set when the principal is authenticated. At
+  that point, the current site is the site containing the principal
+  definition. Groups defined in lower sites will not be consulted,
+
+o It is impossible to assign users from lower authentication
+  utilities because they can't be seen when managing the group,
+  from the site containing the group.
+
+A better design might be to store user-role assignments independent of
+the group definitions and to look for assignments during (url)
+traversal.  This could get quite complex though.
+
+While it is possible to have multiple authentication utilities long a
+URL path, it is generally better to stick to a simpler model in which
+there is only one authentication utility along a URL path (in addition
+to the global utility, which is used for bootstrapping purposes).


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,66 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope"
+    >
+
+  <class class=".groupfolder.GroupInformation">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".groupfolder.IGroupInformation
+                   .groupfolder.IGroupContained"
+        set_schema=".groupfolder.IGroupInformation"
+        />
+  </class>
+
+  <class class=".groupfolder.GroupFolder">
+    <implements
+        interface=".groupfolder.IGroupFolder"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.container.interfaces.IContainer
+                   zope.app.container.interfaces.INameChooser"
+        />
+  </class>
+
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for=".groupfolder.IGroupFolder"
+      factory=".idpicker.IdPicker"
+      />
+
+  <subscriber
+      for=".interfaces.IPrincipalCreated"
+      handler=".groupfolder.specialGroups"
+      />
+
+  <subscriber
+      for=".interfaces.IPrincipalCreated"
+      handler=".groupfolder.setGroupsForPrincipal"
+      />
+
+  <subscriber
+      handler=".groupfolder.setMemberSubscriber"
+      />
+
+  <include package=".browser" file="groupfolder.zcml" />
+
+  <!-- Registering documentation with API doc -->
+  <configure
+      xmlns:apidoc="http://namespaces.zope.org/apidoc"
+      xmlns:zcml="http://namespaces.zope.org/zcml"
+      zcml:condition="have apidoc">
+
+    <apidoc:bookchapter
+        id="groupfolder"
+        title="Group Folders"
+        doc_path="groupfolder.txt"
+        parent="security/authentication"
+        />
+
+  </configure>
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/groupfolder.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,142 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""PAS plugins related to HTTP
+
+$Id: httpplugins.py 69466 2006-08-14 13:09:26Z ctheune $
+"""
+__docformat__ = "reStructuredText"
+import base64
+from persistent import Persistent
+from zope.interface import implements, Interface
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.schema import TextLine
+
+from zope.app.container.contained import Contained
+from zope.app.authentication import interfaces
+
+
+class IHTTPBasicAuthRealm(Interface):
+    """HTTP Basic Auth Realm
+
+    Represents the realm string that is used during basic HTTP authentication
+    """
+
+    realm = TextLine(title=u'Realm',
+                     description=u'HTTP Basic Authentication Realm',
+                     required=True,
+                     default=u'Zope')
+
+
+class HTTPBasicAuthCredentialsPlugin(Persistent, Contained):
+
+    implements(interfaces.ICredentialsPlugin, IHTTPBasicAuthRealm)
+
+    realm = 'Zope'
+
+    protocol = 'http auth'
+
+    def extractCredentials(self, request):
+        """Extracts HTTP basic auth credentials from a request.
+
+        First we need to create a request that contains some credentials.
+
+          >>> from zope.publisher.browser import TestRequest
+          >>> request = TestRequest(
+          ...     environ={'HTTP_AUTHORIZATION': u'Basic bWdyOm1ncnB3'})
+
+        Now create the plugin and get the credentials.
+
+          >>> plugin = HTTPBasicAuthCredentialsPlugin()
+          >>> plugin.extractCredentials(request)
+          {'login': u'mgr', 'password': u'mgrpw'}
+
+        Make sure we return `None`, if no authentication header has been
+        specified.
+
+          >>> print plugin.extractCredentials(TestRequest())
+          None
+
+        Also, this plugin can *only* handle basic authentication.
+
+          >>> request = TestRequest(environ={'HTTP_AUTHORIZATION': 'foo bar'})
+          >>> print plugin.extractCredentials(TestRequest())
+          None
+
+        This plugin only works with HTTP requests.
+
+          >>> from zope.publisher.base import TestRequest
+          >>> print plugin.extractCredentials(TestRequest('/'))
+          None
+
+        """
+        if not IHTTPRequest.providedBy(request):
+            return None
+
+        if request._auth:
+            if request._auth.lower().startswith(u'basic '):
+                credentials = request._auth.split()[-1]
+                login, password = base64.decodestring(credentials).split(':')
+                return {'login': login.decode('utf-8'),
+                        'password': password.decode('utf-8')}
+        return None
+
+    def challenge(self, request):
+        """Issues an HTTP basic auth challenge for credentials.
+
+        The challenge is issued by setting the appropriate response headers.
+        To illustrate, we'll create a plugin:
+
+          >>> plugin = HTTPBasicAuthCredentialsPlugin()
+
+        The plugin adds its challenge to the HTTP response.
+
+          >>> from zope.publisher.browser import TestRequest
+          >>> request = TestRequest()
+          >>> response = request.response
+          >>> plugin.challenge(request)
+          True
+          >>> response._status
+          401
+          >>> response.getHeader('WWW-Authenticate', literal=True)
+          'basic realm="Zope"'
+
+        Notice that the realm is quoted, as per RFC 2617.
+
+        The plugin only works with HTTP requests.
+
+          >>> from zope.publisher.base import TestRequest
+          >>> request = TestRequest('/')
+          >>> response = request.response
+          >>> print plugin.challenge(request)
+          False
+
+        """
+        if not IHTTPRequest.providedBy(request):
+            return False
+        request.response.setHeader("WWW-Authenticate",
+                                   'basic realm="%s"' % self.realm,
+                                   literal=True)
+        request.response.setStatus(401)
+        return True
+
+    def logout(self, request):
+        """Always returns False as logout is not supported by basic auth.
+
+          >>> plugin = HTTPBasicAuthCredentialsPlugin()
+          >>> from zope.publisher.browser import TestRequest
+          >>> plugin.logout(TestRequest())
+          False
+
+        """
+        return False


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,25 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope"
+    >
+
+  <utility
+      name="Zope Realm Basic-Auth"
+      provides=".interfaces.ICredentialsPlugin"
+      factory=".httpplugins.HTTPBasicAuthCredentialsPlugin"
+      />
+
+  <class class=".httpplugins.HTTPBasicAuthCredentialsPlugin">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".httpplugins.IHTTPBasicAuthRealm"
+        set_schema=".httpplugins.IHTTPBasicAuthRealm"
+        />
+  </class>
+
+  <include package=".browser" file="httpplugins.zcml" />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/httpplugins.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/i18n.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/i18n.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/i18n.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,22 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""Customization of zope.i18n for the Zope application server
+
+$Id: i18n.py 73548 2007-03-25 09:05:22Z dobe $
+"""
+__docformat__ = 'restructuredtext'
+
+# import this as _ to create i18n messages in the zope domain
+from zope.i18nmessageid import MessageFactory
+ZopeMessageFactory = MessageFactory('zope')


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/i18n.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/idpicker.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/idpicker.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/idpicker.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,109 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Helper base class that picks principal ids
+
+$Id: idpicker.py 73548 2007-03-25 09:05:22Z dobe $
+"""
+__docformat__ = 'restructuredtext'
+
+import re
+from zope.exceptions.interfaces import UserError
+from zope.app.container.contained import NameChooser
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+
+ok = re.compile('[!-~]+$').match
+class IdPicker(NameChooser):
+    """Helper base class that picks principal ids.
+
+    Add numbers to ids given by users to make them unique.
+
+    The Id picker is a variation on the name chooser that picks numeric
+    ids when no name is given.
+
+      >>> from zope.app.authentication.idpicker import IdPicker
+      >>> IdPicker({}).chooseName('', None)
+      u'1'
+
+      >>> IdPicker({'1': 1}).chooseName('', None)
+      u'2'
+
+      >>> IdPicker({'2': 1}).chooseName('', None)
+      u'1'
+
+      >>> IdPicker({'1': 1}).chooseName('bob', None)
+      u'bob'
+
+      >>> IdPicker({'bob': 1}).chooseName('bob', None)
+      u'bob1'
+
+    """
+    def chooseName(self, name, object):
+        i = 0
+        name = unicode(name)
+        orig = name
+        while (not name) or (name in self.context):
+            i += 1
+            name = orig+str(i)
+
+        self.checkName(name, object)
+        return name
+
+    def checkName(self, name, object):
+        """Limit ids
+
+        Ids can only contain printable, non-space, 7-bit ASCII strings:
+
+        >>> from zope.app.authentication.idpicker import IdPicker
+        >>> IdPicker({}).checkName(u'1', None)
+        True
+
+        >>> IdPicker({}).checkName(u'bob', None)
+        True
+
+        >>> IdPicker({}).checkName(u'bob\xfa', None)
+        ... # doctest: +NORMALIZE_WHITESPACE
+        Traceback (most recent call last):
+        ...
+        UserError: Ids must contain only printable
+        7-bit non-space ASCII characters
+
+        >>> IdPicker({}).checkName(u'big bob', None)
+        ... # doctest: +NORMALIZE_WHITESPACE
+        Traceback (most recent call last):
+        ...
+        UserError: Ids must contain only printable
+        7-bit non-space ASCII characters
+
+        Ids also can't be over 100 characters long:
+
+        >>> IdPicker({}).checkName(u'x' * 100, None)
+        True
+
+        >>> IdPicker({}).checkName(u'x' * 101, None)
+        Traceback (most recent call last):
+        ...
+        UserError: Ids can't be more than 100 characters long.
+
+        """
+        NameChooser.checkName(self, name, object)
+        if not ok(name):
+            raise UserError(
+                _("Ids must contain only printable 7-bit non-space"
+                  " ASCII characters")
+                )
+        if len(name) > 100:
+            raise UserError(
+                _("Ids can't be more than 100 characters long.")
+                )
+        return True


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/idpicker.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/interfaces.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/interfaces.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/interfaces.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,362 @@
+##############################################################################
+#
+# 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 Interfaces
+
+$Id: interfaces.py 73548 2007-03-25 09:05:22Z dobe $
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+import zope.security.interfaces
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+from zope.app.security.interfaces import ILogout
+from zope.app.container.constraints import contains, containers
+from zope.app.container.interfaces import IContainer
+
+
+class IPlugin(zope.interface.Interface):
+    """A plugin for a pluggable authentication component."""
+
+
+class IPluggableAuthentication(ILogout, IContainer):
+    """Provides authentication services with the help of various plugins.
+    
+    IPluggableAuthentication implementations will also implement
+    zope.app.security.interfaces.IAuthentication.  The `authenticate` method
+    of this interface in an IPluggableAuthentication should annotate the
+    IPrincipalInfo with the credentials plugin and authentication plugin used.
+    The `getPrincipal` method should annotate the IPrincipalInfo with the
+    authentication plugin used.
+    """
+
+    contains(IPlugin)
+
+    credentialsPlugins = zope.schema.List(
+        title=_('Credentials Plugins'),
+        description=_("""Used for extracting credentials.
+        Names may be of ids of non-utility ICredentialsPlugins contained in
+        the IPluggableAuthentication, or names of registered
+        ICredentialsPlugins utilities.  Contained non-utility ids mask 
+        utility names."""),
+        value_type=zope.schema.Choice(vocabulary='CredentialsPlugins'),
+        default=[],
+        )
+
+    authenticatorPlugins = zope.schema.List(
+        title=_('Authenticator Plugins'),
+        description=_("""Used for converting credentials to principals.
+        Names may be of ids of non-utility IAuthenticatorPlugins contained in
+        the IPluggableAuthentication, or names of registered
+        IAuthenticatorPlugins utilities.  Contained non-utility ids mask 
+        utility names."""),
+        value_type=zope.schema.Choice(vocabulary='AuthenticatorPlugins'),
+        default=[],
+        )
+
+    def getCredentialsPlugins():
+        """Return iterable of (plugin name, actual credentials plugin) pairs.
+        Looks up names in credentialsPlugins as contained ids of non-utility
+        ICredentialsPlugins first, then as registered ICredentialsPlugin
+        utilities.  Names that do not resolve are ignored."""
+
+    def getAuthenticatorPlugins():
+        """Return iterable of (plugin name, actual authenticator plugin) pairs.
+        Looks up names in authenticatorPlugins as contained ids of non-utility
+        IAuthenticatorPlugins first, then as registered IAuthenticatorPlugin
+        utilities.  Names that do not resolve are ignored."""
+
+    prefix = zope.schema.TextLine(
+        title=_('Prefix'),
+        default=u'',
+        required=True,
+        readonly=True,
+        )
+
+    def logout(request):
+        """Performs a logout by delegating to its authenticator plugins."""
+
+
+class ICredentialsPlugin(IPlugin):
+    """Handles credentials extraction and challenges per request."""
+
+    containers(IPluggableAuthentication)
+
+    challengeProtocol = zope.interface.Attribute(
+        """A challenge protocol used by the plugin.
+
+        If a credentials plugin works with other credentials pluggins, it
+        and the other cooperating plugins should specify a common (non-None)
+        protocol. If a plugin returns True from its challenge method, then
+        other credentials plugins will be called only if they have the same
+        protocol.
+        """)
+
+    def extractCredentials(request):
+        """Ties to extract credentials from a request.
+
+        A return value of None indicates that no credentials could be found.
+        Any other return value is treated as valid credentials.
+        """
+
+    def challenge(request):
+        """Possibly issues a challenge.
+
+        This is typically done in a protocol-specific way.
+
+        If a challenge was issued, return True, otherwise return False.
+        """
+
+    def logout(request):
+        """Possibly logout.
+
+        If a logout was performed, return True, otherwise return False.
+        """
+
+class IAuthenticatorPlugin(IPlugin):
+    """Authenticates a principal using credentials.
+
+    An authenticator may also be responsible for providing information
+    about and creating principals.
+    """
+    containers(IPluggableAuthentication)
+
+    def authenticateCredentials(credentials):
+        """Authenticates credentials.
+
+        If the credentials can be authenticated, return an object that provides
+        IPrincipalInfo. If the plugin cannot authenticate the credentials,
+        returns None.
+        """
+
+    def principalInfo(id):
+        """Returns an IPrincipalInfo object for the specified principal id.
+
+        If the plugin cannot find information for the id, returns None.
+        """
+
+class IPasswordManager(zope.interface.Interface):
+    """Password manager."""
+
+    def encodePassword(password):
+        """Return encoded data for the password."""
+
+    def checkPassword(storedPassword, password):
+        """Return whether the password coincide with the storedPassword."""
+
+class IPrincipalInfo(zope.interface.Interface):
+    """Minimal information about a principal."""
+
+    id = zope.interface.Attribute("The principal id.")
+
+    title = zope.interface.Attribute("The principal title.")
+
+    description = zope.interface.Attribute("A description of the principal.")
+
+    credentialsPlugin = zope.interface.Attribute(
+        """Plugin used to generate the credentials for this principal info.
+        
+        Optional.  Should be set in IPluggableAuthentication.authenticate.
+        """)
+
+    authenticatorPlugin = zope.interface.Attribute(
+        """Plugin used to authenticate the credentials for this principal info.
+        
+        Optional.  Should be set in IPluggableAuthentication.authenticate and
+        IPluggableAuthentication.getPrincipal.
+        """)
+
+class IPrincipal(zope.security.interfaces.IGroupClosureAwarePrincipal):
+
+    groups = zope.schema.List(
+        title=_("Groups"),
+        description=_(
+            """ids of groups to which the principal directly belongs.
+
+            Plugins may append to this list.  Mutating the list only affects
+            the life of the principal object, and does not persist (so
+            persistently adding groups to a principal should be done by working
+            with a plugin that mutates this list every time the principal is
+            created, like the group folder in this package.)
+            """),
+        value_type=zope.schema.TextLine(),
+        required=False)
+
+class IPrincipalFactory(zope.interface.Interface):
+    """A principal factory."""
+
+    def __call__(authentication):
+        """Creates a principal.
+
+        The authentication utility that called the factory is passed
+        and should be included in the principal-created event.
+        """
+
+
+class IFoundPrincipalFactory(IPrincipalFactory):
+    """A found principal factory."""
+
+
+class IAuthenticatedPrincipalFactory(IPrincipalFactory):
+    """An authenticated principal factory."""
+
+
+class IPrincipalCreated(zope.interface.Interface):
+    """A principal has been created."""
+
+    principal = zope.interface.Attribute("The principal that was created")
+
+    authentication = zope.interface.Attribute(
+        "The authentication utility that created the principal")
+
+    info = zope.interface.Attribute("An object providing IPrincipalInfo.")
+
+
+class IAuthenticatedPrincipalCreated(IPrincipalCreated):
+    """A principal has been created by way of an authentication operation."""
+
+    request = zope.interface.Attribute(
+        "The request the user was authenticated against")
+
+
+class AuthenticatedPrincipalCreated:
+    """
+    >>> from zope.interface.verify import verifyObject
+    >>> event = AuthenticatedPrincipalCreated("authentication", "principal",
+    ...     "info", "request")
+    >>> verifyObject(IAuthenticatedPrincipalCreated, event)
+    True
+    """
+
+    zope.interface.implements(IAuthenticatedPrincipalCreated)
+
+    def __init__(self, authentication, principal, info, request):
+        self.authentication = authentication
+        self.principal = principal
+        self.info = info
+        self.request = request
+
+
+class IFoundPrincipalCreated(IPrincipalCreated):
+    """A principal has been created by way of a search operation."""
+
+
+class FoundPrincipalCreated:
+    """
+    >>> from zope.interface.verify import verifyObject
+    >>> event = FoundPrincipalCreated("authentication", "principal",
+    ...     "info")
+    >>> verifyObject(IFoundPrincipalCreated, event)
+    True
+    """
+
+    zope.interface.implements(IFoundPrincipalCreated)
+
+    def __init__(self, authentication, principal, info):
+        self.authentication = authentication
+        self.principal = principal
+        self.info = info
+
+
+class IQueriableAuthenticator(zope.interface.Interface):
+    """Indicates the authenticator provides a search UI for principals."""
+
+
+class IQuerySchemaSearch(zope.interface.Interface):
+    """An interface for searching using schema-constrained input."""
+
+    schema = zope.interface.Attribute("""
+        The schema that constrains the input provided to the search method.
+
+        A mapping of name/value pairs for each field in this schema is used
+        as the query argument in the search method.
+        """)
+
+    def search(query, start=None, batch_size=None):
+        """Returns an iteration of principal IDs matching the query.
+
+        query is a mapping of name/value pairs for fields specified by the
+        schema.
+
+        If the start argument is provided, then it should be an
+        integer and the given number of initial items should be
+        skipped.
+
+        If the batch_size argument is provided, then it should be an
+        integer and no more than the given number of items should be
+        returned.
+        """
+
+class IGroupAdded(zope.interface.Interface):
+    """A group has been added."""
+
+    group = zope.interface.Attribute("""The group that was defined""")
+
+
+class GroupAdded:
+    """
+    >>> from zope.interface.verify import verifyObject
+    >>> event = GroupAdded("group")
+    >>> verifyObject(IGroupAdded, event)
+    True
+    """
+
+    zope.interface.implements(IGroupAdded)
+
+    def __init__(self, group):
+        self.group = group
+
+    def __repr__(self):
+        return "<GroupAdded %r>" % self.group.id
+
+class IPrincipalsAddedToGroup(zope.interface.Interface):
+    group_id = zope.interface.Attribute(
+        'the id of the group to which the principal was added')
+    principal_ids = zope.interface.Attribute(
+        'an iterable of one or more ids of principals added')
+
+class IPrincipalsRemovedFromGroup(zope.interface.Interface):
+    group_id = zope.interface.Attribute(
+        'the id of the group from which the principal was removed')
+    principal_ids = zope.interface.Attribute(
+        'an iterable of one or more ids of principals removed')
+
+class AbstractMembersChanged(object):
+
+    def __init__(self, principal_ids, group_id):
+        self.principal_ids = principal_ids
+        self.group_id = group_id
+
+    def __repr__(self):
+        return "<%s %r %r>" % (
+            self.__class__.__name__, sorted(self.principal_ids), self.group_id)
+
+class PrincipalsAddedToGroup(AbstractMembersChanged):
+    zope.interface.implements(IPrincipalsAddedToGroup)
+
+class PrincipalsRemovedFromGroup(AbstractMembersChanged):
+    zope.interface.implements(IPrincipalsRemovedFromGroup)
+
+class ISessionCredentialsPlugin(zope.interface.Interface):
+
+    def extractCredentials(request, overrides):
+        """Extract credentials and allow the application to provide the
+        credentials instead of using the request"""
+
+    passwordManager = zope.interface.Attribute(
+        'The passwordManager object, currently being to encrypt the password')
+
+class PasswordManagerMismatchError(Exception):
+    """The password manager defined by the credentials extraction
+    and the principal storage do not match"""


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,160 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Password managers
+
+$Id: password.py 74706 2007-04-24 16:15:34Z hdima $
+"""
+__docformat__ = 'restructuredtext'
+
+import md5
+import sha
+from random import randint
+from codecs import getencoder
+
+from zope.interface import implements, classProvides
+from zope.schema.interfaces import IVocabularyFactory
+from zope.app.component.vocabulary import UtilityVocabulary
+
+from zope.app.authentication.interfaces import IPasswordManager
+
+
+_encoder = getencoder("utf-8")
+
+
+class PlainTextPasswordManager(object):
+    """Plain text password manager.
+
+    >>> from zope.interface.verify import verifyObject
+
+    >>> manager = PlainTextPasswordManager()
+    >>> verifyObject(IPasswordManager, manager)
+    True
+
+    >>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
+    >>> encoded = manager.encodePassword(password)
+    >>> encoded
+    u'right \u0410'
+    >>> manager.checkPassword(encoded, password)
+    True
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    False
+    """
+
+    implements(IPasswordManager)
+
+    def encodePassword(self, password):
+        return password
+
+    def checkPassword(self, storedPassword, password):
+        return storedPassword == self.encodePassword(password)
+
+
+class MD5PasswordManager(PlainTextPasswordManager):
+    """MD5 password manager.
+
+    >>> from zope.interface.verify import verifyObject
+
+    >>> manager = MD5PasswordManager()
+    >>> verifyObject(IPasswordManager, manager)
+    True
+
+    >>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
+    >>> encoded = manager.encodePassword(password, salt="")
+    >>> encoded
+    '86dddccec45db4599f1ac00018e54139'
+    >>> manager.checkPassword(encoded, password)
+    True
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    False
+
+    >>> encoded = manager.encodePassword(password)
+    >>> encoded[-32:]
+    '86dddccec45db4599f1ac00018e54139'
+    >>> manager.checkPassword(encoded, password)
+    True
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    False
+
+    >>> manager.encodePassword(password) != manager.encodePassword(password)
+    True
+    """
+
+    implements(IPasswordManager)
+
+    def encodePassword(self, password, salt=None):
+        if salt is None:
+            salt = "%08x" % randint(0, 0xffffffff)
+        return salt + md5.new(_encoder(password)[0]).hexdigest()
+
+    def checkPassword(self, storedPassword, password):
+        salt = storedPassword[:-32]
+        return storedPassword == self.encodePassword(password, salt)
+
+
+class SHA1PasswordManager(PlainTextPasswordManager):
+    """SHA1 password manager.
+
+    >>> from zope.interface.verify import verifyObject
+
+    >>> manager = SHA1PasswordManager()
+    >>> verifyObject(IPasswordManager, manager)
+    True
+
+    >>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
+    >>> encoded = manager.encodePassword(password, salt="")
+    >>> encoded
+    '04b4eec7154c5f3a2ec6d2956fb80b80dc737402'
+    >>> manager.checkPassword(encoded, password)
+    True
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    False
+
+    >>> encoded = manager.encodePassword(password)
+    >>> encoded[-40:]
+    '04b4eec7154c5f3a2ec6d2956fb80b80dc737402'
+    >>> manager.checkPassword(encoded, password)
+    True
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    False
+
+    >>> manager.encodePassword(password) != manager.encodePassword(password)
+    True
+    """
+
+    implements(IPasswordManager)
+
+    def encodePassword(self, password, salt=None):
+        if salt is None:
+            salt = "%08x" % randint(0, 0xffffffff)
+        return salt + sha.new(_encoder(password)[0]).hexdigest()
+
+    def checkPassword(self, storedPassword, password):
+        salt = storedPassword[:-40]
+        return storedPassword == self.encodePassword(password, salt)
+
+
+# Simple registry used by mkzopeinstance script
+managers = [
+    ("Plain Text", PlainTextPasswordManager()), # default
+    ("MD5", MD5PasswordManager()),
+    ("SHA1", SHA1PasswordManager()),
+]
+
+
+class PasswordManagerNamesVocabulary(UtilityVocabulary):
+    """Vocabulary of password managers."""
+
+    classProvides(IVocabularyFactory)
+    interface = IPasswordManager
+    nameOnly = True


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,41 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope"
+    >
+
+  <utility
+      component=".password.PasswordManagerNamesVocabulary"
+      name="Password Manager Names"
+      />
+
+  <class class=".password.PlainTextPasswordManager">
+    <allow interface=".interfaces.IPasswordManager" />
+  </class>
+
+  <utility
+      name="Plain Text"
+      provides=".interfaces.IPasswordManager"
+      factory=".password.PlainTextPasswordManager"
+      />
+
+  <class class=".password.MD5PasswordManager">
+    <allow interface=".interfaces.IPasswordManager" />
+  </class>
+
+  <utility
+      name="MD5"
+      provides=".interfaces.IPasswordManager"
+      factory=".password.MD5PasswordManager"
+      />
+
+  <class class=".password.SHA1PasswordManager">
+    <allow interface=".interfaces.IPasswordManager" />
+  </class>
+
+  <utility
+      name="SHA1"
+      provides=".interfaces.IPasswordManager"
+      factory=".password.SHA1PasswordManager"
+      />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/password.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/placelesssetup.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/placelesssetup.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/placelesssetup.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# 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 Service Placeless Setup
+
+$Id: placelesssetup.py 70794 2006-10-19 04:29:42Z baijum $
+"""
+__docformat__ = "reStructuredText"
+
+from zope.app.testing import ztapi
+from zope.app.authentication.interfaces import IPasswordManager
+from zope.app.authentication.password import PlainTextPasswordManager
+from zope.app.authentication.password import MD5PasswordManager
+from zope.app.authentication.password import SHA1PasswordManager
+
+
+class PlacelessSetup(object):
+
+    def setUp(self):
+        ztapi.provideUtility(IPasswordManager, PlainTextPasswordManager(),
+            "Plain Text")
+        ztapi.provideUtility(IPasswordManager, MD5PasswordManager(), "MD5")
+        ztapi.provideUtility(IPasswordManager, SHA1PasswordManager(), "SHA1")


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/placelesssetup.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,578 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""ZODB-based Authentication Source
+
+$Id: principalfolder.py 78799 2007-08-13 20:46:48Z fdrake $
+"""
+__docformat__ = "reStructuredText"
+
+from persistent import Persistent
+from zope import interface
+from zope import component
+from zope.event import notify
+from zope.schema import Text, TextLine, Password, Choice
+from zope.publisher.interfaces import IRequest
+
+from zope.app import zapi
+from zope.app.container.interfaces import DuplicateIDError
+from zope.app.container.contained import Contained
+from zope.app.container.constraints import contains, containers
+from zope.app.container.btree import BTreeContainer
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+from zope.app.security.interfaces import IAuthentication
+
+from zope.app.authentication import interfaces
+
+class IInternalPrincipal(interface.Interface):
+    """Principal information"""
+
+    login = TextLine(
+        title=_("Login"),
+        description=_("The Login/Username of the principal. "
+                      "This value can change."))
+
+    def setPassword(password, passwordManagerName=None):
+        pass
+
+    password = Password(
+        title=_("Password"),
+        description=_("The password for the principal."))
+
+    passwordManagerName = Choice(
+        title=_("Password Manager"),
+        vocabulary="Password Manager Names",
+        description=_("The password manager will be used"
+            " for encode/check the password"),
+        default="Plain Text",
+        # TODO: The password manager name may be changed only
+        # if the password changed
+        readonly=True
+        )
+
+    title = TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the principal."))
+
+    description = Text(
+        title=_("Description"),
+        description=_("Provides a description for the principal."),
+        required=False,
+        missing_value='',
+        default=u'')
+
+
+class IInternalPrincipalContainer(interface.Interface):
+    """A container that contains internal principals."""
+
+    prefix = TextLine(
+        title=_("Prefix"),
+        description=_(
+        "Prefix to be added to all principal ids to assure "
+        "that all ids are unique within the authentication service"),
+        missing_value=u"",
+        default=u'',
+        readonly=True)
+
+    def getIdByLogin(login):
+        """Return the principal id currently associated with login.
+
+        The return value includes the container prefix, but does not
+        include the PAU prefix.
+
+        KeyError is raised if no principal is associated with login.
+
+        """
+
+    contains(IInternalPrincipal)
+
+
+class IInternalPrincipalContained(interface.Interface):
+    """Principal information"""
+
+    containers(IInternalPrincipalContainer)
+
+
+class ISearchSchema(interface.Interface):
+    """Search Interface for this Principal Provider"""
+
+    search = TextLine(
+        title=_("Search String"),
+        description=_("A Search String"),
+        required=False,
+        default=u'',
+        missing_value=u'')
+
+
+class InternalPrincipal(Persistent, Contained):
+    """An internal principal for Persistent Principal Folder."""
+
+    interface.implements(IInternalPrincipal, IInternalPrincipalContained)
+
+    # If you're searching for self._passwordManagerName, or self._password
+    # probably you just need to evolve the database to new generation
+    # at /++etc++process/@@generations.html
+
+    # NOTE: All changes needs to be synchronized with the evolver at
+    # zope.app.zopeappgenerations.evolve2
+
+    def __init__(self, login, password, title, description=u'',
+            passwordManagerName="Plain Text"):
+        self._login = login
+        self._passwordManagerName = passwordManagerName
+        self.password = password
+        self.title = title
+        self.description = description
+
+    def getPasswordManagerName(self):
+        return self._passwordManagerName
+
+    passwordManagerName = property(getPasswordManagerName)
+
+    def _getPasswordManager(self):
+        return zapi.getUtility(
+            interfaces.IPasswordManager, self.passwordManagerName)
+
+    def getPassword(self):
+        return self._password
+
+    def setPassword(self, password, passwordManagerName=None):
+        if passwordManagerName is not None:
+            self._passwordManagerName = passwordManagerName
+        passwordManager = self._getPasswordManager()
+        self._password = passwordManager.encodePassword(password)
+
+    password = property(getPassword, setPassword)
+
+    def checkPassword(self, password, passwordManager_forCredentials=None):
+        passwordManager = self._getPasswordManager()
+        if passwordManager_forCredentials and passwordManager:
+            if passwordManager_forCredentials.__class__ != passwordManager.__class__:
+            # TODO: Check syntax
+                raise interfaces.PasswordManagerMismatchError
+
+        # If a password Manager has been used to encrypt the crendentials
+        # while in memory, use it to verify the crendentials
+        if passwordManager_forCredentials:
+            return passwordManager_forCrendentials.checkPassword(self.password, password)
+        return passwordManager.checkPassword(self.password, password)
+
+    def getPassword(self):
+        return self._password
+
+    def getLogin(self):
+        return self._login
+
+    def setLogin(self, login):
+        oldLogin = self._login
+        self._login = login
+        if self.__parent__ is not None:
+            try:
+                self.__parent__.notifyLoginChanged(oldLogin, self)
+            except ValueError:
+                self._login = oldLogin
+                raise
+
+    login = property(getLogin, setLogin)
+
+
+class PrincipalInfo(object):
+    """An implementation of IPrincipalInfo used by the principal folder.
+
+    A principal info is created with id, login, title, and description:
+
+      >>> info = PrincipalInfo('users.foo', 'foo', 'Foo', 'An over-used term.')
+      >>> info
+      PrincipalInfo('users.foo')
+      >>> info.id
+      'users.foo'
+      >>> info.login
+      'foo'
+      >>> info.title
+      'Foo'
+      >>> info.description
+      'An over-used term.'
+
+    """
+    interface.implements(interfaces.IPrincipalInfo)
+
+    def __init__(self, id, login, title, description):
+        self.id = id
+        self.login = login
+        self.title = title
+        self.description = description
+
+    def __repr__(self):
+        return 'PrincipalInfo(%r)' % self.id
+
+
+class PrincipalFolder(BTreeContainer):
+    """A Persistent Principal Folder and Authentication plugin.
+
+    See principalfolder.txt for details.
+    """
+
+    interface.implements(interfaces.IAuthenticatorPlugin,
+                         interfaces.IQuerySchemaSearch,
+                         IInternalPrincipalContainer)
+
+    schema = ISearchSchema
+
+    def __init__(self, prefix=''):
+        self.prefix = unicode(prefix)
+        super(PrincipalFolder, self).__init__()
+        self.__id_by_login = self._newContainerData()
+
+    def notifyLoginChanged(self, oldLogin, principal):
+        """Notify the Container about changed login of a principal.
+
+        We need this, so that our second tree can be kept up-to-date.
+        """
+        # A user with the new login already exists
+        if principal.login in self.__id_by_login:
+            raise ValueError('Principal Login already taken!')
+
+        del self.__id_by_login[oldLogin]
+        self.__id_by_login[principal.login] = principal.__name__
+
+    def __setitem__(self, id, principal):
+        """Add principal information.
+
+        Create a Principal Folder
+
+            >>> pf = PrincipalFolder()
+
+        Create a principal with 1 as id
+        Add a login attr since __setitem__ is in need of one
+
+            >>> principal = Principal(1)
+            >>> principal.login = 1
+
+        Add the principal within the Principal Folder
+
+            >>> pf.__setitem__(u'1', principal)
+
+        Try to add another principal with the same id.
+        It should raise a DuplicateIDError
+
+            >>> try:
+            ...     pf.__setitem__(u'1', principal)
+            ... except DuplicateIDError, e:
+            ...     pass
+            >>>
+        """
+        # A user with the new login already exists
+        if principal.login in self.__id_by_login:
+            raise DuplicateIDError('Principal Login already taken!')
+
+        super(PrincipalFolder, self).__setitem__(id, principal)
+        self.__id_by_login[principal.login] = id
+
+    def __delitem__(self, id):
+        """Remove principal information."""
+        principal = self[id]
+        super(PrincipalFolder, self).__delitem__(id)
+        del self.__id_by_login[principal.login]
+
+    def authenticateCredentials(self, credentials):
+        """Return principal info if credentials can be authenticated
+        """
+        if not isinstance(credentials, dict):
+            return None
+        if not ('login' in credentials and 'password' in credentials):
+            return None
+        id = self.__id_by_login.get(credentials['login'])
+        if id is None:
+            return None
+        internal = self[id]
+
+        passwordManager = credentials.get('passwordManager', None)
+        if not internal.checkPassword(credentials["password"],
+                passwordManager_forCredentials=passwordManager):
+            return None
+
+        return PrincipalInfo(self.prefix + id, internal.login, internal.title,
+                             internal.description)
+
+    def principalInfo(self, id):
+        if id.startswith(self.prefix):
+            internal = self.get(id[len(self.prefix):])
+            if internal is not None:
+                return PrincipalInfo(id, internal.login, internal.title,
+                                     internal.description)
+
+    def getIdByLogin(self, login):
+        return self.prefix + self.__id_by_login[login]
+
+    def search(self, query, start=None, batch_size=None):
+        """Search through this principal provider."""
+        search = query.get('search')
+        if search is None:
+            return
+        search = search.lower()
+        n = 1
+        for i, value in enumerate(self.values()):
+            if (search in value.title.lower() or
+                search in value.description.lower() or
+                search in value.login.lower()):
+                if not ((start is not None and i < start)
+                        or (batch_size is not None and n > batch_size)):
+                    n += 1
+                    yield self.prefix + value.__name__
+
+class Principal(object):
+    """A group-aware implementation of zope.security.interfaces.IPrincipal.
+
+    A principal is created with an ID:
+
+      >>> p = Principal(1)
+      >>> p
+      Principal(1)
+      >>> p.id
+      1
+
+    title and description may also be provided:
+
+      >>> p = Principal('george', 'George', 'A site member.')
+      >>> p
+      Principal('george')
+      >>> p.id
+      'george'
+      >>> p.title
+      'George'
+      >>> p.description
+      'A site member.'
+
+    The `groups` is a simple list, filled in by plugins.
+
+      >>> p.groups
+      []
+
+    The `allGroups` attribute is a readonly iterable of the full closure of the
+    groups in the `groups` attribute--that is, if the principal is a direct
+    member of the 'Administrators' group, and the 'Administrators' group is
+    a member of the 'Reviewers' group, then p.groups would be 
+    ['Administrators'] and list(p.allGroups) would be
+    ['Administrators', 'Reviewers'].
+
+    To illustrate this, we'll need to set up a dummy authentication utility,
+    and a few principals.  Our main principal will also gain some groups, as if
+    plugins had added the groups to the list.  This is all setup--skip to the
+    next block to actually see `allGroups` in action.
+    
+      >>> p.groups.extend(
+      ...     ['content_administrators', 'zope_3_project',
+      ...      'list_administrators', 'zpug'])
+      >>> editor = Principal('editors', 'Content Editors')
+      >>> creator = Principal('creators', 'Content Creators')
+      >>> reviewer = Principal('reviewers', 'Content Reviewers')
+      >>> reviewer.groups.extend(['editors', 'creators'])
+      >>> usermanager = Principal('user_managers', 'User Managers')
+      >>> contentAdmin = Principal(
+      ...     'content_administrators', 'Content Administrators')
+      >>> contentAdmin.groups.extend(['reviewers', 'user_managers'])
+      >>> zope3Dev = Principal('zope_3_project', 'Zope 3 Developer')
+      >>> zope3ListAdmin = Principal(
+      ...     'zope_3_list_admin', 'Zope 3 List Administrators')
+      >>> zope3ListAdmin.groups.append('zope_3_project') # duplicate, but
+      ... # should only appear in allGroups once
+      >>> listAdmin = Principal('list_administrators', 'List Administrators')
+      >>> listAdmin.groups.append('zope_3_list_admin')
+      >>> zpugMember = Principal('zpug', 'ZPUG Member')
+      >>> martians = Principal('martians', 'Martians') # not in p's allGroups
+      >>> group_data = dict((p.id, p) for p in (
+      ...     editor, creator, reviewer, usermanager, contentAdmin,
+      ...     zope3Dev, zope3ListAdmin, listAdmin, zpugMember, martians))
+      >>> class DemoAuth(object):
+      ...     interface.implements(IAuthentication)
+      ...     def getPrincipal(self, id):
+      ...         return group_data[id]
+      ...
+      >>> demoAuth = DemoAuth()
+      >>> component.provideUtility(demoAuth)
+
+    Now, we have a user with the following groups (lowest level are p's direct
+    groups, and lines show membership):
+
+      editors  creators
+         \------/
+             |                                     zope_3_project (duplicate)
+          reviewers  user_managers                          |
+               \---------/                           zope_3_list_admin
+                    |                                       |
+          content_administrators   zope_3_project   list_administrators   zpug
+
+    The allGroups value includes all of the shown groups, and with
+    'zope_3_project' only appearing once.
+
+      >>> p.groups # doctest: +NORMALIZE_WHITESPACE
+      ['content_administrators', 'zope_3_project', 'list_administrators',
+       'zpug']
+      >>> list(p.allGroups) # doctest: +NORMALIZE_WHITESPACE
+      ['content_administrators', 'reviewers', 'editors', 'creators',
+       'user_managers', 'zope_3_project', 'list_administrators',
+       'zope_3_list_admin', 'zpug']
+    """
+    interface.implements(interfaces.IPrincipal)
+
+    def __init__(self, id, title=u'', description=u''):
+        self.id = id
+        self.title = title
+        self.description = description
+        self.groups = []
+
+    def __repr__(self):
+        return 'Principal(%r)' % self.id
+
+    @property
+    def allGroups(self):
+        if self.groups:
+            seen = set()
+            principals = component.getUtility(IAuthentication)
+            stack = [iter(self.groups)]
+            while stack:
+                try:
+                    group_id = stack[-1].next()
+                except StopIteration:
+                    stack.pop()
+                else:
+                    if group_id not in seen:
+                        yield group_id
+                        seen.add(group_id)
+                        group = principals.getPrincipal(group_id)
+                        stack.append(iter(group.groups))
+
+class AuthenticatedPrincipalFactory(object):
+    """Creates 'authenticated' principals.
+
+    An authenticated principal is created as a result of an authentication
+    operation.
+
+    To use the factory, create it with the info (interfaces.IPrincipalInfo) of
+    the principal to create and a request:
+
+      >>> info = PrincipalInfo('users.mary', 'mary', 'Mary', 'The site admin.')
+      >>> from zope.publisher.base import TestRequest
+      >>> request = TestRequest('/')
+      >>> factory = AuthenticatedPrincipalFactory(info, request)
+
+    The factory must be called with a pluggable-authentication object:
+
+      >>> class Auth:
+      ...     prefix = 'auth.'
+      >>> auth = Auth()
+
+      >>> principal = factory(auth)
+
+    The factory uses the pluggable authentication and the info to
+    create a principal with the same ID, title, and description:
+
+      >>> principal.id
+      'auth.users.mary'
+      >>> principal.title
+      'Mary'
+      >>> principal.description
+      'The site admin.'
+
+    It also fires an AuthenticatedPrincipalCreatedEvent:
+
+      >>> from zope.component.eventtesting import getEvents
+      >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
+      >>> event.principal is principal, event.authentication is auth
+      (True, True)
+      >>> event.info
+      PrincipalInfo('users.mary')
+      >>> event.request is request
+      True
+
+    Listeners can subscribe to this event to perform additional operations
+    when the authenticated principal is created.
+
+    For information on how factories are used in the authentication process,
+    see README.txt.
+    """
+    component.adapts(interfaces.IPrincipalInfo, IRequest)
+
+    interface.implements(interfaces.IAuthenticatedPrincipalFactory)
+
+    def __init__(self, info, request):
+        self.info = info
+        self.request = request
+
+    def __call__(self, authentication):
+        principal = Principal(authentication.prefix + self.info.id,
+                              self.info.title,
+                              self.info.description)
+        notify(interfaces.AuthenticatedPrincipalCreated(
+            authentication, principal, self.info, self.request))
+        return principal
+
+
+class FoundPrincipalFactory(object):
+    """Creates 'found' principals.
+
+    A 'found' principal is created as a result of a principal lookup.
+
+    To use the factory, create it with the info (interfaces.IPrincipalInfo) of
+    the principal to create:
+
+      >>> info = PrincipalInfo('users.sam', 'sam', 'Sam', 'A site user.')
+      >>> factory = FoundPrincipalFactory(info)
+
+    The factory must be called with a pluggable-authentication object:
+
+      >>> class Auth:
+      ...     prefix = 'auth.'
+      >>> auth = Auth()
+
+      >>> principal = factory(auth)
+
+    The factory uses the pluggable-authentication object and the info
+    to create a principal with the same ID, title, and description:
+
+      >>> principal.id
+      'auth.users.sam'
+      >>> principal.title
+      'Sam'
+      >>> principal.description
+      'A site user.'
+
+    It also fires a FoundPrincipalCreatedEvent:
+
+      >>> from zope.component.eventtesting import getEvents
+      >>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
+      >>> event.principal is principal, event.authentication is auth
+      (True, True)
+      >>> event.info
+      PrincipalInfo('users.sam')
+
+    Listeners can subscribe to this event to perform additional operations
+    when the 'found' principal is created.
+
+    For information on how factories are used in the authentication process,
+    see README.txt.
+    """
+    component.adapts(interfaces.IPrincipalInfo)
+
+    interface.implements(interfaces.IFoundPrincipalFactory)
+
+    def __init__(self, info):
+        self.info = info
+
+    def __call__(self, authentication):
+        principal = Principal(authentication.prefix + self.info.id,
+                              self.info.title,
+                              self.info.description)
+        notify(interfaces.FoundPrincipalCreated(authentication,
+                                                principal, self.info))
+        return principal


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,170 @@
+================
+Principal Folder
+================
+
+Principal folders contain principal-information objects that contain principal
+information. We create an internal principal using the `InternalPrincipal`
+class:
+
+  >>> from zope.app.authentication.principalfolder import InternalPrincipal
+  >>> p1 = InternalPrincipal('login1', '123', "Principal 1",
+  ...     passwordManagerName="SHA1")
+  >>> p2 = InternalPrincipal('login2', '456', "The Other One")
+
+and add them to a principal folder:
+
+  >>> from zope.app.authentication.principalfolder import PrincipalFolder
+  >>> principals = PrincipalFolder('principal.')
+  >>> principals['p1'] = p1
+  >>> principals['p2'] = p2
+
+Authentication
+--------------
+
+Principal folders provide the `IAuthenticatorPlugin` interface. When we
+provide suitable credentials:
+
+  >>> from zope.testing.doctestunit import pprint
+  >>> principals.authenticateCredentials({'login': 'login1', 'password': '123'})
+  PrincipalInfo(u'principal.p1')
+
+We get back a principal id and supplementary information, including the
+principal title and description.  Note that the principal id is a concatenation
+of the principal-folder prefix and the name of the principal-information object
+within the folder.
+
+None is returned if the credentials are invalid:
+
+  >>> principals.authenticateCredentials({'login': 'login1',
+  ...                                     'password': '1234'})
+  >>> principals.authenticateCredentials(42)
+
+Search
+------
+
+Principal folders also provide the IQuerySchemaSearch interface.  This
+supports both finding principal information based on their ids:
+
+  >>> principals.principalInfo('principal.p1')
+  PrincipalInfo('principal.p1')
+
+  >>> principals.principalInfo('p1')
+
+and searching for principals based on a search string:
+
+  >>> list(principals.search({'search': 'other'}))
+  [u'principal.p2']
+
+  >>> list(principals.search({'search': 'OTHER'}))
+  [u'principal.p2']
+
+  >>> list(principals.search({'search': ''}))
+  [u'principal.p1', u'principal.p2']
+
+  >>> list(principals.search({'search': 'eek'}))
+  []
+
+  >>> list(principals.search({}))
+  []
+
+If there are a large number of matches:
+
+  >>> for i in range(20):
+  ...     i = str(i)
+  ...     p = InternalPrincipal('l'+i, i, "Dude "+i)
+  ...     principals[i] = p
+
+  >>> pprint(list(principals.search({'search': 'D'})))
+  [u'principal.0',
+   u'principal.1',
+   u'principal.10',
+   u'principal.11',
+   u'principal.12',
+   u'principal.13',
+   u'principal.14',
+   u'principal.15',
+   u'principal.16',
+   u'principal.17',
+   u'principal.18',
+   u'principal.19',
+   u'principal.2',
+   u'principal.3',
+   u'principal.4',
+   u'principal.5',
+   u'principal.6',
+   u'principal.7',
+   u'principal.8',
+   u'principal.9']
+
+We can use batching parameters to specify a subset of results:
+
+  >>> pprint(list(principals.search({'search': 'D'}, start=17)))
+  [u'principal.7',
+   u'principal.8',
+   u'principal.9']
+
+  >>> pprint(list(principals.search({'search': 'D'}, batch_size=5)))
+  [u'principal.0',
+   u'principal.1',
+   u'principal.10',
+   u'principal.11',
+   u'principal.12']
+
+  >>> pprint(list(principals.search({'search': 'D'}, start=5, batch_size=5)))
+  [u'principal.13',
+   u'principal.14',
+   u'principal.15',
+   u'principal.16',
+   u'principal.17']
+
+There is an additional method that allows requesting the principal id
+associated with a login id.  The method raises KeyError when there is
+no associated principal::
+
+  >>> principals.getIdByLogin("not-there")
+  Traceback (most recent call last):
+  KeyError: 'not-there'
+
+If there is a matching principal, the id is returned::
+
+  >>> principals.getIdByLogin("login1")
+  u'principal.p1'
+
+Changing credentials
+--------------------
+
+Credentials can be changed by modifying principal-information objects:
+
+  >>> p1.login = 'bob'
+  >>> p1.password = 'eek'
+
+  >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'})
+  PrincipalInfo(u'principal.p1')
+
+  >>> principals.authenticateCredentials({'login': 'login1',
+  ...                                     'password': 'eek'})
+
+  >>> principals.authenticateCredentials({'login': 'bob',
+  ...                                     'password': '123'})
+
+
+It is an error to try to pick a login name that is already taken:
+
+  >>> p1.login = 'login2'
+  Traceback (most recent call last):
+  ...
+  ValueError: Principal Login already taken!
+
+If such an attempt is made, the data are unchanged:
+
+  >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'})
+  PrincipalInfo(u'principal.p1')
+
+Removing principals
+-------------------
+
+Of course, if a principal is removed, we can no-longer authenticate it:
+
+  >>> del principals['p1']
+  >>> principals.authenticateCredentials({'login': 'bob',
+  ...                                     'password': 'eek'})


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,59 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope"
+    >
+
+  <class class=".principalfolder.InternalPrincipal">
+    <require
+        permission="zope.ManageServices"
+        interface=".principalfolder.IInternalPrincipal"
+        set_schema=".principalfolder.IInternalPrincipal"
+        />
+  </class>
+
+  <class class=".principalfolder.PrincipalFolder">
+
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.container.interfaces.IContainer"
+        />
+
+    <require
+        permission="zope.ManageServices"
+        attributes="prefix"
+        />
+
+  </class>
+
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for=".principalfolder.IInternalPrincipalContainer"
+      factory=".idpicker.IdPicker"
+      />
+
+  <adapter factory=".principalfolder.FoundPrincipalFactory" />
+
+  <adapter factory=".principalfolder.AuthenticatedPrincipalFactory" />
+
+  <include package=".browser" file="principalfolder.zcml" />
+
+  <!-- Registering documentation with API doc -->
+  <configure
+      xmlns:apidoc="http://namespaces.zope.org/apidoc"
+      xmlns:zcml="http://namespaces.zope.org/zcml"
+      zcml:condition="have apidoc">
+
+    <apidoc:bookchapter
+        id="principalfolder"
+        title="Principal Folder"
+        doc_path="principalfolder.txt"
+        parent="security/authentication"
+        />
+
+  </configure>
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/principalfolder.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,689 @@
+##############################################################################
+#
+# 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: session.py 80262 2007-09-27 23:47:04Z srichter $
+"""
+__docformat__ = 'restructuredtext'
+
+import transaction
+from persistent import Persistent
+from urllib import urlencode
+from time import time
+
+from zope.interface import implements, Interface
+from zope.schema import TextLine, Int, Choice
+from zope.component import getUtility
+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.app.container.contained import Contained
+from zope.app.authentication.interfaces import ISessionCredentialsPlugin
+from zope.app.authentication.interfaces import ICredentialsPlugin
+
+from interfaces import IPasswordManager
+
+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, ip=None, domain=None):
+        pass
+
+    def getLogin():
+        """Return login name."""
+
+    def getPassword():
+        """Return password."""
+
+    def getIP():
+        """Return the IP address of the remote address from which the login session
+        originated (IP address is a string)."""
+
+    def getDomain():
+        """Return the domain. The presence of the domain is optional and predicated
+        by the layout of the login form.."""
+
+    def getRequestAnnotations():
+        """Provides a mechanism for passing information from the login form"""
+
+    def getExtractTime():
+        """Returns the time of the original login"""
+
+    def getAccessTime():
+        """Return the time of the last access - can be used to compute idle time"""
+
+    def getPasswordManager():
+        """Return the class used for encrypting the password"""
+
+    def isExpired(timeout):
+        """Returns True if the credentials have expired based on the timeout
+        parameter (specified in minutes)"""
+
+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)
+
+    # Included these here for migration. 
+    ip=None
+    domain=None
+    request_annotations=None
+    extractTime=None
+    accessTime=None
+    passwordManager=None
+
+    def __init__(self, login, password, ip=None, domain=None, request_annotations=None, passwordManager=None):
+        self.login = login
+        if passwordManager:
+            self.password = passwordManager.encodePassword(password)
+        else:
+            self.password = password
+        self.ip = ip
+        self.domain = domain
+        self.request_annotations = request_annotations
+        self.passwordManager=passwordManager
+
+        self.extractTime = time()
+        self.accessTime = 0
+
+    def getLogin(self): return self.login
+
+    def getPassword(self): return self.password
+
+    def getIP(self): return self.ip
+
+    def getDomain(self): return self.domain
+
+    def getRequestAnnotations(self): return self.request_annotations
+
+    def getExtractTime(self): return self.extractTime
+
+    def getAccessTime(self): return self.accessTime
+
+    def getPasswordManager(self): return self.passwordManager
+
+    def isExpired(self, timeout):
+        """Determine whether the session is timed out. If there is no
+        idletime out set, then the self.accessTime value remains at 0"""
+        if timeout <= 0: return False
+        timeNow = time()
+        if self.accessTime == 0:
+            self.bumpAccessTime(timeNow)
+            return False
+        return (timeNow // 60 - self.accessTime // 60) > timeout
+
+    def bumpAccessTime(self, timeNow):
+        """Increment the access time if necessary"""
+        if int(timeNow // 60) > int(self.accessTime // 60):
+            self.accessTime = timeNow
+
+    def __str__(self):
+        if self.domain:
+            return self.getDomain() + ':' + self.getLogin() + ':' + self.getPassword()
+        else:
+            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')
+
+    domainfield = TextLine(
+        title=u'Domainfield (only used if active on login page see loginform.pt)',
+        description=u"Field on the login page in which the login domain is provided if login domains are used.",
+        default=u"domain")
+
+    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")
+
+    idleExpiry = TextLine(
+        title=u'Expiry timeout in minutes for idle sessions (0 for unused)',
+        description=u"Expiry timeout in minutes for idle sessions.",
+        default=u"0")
+
+    passwordManagerName = Choice(
+        title=u'Password Manager Class',
+        description=u"Password Manager Class.",
+        vocabulary=u"Password Manager Names")
+
+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')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['login'] == 'scott'
+      True
+      >>> credentials['password'] == 'tiger'
+      True
+
+    Subsequent requests now have access to the credentials even if they're
+    not explicitly in the request:
+
+      >>> credentials = plugin.extractCredentials(TestRequest()) 
+      >>> credentials['login'] == 'scott'
+      True
+      >>> credentials['password'] == 'tiger'
+      True
+
+    We can always provide new credentials explicitly in the request:
+
+      >>> credentials = plugin.extractCredentials(TestRequest(
+      ...     login='harry', password='hirsch'))
+      >>> credentials['login'] == 'harry'
+      True
+      >>> credentials['password'] == 'hirsch'
+      True
+
+    and these will be used on subsequent requests:
+
+      >>> credentials = plugin.extractCredentials(TestRequest())
+      >>> credentials['login'] == 'harry'
+      True
+      >>> credentials['password'] == 'hirsch'
+      True
+
+    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:
+
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['login'] == 'luke'
+      True
+      >>> credentials['password'] == 'the_force'
+      True
+
+    Set these fields back to their standard names.
+
+      >>> plugin.loginfield = "login"
+      >>> plugin.passwordfield = "password"
+
+    The credentials collector also collects and returns a domain, if it is present,
+    the IP address of the request machine and the time of the credentials extraction.
+
+      >>> request = TestRequest(login='scott', password='tiger', domain='company',
+      ...         REMOTE_ADDR='127.0.0.1')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['domain'] == 'company'
+      True
+      >>> credentials['ip'] == '127.0.0.1'
+      True
+      >>> request.response.getCookie('login.domain')['value']
+      'company'
+
+    And these are also stored in the session data store.
+
+      >>> credentials = plugin.extractCredentials(TestRequest()) 
+      >>> credentials['domain'] == 'company'
+      True
+      >>> credentials['ip'] == '127.0.0.1'
+      True
+
+    The HTTP_X_FORWARDED_FOR header overrides the REMOTE_ADDR header
+
+      >>> request = TestRequest(login='scott', password='tiger', domain='company',
+      ...         REMOTE_ADDR='127.0.0.1', HTTP_X_FORWARDED_FOR='192.168.0.1')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['ip'] == '192.168.0.1'
+      True
+
+    When the user is logging in the 'logging_in' return value is set to True,
+    otherwise it is set to False.
+
+      >>> request = TestRequest(login='scott', password='tiger')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['logging_in'] == True
+      True
+      >>> request = TestRequest()
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['logging_in'] == False
+      True
+
+    A password manager can be configured. The name must match the name of a 
+    utility in the global site manager.
+        
+      >>> class NullManager:
+      ...     implements(IPasswordManager)
+      ...     def encodePassword(self, password):
+      ...         return password.lower()
+      ...     def checkPassword(self, storedPassword, password):
+      ...         return storedPassword == self.encodePassword(password)
+        
+      >>> from zope.app.authentication.interfaces import IPasswordManager
+      >>> manager = NullManager()
+      >>> from zope.component import getGlobalSiteManager
+      >>> gsm = getGlobalSiteManager()
+      >>> gsm.registerUtility(manager, IPasswordManager, 'manager')
+      >>> plugin.passwordManagerName = "manager"
+
+    When an encrypter is configured, the password will be encrypted, and the
+    encrypter will be returned.
+
+      >>> request = TestRequest(login='scott', password='TIGER')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['password'] == 'tiger'
+      True
+      >>> credentials['passwordManager'].encodePassword('TIGER') == credentials['password']
+      True
+
+    The extraction time is recorded. It is returned with the credentials.
+
+      >>> import time
+      >>> request = TestRequest(login='scott', password='TIGER')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> timeNow = time.time()
+      >>> credentials['extractTime'] < timeNow
+      True
+      >>> credentials['extractTime'] > timeNow - 2.0
+      True
+      >>> extracted_time = credentials['extractTime']
+      >>> request = TestRequest()
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> extracted_time == credentials['extractTime']
+      True
+
+    Access time is recorded if there is an idle expiry timeout.
+
+      >>> request = TestRequest(login='scott', password='TIGER')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['accessTime'] == 0
+      True
+      >>> plugin.idleExpiry = 30
+      >>> request = TestRequest()
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['accessTime'] != 0
+      True
+
+    When the credentials time out, they will no longer be returned
+
+      >>> request = TestRequest()
+      >>> session = ISession(request)
+      >>> sessionData = session.get(
+      ...     'zope.app.authentication.browserplugins')
+      >>> credentials = sessionData.get('credentials', None)
+      >>> credentials.accessTime = 1
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials == None
+      True
+
+    When overrides are provided as a parameter to the extractCredentials,
+    they are used in preference to the data on the request.
+
+      >>> request = TestRequest(login='r_login', password='r_pass',
+      ...       REMOTE_ADDR='r_ip', domain='r_domain')
+      >>> overrides = {}
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['login'] == 'r_login'
+      True
+      >>> credentials['password'] == 'r_pass'
+      True
+      >>> credentials['ip'] == 'r_ip'
+      True
+      >>> credentials['domain'] == 'r_domain'
+      True
+      >>> overrides = {'login':'o_login', 'password':'o_pass',
+      ...       'ip':'o_ip', 'domain':'o_domain'}
+      >>> credentials = plugin.extractCredentials(request, overrides)
+      >>> credentials['login'] == 'o_login'
+      True
+      >>> credentials['password'] == 'o_pass'
+      True
+      >>> credentials['ip'] == 'o_ip'
+      True
+      >>> credentials['domain'] == 'o_domain'
+      True
+
+    If the request annotations are attached to the request, then they
+    are stored and passed on with the credentials.
+
+      >>> request = TestRequest(login='r_login', password='r_pass')
+      >>> request.annotations = 'ANNOTATED'
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> credentials['request-annotations']
+      'ANNOTATED'
+
+    Finally, we clear the session credentials using the logout method:
+
+      >>> request = TestRequest(login='scott', password='TIGER')
+      >>> credentials = plugin.extractCredentials(request) 
+      >>> plugin.logout(TestRequest())
+      True
+      >>> print plugin.extractCredentials(TestRequest())
+      None
+
+    """
+    implements(ICredentialsPlugin, IBrowserFormChallenger, ISessionCredentialsPlugin)
+
+    loginpagename = 'loginForm.html'
+    loginfield = 'login'
+    passwordfield = 'password'
+    domainfield = 'domain'
+    idleExpiry = 0
+    passwordManagerName = None
+
+    def extractCredentials(self, request, overrides=None):
+        """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, password, domain, ip) = (None, None, None, None)
+        credentials = None
+        logging_in = False
+
+        if overrides:
+            login = overrides.get('login', None)
+            password = overrides.get('password', None)
+            ip = overrides.get('ip', None)
+            domain = overrides.get('domain', None)
+
+        login = login or request.get(self.loginfield, None)
+        password = password or request.get(self.passwordfield, None)
+        domain = domain or request.get(self.domainfield, None)
+        ip = ip or request.environment.get('HTTP_X_FORWARDED_FOR', None)
+        ip = ip or request.environment.get('REMOTE_ADDR', None)
+
+        if login and password:
+            credentials = SessionCredentials(login, password,
+                ip=ip, domain=domain, request_annotations = request.annotations,
+                passwordManager = self.passwordManager)
+            if IHTTPRequest.providedBy(request):
+                self._update_cookie(request, credentials)
+            logging_in = True
+                    
+        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
+            if credentials.isExpired(self.idleExpiry):
+                return None
+        return {'login': credentials.getLogin(),
+                'password': credentials.getPassword(),
+                'ip': credentials.getIP(),
+                'domain': credentials.getDomain(),
+                'logging_in': logging_in,
+                'request-annotations': credentials.getRequestAnnotations(),
+                'extractTime': credentials.getExtractTime(),
+                'accessTime': credentials.getAccessTime(),
+                'passwordManager': credentials.getPasswordManager(),
+                }
+
+    def _update_cookie(self, request, credentials):
+        """Sets a cookie to record the login domain"""
+        if credentials:
+            domain = credentials.getDomain()
+            if domain and (request.cookies.get('login.domain') != domain):
+                request.response.setCookie(
+                    'login.domain', domain,
+                    expires = 'Wed, 01-Jan-3000 00:00:00 GMT',
+                )
+            return credentials
+
+    @property
+    def passwordManager(self):
+        """Return the Password Manager object in used for encrypting passwords"""
+        if not self.passwordManagerName:
+            return None
+        try:
+            return self._v_cache[self.passwordManagerName]
+        except:
+            pass
+        manager = getUtility(IPasswordManager, self.passwordManagerName, context = self.__parent__)
+        if not hasattr(self, '_v_cache'):
+            self._v_cache = {}
+        try:
+            self._v_cache[self.passwordManagerName] = manager
+        except AttributeError:
+            self._v_cache = {self.passwordManagerName: manager}
+        return manager
+
+    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
+
+    def clearPasswordManager(self, request):
+        """If there is a mistmatch between the encryption used by the
+        principal and the encryption used for the session, then there
+        is no possible recovery. Trigger this function, which will 
+        clear the encryption used for the session. Clear text passwords
+        can flow through to the principal machinery, which can then use
+        it's own configured password manager to do the encryption.
+        
+        To demonstrate, first set up a plugin with a passwordManager
+
+          >>> from zope.publisher.browser import TestRequest
+
+          >>> from zope.session.session import RAMSessionDataContainer
+          >>> from tests import sessionSetUp
+          >>> sessionSetUp(RAMSessionDataContainer)
+
+          >>> plugin = SessionCredentialsPlugin()
+
+          >>> class NullManager:
+          ...     implements(IPasswordManager)
+          ...     def encodePassword(self, password):
+          ...         return password.lower()
+          ...     def checkPassword(self, storedPassword, password):
+          ...         return storedPassword == self.encodePassword(password)
+
+          >>> from zope.app.authentication.interfaces import IPasswordManager
+          >>> manager = NullManager()
+          >>> from zope.component import getGlobalSiteManager
+          >>> gsm = getGlobalSiteManager()
+          >>> gsm.registerUtility(manager, IPasswordManager, 'manager')
+          >>> plugin.passwordManagerName = "manager"
+                
+          >>> request = TestRequest(login='scott', password='TIGER')
+          >>> credentials = plugin.extractCredentials(request) 
+          >>> credentials['password']
+          'tiger'
+
+        Next, clear the password manager
+
+          >>> plugin.clearPasswordManager(request)
+
+        The cached credentials are cleared. The password was encrypted so that it
+        is unusable.
+
+          >>> plugin.extractCredentials(TestRequest())  == None
+          True
+
+        Subsequent login attempts will return unencrypted credentials.
+
+          >>> credentials = plugin.extractCredentials(request) 
+          >>> credentials['passwordManager'] == None
+          True
+          >>> credentials['password']
+          'TIGER'
+
+        """
+            
+        # FORCE THE CHANGE - TODO: LOG, TODO: Work with published interface
+        #print 'clearPasswordManager'
+
+        self.passwordManagerName = None
+
+        session = ISession(request)
+        sessionData = session.get(
+            'zope.app.authentication.browserplugins')
+        del sessionData['credentials']
+


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,28 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope"
+    >
+
+  <utility
+      name="Session Credentials"
+      provides=".interfaces.ICredentialsPlugin"
+      factory=".session.SessionCredentialsPlugin"
+      />
+
+  <class class=".session.SessionCredentialsPlugin">
+
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+
+    <require
+        permission="zope.ManageServices"
+        interface=".session.IBrowserFormChallenger"
+        set_schema=".session.IBrowserFormChallenger"
+        />
+
+  </class>
+
+  <include package=".browser" file="session.zcml" />
+
+</configure>


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/session.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/testing.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/testing.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/testing.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""zope.app.authentication common test related classes/functions/objects.
+
+$Id: testing.py 72477 2007-02-09 03:25:38Z baijum $
+"""
+
+__docformat__ = "reStructuredText"
+
+import os
+from zope.app.testing.functional import ZCMLLayer
+
+AppAuthenticationLayer = ZCMLLayer(
+    os.path.join(os.path.split(__file__)[0], 'ftesting.zcml'),
+    __name__, 'AppAuthenticationLayer', allow_teardown=True)


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/testing.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/tests.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/tests.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/tests.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,133 @@
+##############################################################################
+#
+# 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 Service Tests
+
+$Id: tests.py 80053 2007-09-25 21:28:25Z rogerineichen $
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+
+from zope.testing import doctest
+from zope.interface import implements
+from zope.component import provideUtility, provideAdapter
+from zope.component.eventtesting import getEvents, clearEvents
+from zope.publisher.interfaces import IRequest
+
+from zope.app.testing import placelesssetup, ztapi
+from zope.app.testing.setup import placefulSetUp, placefulTearDown
+from zope.session.interfaces import \
+        IClientId, IClientIdManager, ISession, ISessionDataContainer
+from zope.session.session import \
+        ClientId, Session, PersistentSessionDataContainer
+from zope.session.http import CookieClientIdManager
+
+from zope.publisher import base
+from zope.app.authentication.session import SessionCredentialsPlugin
+
+
+class TestClientId(object):
+    implements(IClientId)
+    def __new__(cls, request):
+        return 'dummyclientidfortesting'
+
+def siteSetUp(test):
+    placefulSetUp(site=True)
+
+def siteTearDown(test):
+    placefulTearDown()
+
+def sessionSetUp(session_data_container_class=PersistentSessionDataContainer):
+    placelesssetup.setUp()
+    ztapi.provideAdapter(IRequest, IClientId, TestClientId)
+    ztapi.provideAdapter(IRequest, ISession, Session)
+    ztapi.provideUtility(IClientIdManager, CookieClientIdManager())
+    sdc = session_data_container_class()
+    ztapi.provideUtility(ISessionDataContainer, sdc, '')
+
+def nonHTTPSessionTestCaseSetUp(sdc_class=PersistentSessionDataContainer):
+    # I am getting an error with ClientId and not TestClientId
+    placelesssetup.setUp()
+    ztapi.provideAdapter(IRequest, IClientId, ClientId)
+    ztapi.provideAdapter(IRequest, ISession, Session)
+    ztapi.provideUtility(IClientIdManager, CookieClientIdManager())
+    sdc = sdc_class()
+    ztapi.provideUtility(ISessionDataContainer, sdc, '')
+
+
+class NonHTTPSessionTestCase(unittest.TestCase):
+    # Small test suite to catch an error with non HTTP protocols, like FTP
+    # and SessionCredentialsPlugin.
+    def setUp(self):
+        nonHTTPSessionTestCaseSetUp()
+
+    def tearDown(self):
+        placefulTearDown()
+
+    def test_exeractCredentials(self):
+        plugin = SessionCredentialsPlugin()
+
+        self.assertEqual(plugin.extractCredentials(base.TestRequest('/')), None)
+
+    def test_challenge(self):
+        plugin = SessionCredentialsPlugin()
+
+        self.assertEqual(plugin.challenge(base.TestRequest('/')), False)
+
+    def test_logout(self):
+        plugin = SessionCredentialsPlugin()
+
+        self.assertEqual(plugin.logout(base.TestRequest('/')), False)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocTestSuite('zope.app.authentication.interfaces'),
+        doctest.DocTestSuite('zope.app.authentication.password'),
+        doctest.DocTestSuite('zope.app.authentication.generic'),
+        doctest.DocTestSuite('zope.app.authentication.httpplugins'),
+        doctest.DocTestSuite('zope.app.authentication.ftpplugins'),
+        doctest.DocTestSuite('zope.app.authentication.groupfolder'),
+        doctest.DocFileSuite('principalfolder.txt',
+                             setUp=placelesssetup.setUp,
+                             tearDown=placelesssetup.tearDown),
+        doctest.DocTestSuite('zope.app.authentication.principalfolder',
+                             setUp=placelesssetup.setUp,
+                             tearDown=placelesssetup.tearDown),
+        doctest.DocTestSuite('zope.app.authentication.idpicker'),
+        doctest.DocTestSuite('zope.app.authentication.session',
+                             setUp=siteSetUp,
+                             tearDown=siteTearDown),
+        doctest.DocFileSuite('README.txt',
+                             setUp=siteSetUp,
+                             tearDown=siteTearDown,
+                             globs={'provideUtility': provideUtility,
+                                    'provideAdapter': provideAdapter,
+                                    'getEvents': getEvents,
+                                    'clearEvents': clearEvents,
+                                    'subscribe': ztapi.subscribe,
+                                    }),
+        doctest.DocFileSuite('groupfolder.txt',
+                             setUp=placelesssetup.setUp,
+                             tearDown=placelesssetup.tearDown,
+                             ),
+        doctest.DocFileSuite('vocabulary.txt',
+                             setUp=placelesssetup.setUp,
+                             tearDown=placelesssetup.tearDown,
+                             ),
+        unittest.makeSuite(NonHTTPSessionTestCase),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.py
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.py	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.py	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Plugin Vocabulary.
+
+This vocabulary provides terms for authentication utility plugins.
+
+$Id: vocabulary.py 73548 2007-03-25 09:05:22Z dobe $
+"""
+__docformat__ = "reStructuredText"
+
+import zope.dublincore.interfaces
+from zope import interface, component, i18n
+from zope.schema import vocabulary
+from zope.schema.interfaces import IVocabularyFactory
+
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+
+from zope.app.authentication import interfaces
+
+UTILITY_TITLE = _(
+    'zope.app.authentication.vocabulary-utility-plugin-title',
+    '${name} (a utility)')
+CONTAINED_TITLE = _(
+    'zope.app.authentication.vocabulary-contained-plugin-title',
+    '${name} (in contents)')
+MISSING_TITLE = _(
+    'zope.app.authentication.vocabulary-missing-plugin-title',
+    '${name} (not found; deselecting will remove)')
+
+def _pluginVocabulary(context, interface, attr_name):
+    """Vocabulary that provides names of plugins of a specified interface.
+
+    Given an interface, the options should include the unique names of all of
+    the plugins that provide the specified interface for the current context--
+    which is expected to be a pluggable authentication utility, hereafter
+    referred to as a PAU).
+
+    These plugins may be objects contained within the PAU ("contained
+    plugins"), or may be utilities registered for the specified
+    interface, found in the context of the PAU ("utility plugins").
+    Contained plugins mask utility plugins of the same name.
+
+    The vocabulary also includes the current values of the PAU even if they do
+    not correspond to a contained or utility plugin.
+    """
+    terms = {}
+    isPAU = interfaces.IPluggableAuthentication.providedBy(context)
+    if isPAU:
+        for k, v in context.items():
+            if interface.providedBy(v):
+                dc = zope.dublincore.interfaces.IDCDescriptiveProperties(
+                    v, None)
+                if dc is not None and dc.title:
+                    title = dc.title
+                else:
+                    title = k
+                terms[k] = vocabulary.SimpleTerm(
+                    k, k.encode('base64').strip(), i18n.Message(
+                        CONTAINED_TITLE, mapping={'name': title}))
+    utils = component.getUtilitiesFor(interface, context)
+    for nm, util in utils:
+        if nm not in terms:
+            terms[nm] = vocabulary.SimpleTerm(
+                nm, nm.encode('base64').strip(), i18n.Message(
+                    UTILITY_TITLE, mapping={'name': nm}))
+    if isPAU:
+        for nm in set(getattr(context, attr_name)):
+            if nm not in terms:
+                terms[nm] = vocabulary.SimpleTerm(
+                    nm, nm.encode('base64').strip(), i18n.Message(
+                        MISSING_TITLE, mapping={'name': nm}))
+    return vocabulary.SimpleVocabulary(
+        [term for nm, term in sorted(terms.items())])
+
+def authenticatorPlugins(context):
+    return _pluginVocabulary(
+        context, interfaces.IAuthenticatorPlugin, 'authenticatorPlugins')
+
+interface.alsoProvides(authenticatorPlugins, IVocabularyFactory)
+
+def credentialsPlugins(context):
+    return _pluginVocabulary(
+        context, interfaces.ICredentialsPlugin, 'credentialsPlugins')
+
+interface.alsoProvides(credentialsPlugins, IVocabularyFactory)


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.txt
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.txt	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.txt	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1,203 @@
+============
+Vocabularies
+============
+
+The vocabulary module provides vocabularies for the authenticator plugins and
+the credentials plugins.
+
+The options should include the unique names of all of the plugins that provide
+the appropriate interface (interfaces.ICredentialsPlugin or
+interfaces.IAuthentiatorPlugin, respectively) for the current context-- which
+is expected to be a pluggable authentication utility, hereafter referred to as
+a PAU.
+
+These names may be for objects contained within the PAU ("contained
+plugins"), or may be utilities registered for the specified interface,
+found in the context of the PAU ("utility plugins").  Contained
+plugins mask utility plugins of the same name.  They also may be names
+currently selected in the PAU that do not actually have a
+corresponding plugin at this time.
+
+Here is a short example of how the vocabulary should work.  Let's say we're
+working with authentication plugins.  We'll create some faux
+authentication plugins, and register some of them as utilities and put
+others in a faux PAU.
+
+    >>> from zope.app.authentication import interfaces
+    >>> from zope import interface, component
+    >>> class DemoPlugin(object):
+    ...     interface.implements(interfaces.IAuthenticatorPlugin)
+    ...     def __init__(self, name):
+    ...         self.name = name
+    ...
+    >>> utility_plugins = dict(
+    ...     (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4))
+    >>> contained_plugins = dict(
+    ...     (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5))
+    >>> sorted(utility_plugins.keys())
+    [0, 1, 2, 3]
+    >>> for p in utility_plugins.values():
+    ...     component.provideUtility(p, name=p.name)
+    ...
+    >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1
+    [1, 2, 3, 4]
+    >>> class DemoAuth(dict):
+    ...     interface.implements(interfaces.IPluggableAuthentication)
+    ...     def __init__(self, *args, **kwargs):
+    ...         super(DemoAuth, self).__init__(*args, **kwargs)
+    ...         self.authenticatorPlugins = (u'Plugin 3', u'Plugin X')
+    ...         self.credentialsPlugins = (u'Plugin 4', u'Plugin X')
+    ...
+    >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
+
+    >>> @component.adapter(interface.Interface)
+    ... @interface.implementer(component.IComponentLookup)
+    ... def getSiteManager(context):
+    ...     return component.getGlobalSiteManager()
+    ...
+    >>> component.provideAdapter(getSiteManager)
+
+We are now ready to create a vocabulary that we can use.  The context is
+our faux authentication utility, `auth`.
+
+    >>> from zope.app.authentication import vocabulary
+    >>> vocab = vocabulary.authenticatorPlugins(auth)
+
+Iterating over the vocabulary results in all of the terms, in a relatively
+arbitrary order of their names.  (This vocabulary should typically use a
+widget that sorts values on the basis of localized collation order of the
+term titles.)
+
+    >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
+    [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4',
+     u'Plugin X']
+
+Similarly, we can use `in` to test for the presence of values in the
+vocabulary.
+
+    >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
+    [False, True, True, True, True, True, False]
+    >>> 'Plugin X' in vocab
+    True
+
+The length reports the expected value.
+
+    >>> len(vocab)
+    6
+
+One can get a term for a given value using `getTerm()`; its token, in
+turn, should also return the same effective term from `getTermByToken`.
+
+    >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
+    ...           'Plugin X']
+    >>> for val in values:
+    ...     term = vocab.getTerm(val)
+    ...     assert term.value == val
+    ...     term2 = vocab.getTermByToken(term.token)
+    ...     assert term2.token == term.token
+    ...     assert term2.value == val
+    ...
+
+The terms have titles, which are message ids that show the plugin title or id
+and whether the plugin is a utility or just contained in the auth utility.
+We'll give one of the plugins a dublin core title just to show the
+functionality.
+
+    >>> import zope.dublincore.interfaces
+    >>> class ISpecial(interface.Interface):
+    ...     pass
+    ...
+    >>> interface.directlyProvides(contained_plugins[1], ISpecial)
+    >>> class DemoDCAdapter(object):
+    ...     interface.implements(
+    ...         zope.dublincore.interfaces.IDCDescriptiveProperties)
+    ...     component.adapts(ISpecial)
+    ...     def __init__(self, context):
+    ...         pass
+    ...     title = u'Special Title'
+    ...
+    >>> component.provideAdapter(DemoDCAdapter)
+
+We need to regenerate the vocabulary, since it calculates all of its data at
+once.
+
+    >>> vocab = vocabulary.authenticatorPlugins(auth)
+
+Now we'll check the titles.  We'll have to translate them to see what we
+expect.
+
+    >>> from zope import i18n
+    >>> import pprint
+    >>> pprint.pprint([i18n.translate(term.title) for term in vocab])
+    [u'Plugin 0 (a utility)',
+     u'Special Title (in contents)',
+     u'Plugin 2 (in contents)',
+     u'Plugin 3 (in contents)',
+     u'Plugin 4 (in contents)',
+     u'Plugin X (not found; deselecting will remove)']
+
+credentialsPlugins
+------------------
+
+For completeness, we'll do the same review of the credentialsPlugins.
+
+    >>> class DemoPlugin(object):
+    ...     interface.implements(interfaces.ICredentialsPlugin)
+    ...     def __init__(self, name):
+    ...         self.name = name
+    ...
+    >>> utility_plugins = dict(
+    ...     (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4))
+    >>> contained_plugins = dict(
+    ...     (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5))
+    >>> for p in utility_plugins.values():
+    ...     component.provideUtility(p, name=p.name)
+    ...
+    >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
+    >>> vocab = vocabulary.credentialsPlugins(auth)
+
+Iterating over the vocabulary results in all of the terms, in a relatively
+arbitrary order of their names.  (This vocabulary should typically use a
+widget that sorts values on the basis of localized collation order of the term
+titles.) Similarly, we can use `in` to test for the presence of values in the
+vocabulary. The length reports the expected value.
+
+    >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
+    [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4',
+     u'Plugin X']
+    >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
+    [False, True, True, True, True, True, False]
+    >>> 'Plugin X' in vocab
+    True
+    >>> len(vocab)
+    6
+
+One can get a term for a given value using `getTerm()`; its token, in
+turn, should also return the same effective term from `getTermByToken`.
+
+    >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
+    ...           'Plugin X']
+    >>> for val in values:
+    ...     term = vocab.getTerm(val)
+    ...     assert term.value == val
+    ...     term2 = vocab.getTermByToken(term.token)
+    ...     assert term2.token == term.token
+    ...     assert term2.value == val
+    ...
+
+The terms have titles, which are message ids that show the plugin title or id
+and whether the plugin is a utility or just contained in the auth utility.
+We'll give one of the plugins a dublin core title just to show the
+functionality. We need to regenerate the vocabulary, since it calculates all
+of its data at once. Then we'll check the titles.  We'll have to translate
+them to see what we expect.
+
+    >>> interface.directlyProvides(contained_plugins[1], ISpecial)
+    >>> vocab = vocabulary.credentialsPlugins(auth)
+    >>> pprint.pprint([i18n.translate(term.title) for term in vocab])
+    [u'Plugin 0 (a utility)',
+     u'Special Title (in contents)',
+     u'Plugin 2 (in contents)',
+     u'Plugin 3 (in contents)',
+     u'Plugin 4 (in contents)',
+     u'Plugin X (not found; deselecting will remove)']


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/vocabulary.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/zope.app.authentication-configure.zcml
===================================================================
--- Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/zope.app.authentication-configure.zcml	                        (rev 0)
+++ Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/zope.app.authentication-configure.zcml	2008-04-15 15:15:28 UTC (rev 85384)
@@ -0,0 +1 @@
+<include package="zope.app.authentication" />


Property changes on: Sandbox/kevingill2/zope.app.authentication/zope/app/authentication/zope.app.authentication-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list