[Checkins] SVN: z3c.authenticator/ Import initial implementation

Roger Ineichen roger at projekt01.ch
Sun Mar 23 07:03:23 EDT 2008


Log message for revision 84862:
  Import initial implementation

Changed:
  A   z3c.authenticator/branches/
  A   z3c.authenticator/tags/
  A   z3c.authenticator/trunk/
  A   z3c.authenticator/trunk/CHANGES.txt
  A   z3c.authenticator/trunk/README.txt
  A   z3c.authenticator/trunk/bootstrap.py
  A   z3c.authenticator/trunk/buildout.cfg
  A   z3c.authenticator/trunk/externals/
  A   z3c.authenticator/trunk/setup.py
  A   z3c.authenticator/trunk/src/
  A   z3c.authenticator/trunk/src/z3c/
  A   z3c.authenticator/trunk/src/z3c/__init__.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/
  A   z3c.authenticator/trunk/src/z3c/authenticator/README.txt
  A   z3c.authenticator/trunk/src/z3c/authenticator/__init__.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/authentication.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/__init__.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/configure.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/edit.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/group.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/group.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/login.pt
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/login.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/schemasearch.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/user.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/browser/user.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/configure.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/credential.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/credential.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/event.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/group.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/group.txt
  A   z3c.authenticator/trunk/src/z3c/authenticator/group.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/principal.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/principal.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/testing.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/tests.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/user.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/user.zcml
  A   z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.py
  A   z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.txt

-=-
Added: z3c.authenticator/trunk/CHANGES.txt
===================================================================
--- z3c.authenticator/trunk/CHANGES.txt	                        (rev 0)
+++ z3c.authenticator/trunk/CHANGES.txt	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,8 @@
+=======
+CHANGES
+=======
+
+Version 0.5.0 (unreleased)
+-------------------------
+
+- Initial Release


Property changes on: z3c.authenticator/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/README.txt
===================================================================
--- z3c.authenticator/trunk/README.txt	                        (rev 0)
+++ z3c.authenticator/trunk/README.txt	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,6 @@
+This package provides an IAuthentication implementation for Zope3. Note that
+this implementation is independent of zope.app.authentication and it doesn't
+depend on that package. This means it doesn't even use the credential or
+authentication plugins offered from zope.app.authentication package.
+I only recommend using this package if you need to implement own authentication
+concepts and you don't like to use zope.app.authentication as dependency.


Property changes on: z3c.authenticator/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/bootstrap.py
===================================================================
--- z3c.authenticator/trunk/bootstrap.py	                        (rev 0)
+++ z3c.authenticator/trunk/bootstrap.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 75940 2007-05-24 14:45:00Z srichter $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: z3c.authenticator/trunk/bootstrap.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/buildout.cfg
===================================================================
--- z3c.authenticator/trunk/buildout.cfg	                        (rev 0)
+++ z3c.authenticator/trunk/buildout.cfg	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,18 @@
+[buildout]
+develop = .
+          externals/z3c.contents
+          externals/z3c.table
+
+parts = test checker coverage
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.authenticator [test]
+
+[checker]
+recipe = lovely.recipe:importchecker
+path = src/z3c/authenticator
+
+[coverage]
+recipe = zc.recipe.egg
+eggs = z3c.coverage

Added: z3c.authenticator/trunk/setup.py
===================================================================
--- z3c.authenticator/trunk/setup.py	                        (rev 0)
+++ z3c.authenticator/trunk/setup.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,86 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup
+
+$Id:$
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup (
+    name='z3c.authenticator',
+    version='0.5.0dev',
+    author = "Roger Ineichen and the Zope Community",
+    author_email = "zope3-dev at zope.org",
+    description = "IAuthentication implementation for for Zope3",
+    long_description=(
+        read('README.txt')
+        + '\n\n' +
+        read('CHANGES.txt')
+        ),
+    license = "ZPL 2.1",
+    keywords = "zope3 z3c json rpc tree",
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Zope Public License',
+        'Programming Language :: Python',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Framework :: Zope3'],
+    url = 'http://cheeseshop.python.org/pypi/z3c.authenticator',
+    packages = find_packages('src'),
+    include_package_data = True,
+    package_dir = {'':'src'},
+    namespace_packages = ['z3c'],
+    extras_require = dict(
+        test = [
+            'z3c.template',
+            'z3c.testing',
+            'zope.app.publication',
+            'zope.app.publisher',
+            'zope.app.security',
+            'zope.app.testing',
+            'zope.configuration',
+            'zope.pagetemplate',
+            'zope.publisher',
+            'zope.testbrowser',
+            ],
+        ),
+    install_requires = [
+        'setuptools',
+        'z3c.i18n',
+        'z3c.template',
+        'z3c.contents',
+        'z3c.table',
+        'z3c.form',
+        'z3c.formui',
+        'zope.app.component',
+        'zope.app.container',
+        'zope.component',
+        'zope.interface',
+        'zope.proxy',
+        'zope.publisher',
+        'zope.security',
+        'zope.traversing',
+        'zope.viewlet',
+
+        ],
+    zip_safe = False,
+)


Property changes on: z3c.authenticator/trunk/setup.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/__init__.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/__init__.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)


Property changes on: z3c.authenticator/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/README.txt
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/README.txt	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/README.txt	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,445 @@
+========================
+X Authentication Utility
+========================
+
+The X Authentication Utility provides a framework for authenticating 
+principals and associating information with them. It uses plugins and 
+subscribers to get its work done.
+
+For a simple authentication utility to be used, it should be registered as a
+utility providing the `zope.app.security.interfaces.IAuthentication` interface.
+
+Our target is to support a handy IAuthentication utility which offers a simple
+API for custom IUser implementations.
+
+
+Security
+--------
+
+The Authenticator supports unique id tokens for principals. This means 
+principal that get deleted and again added with the same id, login etc. do
+not have the same id again. We support this by generate a user id token
+generated by the host id, timestamp, a random string and the login attribute.
+
+
+What's different then in PluggableAuthentication
+------------------------------------------------
+
+We use a different pattern for IAuthenticatorPlugins in this implementation 
+then used in PluggableAuthentication located in zope.app.authentication. 
+Because the pluggable authentication is not very handy when it comes to 
+implement custom principal information. The IPrincipalInfo hook supporting 
+not propagate the password of a IInternalPrincipal is droped in this 
+implementation.
+
+In our implementation we offer a IFoundPrincipal and IAuthenticatedPrincipal
+which are implemented as adapters for a IUser. This adapters do not offer
+it's context which is the real IUser.
+
+The Authenticator doesn't use a prefix. The usage of a prefix is only
+implemented in the IUserContainer. 
+
+We do not use a prefix in the IUserContainer because of the used unique user 
+id tokens. This will make sure that the same principal id doesn't get used at 
+a later time (Common criteria). There is a ``add`` method which creates 
+this id for you based on the login. The __setitem__ should not get used 
+directly for adding IUser instances anymore. We heavily restricted the
+usage of this method. See the inline doc tests in __setitem__ for more info.
+
+
+Authentication
+==============
+
+The primary job of Authenticator 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 Authenticator returns a principal object, 
+if it can. The X Autentication utility 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 
+Authenticator 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.
+
+
+Principal
+---------
+
+First we create a principal:
+
+  >>> from z3c.authenticator import interfaces
+  >>> from z3c.authenticator.user import User
+  >>> login = u'bob'
+  >>> password = u'secret'
+  >>> title = u'Bob'
+  >>> p = User(login, password, title)
+
+Such a principal provides the following attributes be default
+
+  >>> p.login
+  u'bob'
+
+  >>> p.password
+  u'secret'
+
+  >>> p.title
+  u'Bob'
+
+and IUser:
+
+  >>> interfaces.IUser.providedBy(p)
+  True
+
+
+Authenticator Plugin
+--------------------
+
+First setup a UserContainer which will store the principals:
+
+  >>> from z3c.authenticator.user import UserContainer
+  >>> authPlugin = UserContainer()
+  
+Now we have a UserContainer that provides a IUserContainer:
+
+  >>> interfaces.IUserContainer.providedBy(authPlugin)
+  True
+
+Now we will add the created principal to the principal container using the 
+containers method ``add``:
+
+  >>> uid, user = authPlugin.add(p)
+
+The method returns the user id and the user object. The id get generated
+by the host IP address, the time, a random string and the user login attr.
+This token should be unique and guranted that it never get generated twice.
+This allows us to add, delete and add the same user again without that such a 
+user will inherit existing permissions. We can test this token by compare it
+only with the __name__ of the object in this test since the token will 
+different every testrun.
+
+  >>> user.__name__ == uid
+  True
+
+The returned user still is our previous added IUser
+
+  >>> user is p
+  True
+
+  >>> len(user.__name__)
+  32
+
+  >>> user.login
+  u'bob'
+
+  >>> user.password
+  u'secret'
+
+  >>> user.title
+  u'Bob'
+
+let's register the UserContainer as a named IAuthenticatorPlugin utility:
+
+  >>> import zope.component
+  >>> zope.component.provideUtility(authPlugin, 
+  ...     provides=interfaces.IAuthenticatorPlugin, 
+  ...     name='My Authenticator Plugin')
+ 
+
+Credentials Plugin
+------------------
+
+After setup the user and user container, we'll create a simple credentials 
+plugin:
+
+  >>> import zope.interface
+  >>> import zope.component
+
+  >>> class MyCredentialsPlugin(object):
+  ...
+  ...     zope.interface.implements(interfaces.ICredentialsPlugin)
+  ...
+  ...     def extractCredentials(self, request):
+  ...         return {'login':request.get('login', ''), 
+  ...                 'password':request.get('password', '')}
+  ...
+  ...     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 or
+it could be stored in the Authenticator attribute credentialsPlugins.
+Use the first and register the plugina utility:
+
+  >>> myCredentialsPlugin = MyCredentialsPlugin()
+  >>> zope.component.provideUtility(myCredentialsPlugin, 
+  ...     name='My Credentials Plugin')
+
+
+AuthenticatedPrincipal and FoundPrincipal
+-----------------------------------------
+
+While authenticator plugins provide user, they are not responsible for 
+creating principals. This function is performed by the Authenticator:
+
+  >>> from z3c.authenticator.principal import AuthenticatedPrincipal
+  >>> from z3c.authenticator.principal import FoundPrincipal
+  >>> zope.component.provideAdapter(AuthenticatedPrincipal, 
+  ...     provides=interfaces.IAuthenticatedPrincipal)
+
+  >>> zope.component.provideAdapter(FoundPrincipal, 
+  ...     provides=interfaces.IFoundPrincipal)
+
+
+Configuring Authenticator
+-------------------------
+
+Finally, we'll create the Authenticator itself:
+
+  >>> from z3c.authenticator import authentication
+  >>> auth = authentication.Authenticator()
+
+and configure it with the two plugins:
+
+  >>> auth.credentialsPlugins = ('My Credentials Plugin', )
+  >>> auth.authenticatorPlugins = ('My Authenticator Plugin', )
+
+
+Authenticate
+------------
+
+We can now use the Authenticator to authenticate a sample request:
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> print auth.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:
+
+  >>> request = TestRequest(form={'login':'let me in!', 'password':'secret'})
+  >>> print auth.authenticate(request)
+  None
+
+However, if we provide the proper credentials:
+
+  >>> request = TestRequest(form={'login':'bob', 'password':'secret'})
+  >>> bob = auth.authenticate(request)
+  >>> bob
+  <AuthenticatedPrincipal...>
+
+  >>> interfaces.IAuthenticatedPrincipal.providedBy(bob)
+  True
+
+we get an authenticated principal.
+
+
+Change login
+------------
+
+Change the login of a principal is a allwas a critical task because such a 
+login together with a password is the key to our implemenation. Let's try to
+change the login and check if everything is correct. We can do this by get the
+principal from the UserContainer and change the login on the IUser
+implementation:
+
+  >>> internal = authPlugin[bob.id]
+  >>> internal.login = u'bob2'
+
+Now we should be able to login with the new login:
+
+  >>> request = TestRequest(form={'login':'bob2', 'password':'secret'})
+  >>> bob2 = auth.authenticate(request)
+  >>> bob2
+  <AuthenticatedPrincipal ...>
+
+  >>> bob2.title
+  u'Bob'
+
+But not with the old one:
+
+  >>> request = TestRequest(form={'login':'bob', 'password':'secret'})
+  >>> auth.authenticate(request) == None
+  True
+
+The user bob has still the same id as bob2 since the user id token doesn't 
+get changed be changing the login:
+
+  >>> bob.id == bob2.id
+  True
+
+
+Events
+------
+
+Authenticate principal will create events.
+
+  >>> from zope.component.eventtesting import getEvents
+  >>> from zope.component.eventtesting import clearEvents
+
+We can verify that the appropriate event was published:
+
+  >>> clearEvents()
+  >>> request = TestRequest(form={'login':'bob2', 'password':'secret'})
+  >>> bobAgain = auth.authenticate(request)
+
+And the principal attribute in the event provides the authenticated principal:
+
+  >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
+  >>> event.principal is bobAgain
+  True
+
+  >>> event.principal
+  <AuthenticatedPrincipal ...>
+
+  >>> event.request is request
+  True
+
+The principal has the id, title, and description.
+
+  >>> event.principal.title
+  u'Bob'
+
+  >>> event.principal.id == uid
+  True
+
+  >>> event.principal.description
+  u''
+
+We provide subscribers to these events that can be used for doing custom 
+processing. Note, the principal attibute provides a IAuthenticatedPrincipal:
+
+  >>> def addInfo(event):
+  ...     id = event.principal.id
+  ...     event.principal.description = 'Description for: %s' % id
+
+  >>> zope.component.provideHandler(addInfo, 
+  ...     [interfaces.IAuthenticatedPrincipalCreated])
+
+Now, if we authenticate a principal, its description is set:
+
+  >>> principal = auth.authenticate(request)
+  >>> principal.description
+  u'Description for: ...'
+
+
+Customization
+-------------
+
+Let's show you how the existing pattern can get used in a real use case. In 
+the next sample we like to provide a additional email attribute for principals.
+First we have to define the interfaces declaring the email attribute:
+
+  >>> class IMyEmail(zope.interface.Interface):
+  ...     email = zope.schema.TextLine(
+  ...         title=u'EMail', 
+  ...         description=u'The EMail')
+
+  >>> class IMyUser(IMyEmail, interfaces.IUser):
+  ...     """Custom IUser interface."""
+
+  >>> class IMyFoundPrincipal(IMyEmail, interfaces.IFoundPrincipal):
+  ...     """Custom IIMyFoundrincipal interface."""
+
+  >>> class IMyAuthenticatedPrincipal(IMyEmail, 
+  ...     interfaces.IAuthenticatedPrincipal):
+  ...     """Custom IAuthenticatedPrincipal interface."""
+
+After the schema, we define a custom principal implementation implementing 
+this interface:
+
+  >>> class MyUser(User):
+  ...     zope.interface.implements(IMyUser)
+  ...     def __init__(self, login, password, title, description, email):
+  ...         super(MyUser, self).__init__(login, password, title, 
+  ...                                           description)
+  ...         self.email = email
+
+Now we have to define the AuthenticatedPrincipal for MyUser:
+
+  >>> class MyAuthenticatedPrincipal(AuthenticatedPrincipal):
+  ...     zope.interface.implements(IMyAuthenticatedPrincipal)
+  ...     def __init__(self, principal):
+  ...         super(MyAuthenticatedPrincipal, self).__init__(principal)
+  ...         self.email = principal.email
+
+And we have to define the FoundPrincipal for MyUser:
+
+  >>> class MyFoundPrincipal(FoundPrincipal):
+  ...     zope.interface.implements(IMyFoundPrincipal)
+  ...     def __init__(self, principal):
+  ...         super(MyFoundPrincipal, self).__init__(principal)
+  ...         self.email = principal.email
+
+Note that you can provide different attributes for the found and authenticated
+principal if needed. That's up to you what you like to do with this attributes 
+later.
+
+Now we need to register our custom authenticated and found principal as 
+adapters:
+
+  >>> zope.component.provideAdapter(MyAuthenticatedPrincipal, 
+  ...     provides=interfaces.IAuthenticatedPrincipal)
+
+  >>> zope.component.provideAdapter(MyFoundPrincipal, 
+  ...     provides=interfaces.IFoundPrincipal)
+
+Now we can use them without any other event subscriber or other registration
+in our principal container. Let's add a principal tho this container:
+
+  >>> p = MyUser(u'max', u'password', u'Max', u'', u'max at foobar.com')
+  >>> token, max = authPlugin.add(p)
+  >>> len(token)
+  32
+
+  >>> max.__name__ == token
+  True
+
+  >>> max.password
+  u'password'
+
+  >>> max.title
+  u'Max'
+
+  >>> max.email
+  u'max at foobar.com'
+
+Let's try to authenticate...
+
+  >>> request = TestRequest(form={'login':'max', 'password':'password'})
+  >>> authenticated = auth.authenticate(request)
+
+and check your authenticated principal:
+
+  >>> interfaces.IAuthenticatedPrincipal.providedBy(authenticated)
+  True
+
+  >>> authenticated
+  <MyAuthenticatedPrincipal ...>
+
+  >>> authenticated.id == token
+  True
+
+  >>> authenticated.email
+  u'max at foobar.com'
+


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/__init__.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/__init__.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/__init__.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1 @@
+# make a package


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/authentication.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/authentication.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/authentication.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,177 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.event
+from zope.schema.interfaces import ISourceQueriables
+from zope.location.interfaces import ILocation
+
+from zope.app.component import queryNextUtility
+from zope.app.container import btree
+from zope.app.security.interfaces import IAuthentication
+from zope.app.security.interfaces import PrincipalLookupError
+
+from z3c.authenticator import interfaces
+from z3c.authenticator import event
+
+
+class Authenticator(btree.BTreeContainer):
+    """See z3c.authentication.interfaces.IAuthenticator."""
+
+    zope.interface.implements(IAuthentication, 
+        interfaces.IAuthenticator, ISourceQueriables)
+
+    authenticatorPlugins = ()
+    credentialsPlugins = ()
+
+    def _plugins(self, names, interface):
+        for name in names:
+            plugin = self.get(name)
+            if not interface.providedBy(plugin):
+                plugin = zope.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)
+            if credentials is None:
+                # do not invoke the auth plugin without credentials
+                continue
+            for authplugin in authenticatorPlugins:
+                if authplugin is None:
+                    continue
+                principal = authplugin.authenticateCredentials(credentials)
+                if principal is None:
+                    continue
+
+                # create authenticated principal
+                authenticated = interfaces.IAuthenticatedPrincipal(principal)
+
+                # send the IAuthenticatedPrincipalCreated event
+                zope.event.notify(event.AuthenticatedPrincipalCreated(
+                    self, authenticated, request))
+                return authenticated
+
+        return None
+
+    def getPrincipal(self, id):
+        for name, authplugin in self.getAuthenticatorPlugins():
+            principal = authplugin.queryPrincipal(id)
+            if principal is None:
+                continue
+
+            # create found principal
+            found = interfaces.IFoundPrincipal(principal)
+
+            # send the IFoundPrincipalCreated event
+            zope.event.notify(event.FoundPrincipalCreated(self, found))
+            return found
+
+        next = queryNextUtility(self, IAuthentication)
+        if next is not None:
+            return next.getPrincipal(id)
+        raise PrincipalLookupError(id)
+
+    def getQueriables(self):
+        for name, authplugin in self.getAuthenticatorPlugins():
+            queriable = zope.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)..
+    """
+    zope.component.adapts(
+        interfaces.IQuerySchemaSearch,
+        interfaces.IAuthenticator)
+
+    zope.interface.implements(
+        interfaces.IQueriableAuthenticator,
+        interfaces.IQuerySchemaSearch,
+        ILocation)
+
+    def __init__(self, authplugin, pau):
+        if ILocation.providedBy(authplugin):
+            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 id
+
+


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/authentication.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/__init__.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/__init__.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/__init__.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,2 @@
+# make a package
+


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.event
+import zope.lifecycleevent
+from zope.traversing.browser import absoluteURL
+import zope.schema
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+from z3c.authenticator import group
+from z3c.authenticator import user
+from z3c.form import field
+from z3c.form import button
+from z3c.formui import form
+from z3c.pagelet import browser
+from z3c.configurator import configurator
+
+
+class IAddName(zope.interface.Interface):
+    """Object name."""
+
+    __name__ = zope.schema.TextLine( 
+        title=u'Object Name',
+        description=u'Object Name',
+        required=True)
+
+
+# IUserContainer
+class AuthenticatorAddForm(form.AddForm):
+    """Authenticator add form."""
+
+    label = _('Add Authenticatorr.')
+    contentName = None
+
+    fields = field.Fields(IAddName)
+
+    def createAndAdd(self, data):
+        obj = user.UserContainer()
+        self.contentName = data.get('__name__', u'')
+        zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
+        self.context[self.contentName] = obj
+
+        #configure
+        configurator.configure(obj, data)
+        return obj
+
+    def nextURL(self):
+        obj = self.context[self.contentName]
+        return '%s/index.html' % absoluteURL(obj, self.request)
+
+
+class AuthenticatorEditForm(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit Authenticator.')
+
+    fields = field.Fields(interfaces.IAuthenticator).select('credentialsPlugins',
+        'authenticatorPlugins')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,33 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/browser"
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="z3c">
+
+  <z3c:pagelet
+      name="addAuthenticator.html"
+      for="..interfaces.IAuthenticator"
+      class=".authenticator.AuthenticatorAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <z3c:pagelet
+      name="edit.html"
+      for="..interfaces.IAuthenticator"
+      class=".authenticator.AuthenticatorEditForm"
+      permission="zope.ManageServices"
+      />
+
+  <z3c:pagelet
+      name="contents.html"
+      for="..interfaces.IAuthenticator"
+      class="z3c.contents.browser.ContentsPage"
+      permission="zope.ManageSite"
+      />
+
+  <defaultView
+      for="..interfaces.IAuthenticator"
+      name="contents.html"
+      />
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/authenticator.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/configure.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/configure.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/configure.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,20 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="z3c">
+
+  <zope:adapter
+      for="..interfaces.IQuerySchemaSearch
+           zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.app.form.browser.interfaces.ISourceQueryView"
+      factory=".schemasearch.QuerySchemaSearchView"
+      />
+
+  <include file="authenticator.zcml" />
+  <include file="credential.zcml" />
+  <include file="group.zcml" />
+  <include file="login.zcml" />
+  <include file="user.zcml" />
+
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+from z3c.i18n import MessageFactory as _
+from z3c.form import field
+from z3c.formui import form
+
+from z3c.authenticator import interfaces
+
+
+class SessionCredentialsPluginEditForm(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit SessionCredentialsPlugin.')
+
+    fields = field.Fields(interfaces.ISessionCredentialsPlugin).select(
+        'loginpagename', 'loginfield', 'passwordfield')
+
+
+class HTTPBasicAuthCredentialsPluginEditForm(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit HTTPBasicAuthCredentialsPlugin.')
+
+    fields = field.Fields(interfaces.IHTTPBasicAuthCredentialsPlugin).select(
+        'realm')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,31 @@
+<configure 
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="z3c">
+
+  <z3c:pagelet
+      name="edit.html"
+      for="..interfaces.ISessionCredentialsPlugin"
+      class=".credential.SessionCredentialsPluginEditForm"
+      permission="zope.ManageServices"
+      />
+
+  <z3c:pagelet
+      name="edit.html"
+      for="..interfaces.IHTTPBasicAuthCredentialsPlugin"
+      class=".credential.HTTPBasicAuthCredentialsPluginEditForm"
+      permission="zope.ManageServices"
+      />
+
+
+
+  <!-- register a loginForm.html page for your layer
+  <page
+      name="loginForm.html" 
+      for="*"
+      template="loginform.pt"
+      permission="zope.Public" 
+      />
+  -->
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/credential.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/edit.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/edit.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/edit.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+from z3c.formui import form
+from z3c.form import field
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+
+
+class AuthenticatorEditForm(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit Authenticator.')
+
+    fields = field.Fields(interfaces.IAuthenticator).select(
+        'credentialsPlugins', 'authenticatorPlugins')
+
+
+class EditGroup(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit Group.')
+
+    form_fields = form.Fields(interfaces.IGroup).select('title', 
+        'description', 'principals')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/edit.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/group.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/group.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/group.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.event
+import zope.lifecycleevent
+from zope.traversing.browser import absoluteURL
+import zope.schema
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+from z3c.authenticator import group
+from z3c.authenticator import user
+from z3c.form import field
+from z3c.form import button
+from z3c.formui import form
+from z3c.pagelet import browser
+from z3c.configurator import configurator
+
+
+class IAddName(zope.interface.Interface):
+    """Object name."""
+
+    __name__ = zope.schema.TextLine( 
+        title=u'Object Name',
+        description=u'Object Name',
+        required=True)
+
+
+# IGroupContainer
+class GroupContainerAddForm(form.AddForm):
+    """GroupContainer add form."""
+
+    label = _('Add Group Container.')
+
+    fields = field.Fields(IAddName)
+    fields += field.Fields(interfaces.IGroupContainer).select(
+        'prefix')
+
+    def createAndAdd(self, data):
+        obj = group.GroupContainer()
+        obj.prefix = data.get('prefix', u'')
+        self.contentName = data.get('__name__', u'')
+        zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
+        self.context[self.contentName] = obj
+
+        #configure
+        configurator.configure(obj, data)
+        return obj
+
+    def nextURL(self):
+        obj = self.context[self.contentName]
+        return '%s/index.html' % absoluteURL(obj, self.request)
+
+
+# IGroup
+class GroupAddForm(form.AddForm):
+    """Group add form."""
+
+    label = _('Add Group.')
+
+    fields = field.Fields(IAddName)
+    fields += field.Fields(interfaces.IGroup).select('title', 'description')
+
+    def createAndAdd(self, data):
+        title = data.get('title', u'')
+        description = data.get('description', u'')
+        obj = group.Group(title, description)
+        name = data.get('__name__', u'')
+        prefix = self.context.prefix
+        if not name.startswith(prefix):
+            name = prefix + name
+        self.contentName = name
+        zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
+        self.context[self.contentName] = obj
+
+        #configure
+        configurator.configure(obj, data)
+        return obj
+
+    def nextURL(self):
+        obj = self.context[self.contentName]
+        return '%s/index.html' % absoluteURL(obj, self.request)
+
+
+class GroupEditForm(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit Group.')
+
+    fields = field.Fields(interfaces.IGroup).select('title', 'description')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/group.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/group.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/group.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/group.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,31 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/browser"
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="z3c">
+
+  <!-- Group -->
+  <z3c:pagelet
+      name="addGroup.html"
+      for="..interfaces.IGroupContainer"
+      class=".group.GroupAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <z3c:pagelet
+      name="edit.html"
+      for="..interfaces.IGroup"
+      class=".group.GroupEditForm"
+      menu="zmi_views" title="Edit"
+      permission="zope.ManageServices"
+      />
+
+  <!-- GroupContainer -->
+  <z3c:pagelet
+      name="addGroupContainer.html"
+      for="..interfaces.IAuthenticator"
+      class=".group.GroupContainerAddForm"
+      permission="zope.ManageServices"
+      />
+
+</configure> 


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/group.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.pt
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/login.pt	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/login.pt	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,19 @@
+<metal:block use-macro="macro:form">
+  <div metal:fill-slot="viewspace">
+    <metal:block use-macro="macro:form-header">
+      header
+    </metal:block>
+    <p tal:content="view/message" i18n:translate="">Please provide Login Information</p>
+    <p tal:condition="python:request.get('lostPassword')"
+       i18n:translate="">The password coverage process was started by mail, check
+      your mailbox for more information.</p>
+    <metal:block use-macro="macro:widget-rows">
+      widgets
+    </metal:block>
+  </div>
+</metal:block>
+<div class="linkRow">
+  <a href="loginForm.html.html"
+     tal:attributes="href string:${request/URL/-1}/sendPassword.html">Ich habe
+  mein Passwort vergessen.</a>
+</div>
\ No newline at end of file


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2007 Projekt01 GmbH.
+# All Rights Reserved.
+#
+##############################################################################
+"""
+$Id: login.py 357 2007-03-15 17:17:37Z roger.ineichen $
+"""
+__docformat__ = "reStructuredText"
+
+from urllib import urlencode
+from urllib import quote
+import zope.interface
+import zope.component
+import zope.schema
+from zope.interface import invariant
+from zope.interface import Invalid
+from zope.publisher.browser import BrowserPage
+from zope.traversing.browser import absoluteURL
+from zope.security.proxy import removeSecurityProxy
+from zope.app.component import hooks
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.app.security.interfaces import IUnauthenticatedPrincipal
+from zope.app.security.interfaces import IAuthentication
+from zope.app.security.interfaces import ILogout
+
+from z3c.form.interfaces import HIDDEN_MODE
+from z3c.form.interfaces import IWidgets
+from z3c.form import field
+from z3c.form import button
+from z3c.formui import form
+import z3c.schema.email
+from z3c.template.template import getPageTemplate
+from z3c.template.template import getLayoutTemplate
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+
+
+class LoginForm(form.Form):
+    """Login form."""
+
+    template = getPageTemplate()
+    layout = getLayoutTemplate()
+
+    fields = field.Fields(interfaces.ILoginSchema)
+    nextURL = None
+    prefix = ''
+
+    def getCameFrom(self):
+        camefrom = self.request.get('camefrom', None)
+        if camefrom is None:
+            site = hooks.getSite()
+            camefrom = '%s/index.html' % absoluteURL(site, self.request)
+        return camefrom
+
+    def updateWidgets(self):
+        self.widgets = zope.component.getMultiAdapter(
+            (self, self.request, self.getContent()), IWidgets)
+        self.widgets.prefix = ''
+        self.widgets.ignoreContext = True
+        self.widgets.ignoreReadonly = True
+        self.widgets.update()
+        self.widgets['camefrom'].mode = HIDDEN_MODE
+        self.widgets['camefrom'].value = self.getCameFrom()
+
+    @property
+    def message(self):
+        if IUnauthenticatedPrincipal.providedBy(self.request.principal):
+            return _(u'Please provide Login Information')
+        return u''
+
+    @button.buttonAndHandler(_('Log-in'))
+    def handleLogin(self, action):
+        """Handle the subscribe action will register and login a user."""
+        if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
+            data, errors = self.widgets.extract()
+            self.nextURL = data['camefrom']
+
+    def __call__(self):
+        self.update()
+        if self.nextURL is not None:
+            self.request.response.redirect(self.nextURL)
+            return ""
+        else:
+            return self.layout()
+
+
+class SiteLogout(BrowserPage):
+
+    def __call__(self):
+        """Force logout and avoid to hang around the login form."""
+        auth = zope.component.getUtility(IAuthentication)
+        ILogout(auth).logout(self.request)
+        siteURL = absoluteURL(hooks.getSite(), self.request)
+        self.request.response.redirect(siteURL + '/loginForm.html')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/login.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/login.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,28 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="xauth">
+
+  <!-- login form -->
+  <z3c:pagelet
+      name="loginForm.html"
+      for="*"
+      class=".login.LoginForm"
+      permission="zope.Public"
+      />
+
+  <z3c:template
+      template="login.pt"
+      for=".login.LoginForm"
+      />
+
+  <!-- logout form -->
+  <page
+      name="logout.html"
+      for="*"
+      class=".login.SiteLogout"
+      permission="zope.Public"
+      />
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/schemasearch.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/schemasearch.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/schemasearch.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,111 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__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 z3c.i18n import MessageFactory 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: z3c.authenticator/trunk/src/z3c/authenticator/browser/schemasearch.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/user.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/user.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/user.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.event
+import zope.lifecycleevent
+from zope.traversing.browser import absoluteURL
+import zope.schema
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+from z3c.authenticator import group
+from z3c.authenticator import user
+from z3c.form import field
+from z3c.form import button
+from z3c.formui import form
+from z3c.pagelet import browser
+from z3c.configurator import configurator
+
+
+class IAddName(zope.interface.Interface):
+    """Object name."""
+
+    __name__ = zope.schema.TextLine( 
+        title=u'Object Name',
+        description=u'Object Name',
+        required=True)
+
+
+# IUserContainer
+class UserContainerAddForm(form.AddForm):
+    """UserContainer add form."""
+
+    label = _('Add User Container.')
+    contentName = None
+
+    fields = field.Fields(IAddName)
+
+    def createAndAdd(self, data):
+        obj = user.UserContainer()
+        self.contentName = data.get('__name__', u'')
+        zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
+        self.context[self.contentName] = obj
+
+        #configure
+        configurator.configure(obj, data)
+        return obj
+
+    def nextURL(self):
+        obj = self.context[self.contentName]
+        return '%s/index.html' % absoluteURL(obj, self.request)
+
+
+# IUser
+class UserAddForm(form.AddForm):
+    """User add form."""
+
+    label = _('Add User.')
+
+    fields = field.Fields(IAddName)
+    fields += field.Fields(interfaces.IUser).select('login', 'password', 
+        'title', 'description', 'passwordManagerName')
+
+    def createAndAdd(self, data):
+        login = data.get('login', u'')
+        password = data.get('password', u'')
+        title = data.get('title', u'')
+        description = data.get('description', u'')
+        passwordManagerName = data.get('passwordManagerName', u'')
+        obj = user.User(login, password, title, description, 
+            passwordManagerName)
+        self.contentName = data.get('__name__', u'')
+        zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
+        self.context[self.contentName] = obj
+
+        #configure
+        configurator.configure(obj, data)
+        return obj
+
+    def nextURL(self):
+        obj = self.context[self.contentName]
+        return '%s/index.html' % absoluteURL(obj, self.request)
+
+
+class UserEditForm(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit User.')
+
+    fields = field.Fields(interfaces.IUser).select('login', 'password', 
+        'title', 'description', 'passwordManagerName')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/user.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/browser/user.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/user.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/user.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,30 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/browser"
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="z3c">
+
+  <!-- UserContainer -->
+  <z3c:pagelet
+      name="addUserContainer.html"
+      for="..interfaces.IAuthenticator"
+      class=".user.UserContainerAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <!-- User -->
+  <z3c:pagelet
+      name="addUser.html"
+      for="..interfaces.IUserContainer"
+      class=".user.UserAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <z3c:pagelet
+      name="edit.html"
+      for="..interfaces.IUser"
+      class=".user.UserEditForm"
+      permission="zope.ManageServices"
+      />
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/browser/user.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/configure.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/configure.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/configure.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,44 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <class class=".authentication.Authenticator">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageSite"
+        interface=".interfaces.IAuthenticator"
+        set_schema=".interfaces.IAuthenticator"
+        />
+    <require
+        permission="zope.ManageServices"
+        attributes="registrationManager"
+        />
+  </class>
+
+  <adapter
+      for=".interfaces.IQuerySchemaSearch
+           .interfaces.IAuthenticator"
+      provides=".interfaces.IQueriableAuthenticator"
+      factory=".authentication.QuerySchemaSearchAdapter"
+      />
+
+  <utility
+      component=".vocabulary.authenticatorPlugins"
+      name="Z3CAuthenticatorPlugins"
+      />
+
+  <utility
+      component=".vocabulary.credentialsPlugins"
+      name="Z3CCredentialsPlugins"
+      />
+
+  <include file="principal.zcml" />
+  <include file="group.zcml" />
+  <include file="user.zcml" />
+  <include file="credential.zcml" />
+
+  <include package=".browser" />
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/credential.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/credential.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/credential.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,367 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import base64
+import transaction
+import persistent
+from urllib import urlencode
+
+import zope.interface
+from zope.schema import TextLine
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.session.interfaces import ISession
+from zope.traversing.browser.absoluteurl import absoluteURL
+
+from zope.app.component import hooks
+from zope.app.container import contained
+
+from z3c.authenticator import interfaces
+
+
+class HTTPBasicAuthCredentialsPlugin(persistent.Persistent,
+    contained.Contained):
+
+
+    zope.interface.implements(interfaces.IHTTPBasicAuthCredentialsPlugin)
+
+    realm = 'Zope Application Management'
+
+    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
+
+
+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'
+
+    """
+    zope.interface.implements(interfaces.ISessionCredentials)
+
+    def __init__(self, login, password):
+        self.login = login
+        self.password = password
+
+    def getLogin(self): return self.login
+
+    def getPassword(self): return self.password
+
+    def __str__(self): return self.getLogin() + ':' + self.getPassword()
+
+
+class SessionCredentialsPlugin(persistent.Persistent, contained.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 zope.app.authentication.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 zope.publisher.tests.httprequest import TestRequest
+      >>> request = TestRequest()
+      >>> print plugin.extractCredentials(request)
+      None
+
+    We must explicitly provide credentials once so the plugin can store
+    them in a session:
+
+      >>> request = TestRequest(login='scott', password='tiger')
+      >>> plugin.extractCredentials(request)
+      {'login': 'scott', 'password': 'tiger'}
+
+    Subsequent requests now have access to the credentials even if they're
+    not explicitly in the request:
+
+      >>> plugin.extractCredentials(TestRequest())
+      {'login': 'scott', 'password': 'tiger'}
+
+    We can always provide new credentials explicitly in the request:
+
+      >>> plugin.extractCredentials(TestRequest(
+      ...     login='harry', password='hirsch'))
+      {'login': 'harry', 'password': 'hirsch'}
+
+    and these will be used on subsequent requests:
+
+      >>> plugin.extractCredentials(TestRequest())
+      {'login': 'harry', 'password': 'hirsch'}
+
+    We can also change the fields from which the credentials are extracted:
+
+      >>> plugin.loginfield = "my_new_login_field"
+      >>> plugin.passwordfield = "my_new_password_field"
+
+    Now we build a request that uses the new fields:
+
+      >>> request = TestRequest(my_new_login_field='luke', my_new_password_field='the_force')
+
+    The plugin now extracts the credentials information from these new fields:
+
+      >>> plugin.extractCredentials(request)
+      {'login': 'luke', 'password': 'the_force'}
+
+    Finally, we clear the session credentials using the logout method:
+
+      >>> plugin.logout(TestRequest())
+      True
+      >>> print plugin.extractCredentials(TestRequest())
+      None
+
+    """
+    zope.interface.implements(interfaces.ISessionCredentialsPlugin)
+
+    challengeProtocol = None
+
+    loginpagename = 'loginForm.html'
+    loginfield = 'login'
+    passwordfield = 'password'
+
+    def extractCredentials(self, request):
+        """Extracts credentials from a session if they exist."""
+        if not IHTTPRequest.providedBy(request):
+            return None
+        session = ISession(request, None)
+        sessionData = session.get('z3c.authenticator.credential.session')
+        login = request.get(self.loginfield, None)
+        password = request.get(self.passwordfield, None)
+        credentials = None
+
+        if login and password:
+            credentials = SessionCredentials(login, password)
+        elif not sessionData:
+            return None
+        sessionData = session['z3c.authenticator.credential.session']
+        if credentials:
+            sessionData['credentials'] = credentials
+        else:
+            credentials = sessionData.get('credentials', None)
+        if not credentials:
+            return None
+        return {'login': credentials.getLogin(),
+                'password': credentials.getPassword()}
+
+    def challenge(self, request):
+        """Challenges by redirecting to a login form.
+
+        To illustrate, we'll create a test request:
+
+          >>> from zope.publisher.browser import TestRequest
+          >>> request = TestRequest()
+
+        and confirm its response's initial status and 'location' header:
+
+          >>> request.response.getStatus()
+          599
+          >>> request.response.getHeader('location')
+
+        When we issue a challenge using a session plugin:
+
+          >>> plugin = SessionCredentialsPlugin()
+          >>> plugin.challenge(request)
+          True
+
+        we get a redirect:
+
+          >>> request.response.getStatus()
+          302
+          >>> request.response.getHeader('location')
+          'http://127.0.0.1/@@loginForm.html?camefrom=%2F'
+
+        The plugin redirects to the page defined by the loginpagename
+        attribute:
+
+          >>> plugin.loginpagename = 'mylogin.html'
+          >>> plugin.challenge(request)
+          True
+          >>> request.response.getHeader('location')
+          'http://127.0.0.1/@@mylogin.html?camefrom=%2F'
+
+        It also provides the request URL as a 'camefrom' GET style parameter.
+        To illustrate, we'll pretend we've traversed a couple names:
+
+          >>> env = {
+          ...     'REQUEST_URI': '/foo/bar/folder/page%201.html?q=value',
+          ...     'QUERY_STRING': 'q=value'
+          ...     }
+          >>> request = TestRequest(environ=env)
+          >>> request._traversed_names = [u'foo', u'bar']
+          >>> request._traversal_stack = [u'page 1.html', u'folder']
+          >>> request['REQUEST_URI']
+          '/foo/bar/folder/page%201.html?q=value'
+
+        When we challenge:
+
+          >>> plugin.challenge(request)
+          True
+
+        We see the 'camefrom' points to the requested URL:
+
+          >>> request.response.getHeader('location') # doctest: +ELLIPSIS
+          '.../@@mylogin.html?camefrom=%2Ffoo%2Fbar%2Ffolder%2Fpage+1.html%3Fq%3Dvalue'
+
+        This can be used by the login form to redirect the user back to the
+        originating URL upon successful authentication.
+        """
+        if not IHTTPRequest.providedBy(request):
+            return False
+
+        site = hooks.getSite()
+        # We need the traversal stack to complete the 'camefrom' parameter
+        stack = request.getTraversalStack()
+        stack.reverse()
+        # Better to add the query string, if present
+        query = request.get('QUERY_STRING')
+
+        camefrom = '/'.join([request.getURL(path_only=True)] + stack)
+        if query:
+            camefrom = camefrom + '?' + query
+        url = '%s/@@%s?%s' % (absoluteURL(site, request),
+                              self.loginpagename,
+                              urlencode({'camefrom': camefrom}))
+        request.response.redirect(url)
+        return True
+
+    def logout(self, request):
+        """Performs logout by clearing session data credentials."""
+        if not IHTTPRequest.providedBy(request):
+            return False
+
+        sessionData = ISession(request)['z3c.authenticator.credential.session']
+        sessionData['credentials'] = None
+        transaction.commit()
+        return True


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/credential.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/credential.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/credential.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/credential.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,27 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zope">
+
+  <class class=".credential.HTTPBasicAuthCredentialsPlugin">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IHTTPBasicAuthCredentialsPlugin"
+        set_schema=".interfaces.IHTTPBasicAuthCredentialsPlugin"
+        />
+  </class>
+
+  <class class=".credential.SessionCredentialsPlugin">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IBrowserFormChallenger"
+        set_schema=".interfaces.IBrowserFormChallenger"
+        />
+  </class>
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/credential.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/event.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/event.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/event.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+
+from z3c.authenticator import interfaces
+
+
+# principal events
+class AuthenticatedPrincipalCreated(object):
+    """
+    >>> from zope.interface.verify import verifyObject
+    >>> event = AuthenticatedPrincipalCreated('authentication', 'principal',
+    ...     'request')
+    >>> verifyObject(interfaces.IAuthenticatedPrincipalCreated, event)
+    True
+    """
+
+    zope.interface.implements(interfaces.IAuthenticatedPrincipalCreated)
+
+    def __init__(self, authentication, principal, request):
+        self.authentication = authentication
+        self.principal = principal
+        self.request = request
+
+
+class FoundPrincipalCreated(object):
+    """
+    >>> from zope.interface.verify import verifyObject
+    >>> event = FoundPrincipalCreated('authentication', 'principal')
+    >>> verifyObject(interfaces.IFoundPrincipalCreated, event)
+    True
+    """
+
+    zope.interface.implements(interfaces.IFoundPrincipalCreated)
+
+    def __init__(self, authentication, principal):
+        self.authentication = authentication
+        self.principal = principal
+
+
+# group events
+class GroupAdded(object):
+    """Group added event
+
+    >>> from zope.interface.verify import verifyObject
+    >>> event = GroupAdded(u'group')
+    >>> verifyObject(IGroupAdded, event)
+    True
+    """
+
+    zope.interface.implements(interfaces.IGroupAdded)
+
+    def __init__(self, group):
+        self.group = group
+
+    def __repr__(self):
+        return "<GroupAdded %r>" % self.group.__name__
+
+
+class AbstractUsersChanged(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(AbstractUsersChanged):
+    zope.interface.implements(interfaces.IPrincipalsAddedToGroup)
+
+
+class PrincipalsRemovedFromGroup(AbstractUsersChanged):
+    zope.interface.implements(interfaces.IPrincipalsRemovedFromGroup)


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/event.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/group.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/group.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/group.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,313 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import BTrees.OOBTree
+import persistent
+
+import zope.interface
+import zope.component
+import zope.event
+from zope.interface import alsoProvides
+from zope.security.interfaces import IGroup
+from zope.security.interfaces import IGroupAwarePrincipal
+from zope.security.interfaces import IMemberAwareGroup
+
+from zope.app.container import btree
+from zope.app.container import contained
+from zope.app.security.interfaces import IAuthentication
+from zope.app.security.interfaces import IAuthenticatedGroup
+from zope.app.security.interfaces import IEveryoneGroup
+from zope.app import zapi
+
+from z3c.authenticator import interfaces
+from z3c.authenticator import event
+
+
+class Group(persistent.Persistent, contained.Contained):
+    """An implementation of IGroup used by the group container."""
+
+    zope.interface.implements(interfaces.IGroup)
+
+    _principals = ()
+
+    def __init__(self, title=u'', description=u''):
+        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)
+
+        oldset = set(old)
+        new = set(prinlist)
+        gid = self.__name__
+        removed = oldset - new
+        added = new - oldset
+        try:
+            parent._removePrincipalsFromGroup(removed, gid)
+        except AttributeError:
+            removed = None
+
+        try:
+            parent._addPrincipalsToGroup(added, gid)
+        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:
+            zope.event.notify(
+                event.PrincipalsRemovedFromGroup(removed, gid))
+        if added:
+            zope.event.notify(
+                event.PrincipalsAddedToGroup(added, gid))
+
+    principals = property(lambda self: self._principals, setPrincipals)
+
+    def __repr__(self):
+        return "<%s %s>" %(self.__class__.__name__, self.__name__)
+
+
+class GroupContainer(btree.BTreeContainer):
+
+    zope.interface.implements(interfaces.IGroupContainer,
+        interfaces.IQuerySchemaSearch,)
+
+    schema = interfaces.IGroupSearchCriteria
+
+    def __init__(self, prefix=u''):
+        self.prefix = prefix
+        super(GroupContainer,self).__init__()
+        # __inversemapping is used to map principals to groups
+        self.__inverseMapping = BTrees.OOBTree.OOBTree()
+
+    def __setitem__(self, name, group):
+        """Add a IGroup object within a correct id.
+
+        Create a GroupContainer
+
+        >>> gc = GroupContainer('groups')
+
+        Try to add something not providing IGroup
+        >>> try:
+        ...     gc.__setitem__(u'groups.members', object())
+        ... except Exception, e:
+        ...     print e
+        Group does not support IGroup!
+
+        Create a group and add them with a wrong prefix:
+
+        >>> group = Group(u'users')
+        >>> try:
+        ...     gc.__setitem__(u'123', group)
+        ... except Exception, e:
+        ...     print e
+        'Wrong prefix used in group id!'
+
+        Add a login attr since __setitem__ is in need of one
+
+        >>> gc.__setitem__(u'groups.users', group)
+        """
+        # check if we store correct groups
+        if not interfaces.IGroup.providedBy(group):
+            raise TypeError('Group does not support IGroup!')
+
+        # check if the given id provides the used prefix
+        if not name.startswith(self.prefix):
+            raise KeyError('Wrong prefix used in group id!')
+
+        super(GroupContainer, self).__setitem__(name, group)
+        gid = group.__name__
+        self._addPrincipalsToGroup(group.principals, gid)
+        if group.principals:
+            zope.event.notify(
+                event.PrincipalsAddedToGroup(group.principals, gid))
+        zope.event.notify(event.GroupAdded(group))
+
+    def addGroup(self, id, group):
+        id = self.prefix + id
+        self[id] = group
+        return id, self[id]
+
+    def __delitem__(self, gid):
+        group = self[gid]
+        self._removePrincipalsFromGroup(group.principals, gid)
+        if group.principals:
+            zope.event.notify(
+                event.PrincipalsRemovedFromGroup(group.principals, gid))
+        super(GroupContainer, self).__delitem__(gid)
+
+    def _addPrincipalsToGroup(self, pids, gid):
+        for pid in pids:
+            self.__inverseMapping[pid] = (
+                self.__inverseMapping.get(pid, ()) + (gid,))
+
+    def _removePrincipalsFromGroup(self, pids, gid):
+        for pid in pids:
+            groups = self.__inverseMapping.get(pid)
+            if groups is None:
+                return
+            new = tuple([id for id in groups if id != gid])
+            if new:
+                self.__inverseMapping[pid] = new
+            else:
+                del self.__inverseMapping[pid]
+
+    def getGroupsForPrincipal(self, pid):
+        """Get groups the given principal belongs to"""
+        return self.__inverseMapping.get(pid, ())
+
+    def getPrincipalsForGroup(self, gid):
+        """Get principals which belong to the group"""
+        return self[gid].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 id
+
+    def authenticateCredentials(self, credentials):
+        # group container don't authenticate
+        pass
+
+    def queryPrincipal(self, id, default=None):
+        return self.get(id, default)
+
+
+class GroupPrincipal(object):
+
+    zope.interface.implements(interfaces.IGroupPrincipal)
+    zope.component.adapts(interfaces.IGroup)
+
+    def __init__(self, group):
+        self.id = group.__name__
+        self._group = group
+        self.groups = []
+
+    @property
+    def allGroups(self):
+        if self.groups:
+            seen = set()
+            auth = zope.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 = auth.getPrincipal(group_id)
+                        stack.append(iter(group.groups))
+
+    @property
+    def title(self):
+        return self._group.title
+
+    @property
+    def description(self):
+        return self._group.description
+
+    @apply
+    def members():
+        def get(self):
+            return self._group.principals
+        def set(self, value):
+            self._group.principals = value
+        return property(get, set)
+
+    def getUsers(self):
+        return self._group.principals
+
+    def setUsers(self, value):
+        self._group.principals = value
+
+    def __repr__(self):
+        return "<%s %s>" %(self.__class__.__name__, self.id)
+
+
+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(pids, seen, getPrincipal):
+    for pid in pids:
+        if pid in seen:
+            raise GroupCycle(pid, seen)
+        seen.append(pid)
+        principal = getPrincipal(pid)
+        nocycles(principal.groups, seen, getPrincipal)
+        seen.pop()
+
+
+def specialGroups(event):
+    principal = event.principal
+    if (IGroup.providedBy(principal) or
+        not IGroupAwarePrincipal.providedBy(principal)):
+        return
+
+    everyone = zope.component.queryUtility(IEveryoneGroup)
+    if everyone is not None:
+        principal.groups.append(everyone.id)
+
+    auth = zope.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 interfaces.IGroupContainer.providedBy(plugin):
+            continue
+        principal.groups.extend(
+            [id for id in plugin.getGroupsForPrincipal(principal.id)])


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/group.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/group.txt
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/group.txt	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/group.txt	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,581 @@
+=====
+Group
+=====
+
+Group container provide support for groups information stored in the ZODB. 
+They are persistent, and must be contained within the IAuthentication that 
+use them.
+
+
+Group
+-----
+
+Like other users, groups are created when they are needed.
+
+  >>> from z3c.authenticator import interfaces
+  >>> from z3c.authenticator.group import Group
+  >>> group1 = Group(u'groups')
+  >>> interfaces.IGroup.providedBy(group1)
+  True
+
+  >>> group1.title
+  u'groups'
+
+  >>> group1.description
+  u''
+
+  >>> group1.principals
+  ()
+
+
+GroupContainer
+--------------
+
+Group containers contain IGroup objects. A IAuthentication will adapt
+IFoundGroup to this IGroup objects.
+
+  >>> from z3c.authenticator.group import GroupContainer
+  >>> groups = GroupContainer('groups.')
+
+  >>> interfaces.IGroupContainer.providedBy(groups)
+  True
+
+We can add your previous created group to the group container using the 
+addGroup method which returns the group id and group:
+
+  >>> gid, g1 = groups.addGroup(u'g1', group1)
+  >>> gid
+  u'groups.g1'
+
+  >>> interfaces.IGroup.providedBy(g1)
+  True
+
+  >>> g1.__name__
+  u'groups.g1'
+
+Note that when group is added, a GroupAdded event is generated:
+
+  >>> from zope.component.eventtesting import getEvents
+  >>> getEvents(interfaces.IGroupAdded)
+  [<GroupAdded u'groups.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 will setup a Authenticator utility:
+
+  >>> from z3c.authenticator.authentication import Authenticator
+  >>> sau = Authenticator()
+
+Give them a location and register them as a IAuthentication utility :
+
+  >>> import zope.component
+  >>> from zope.app.security.interfaces import IAuthentication
+  >>> rootFolder['sau'] = sau
+  >>> zope.component.provideUtility(sau, IAuthentication)
+
+We will create and register a new principals utility:
+
+  >>> zope.component.provideUtility(sau, IAuthentication)
+
+We also need to register the group athentication plugin:
+
+  >>> zope.component.provideUtility(groups, 
+  ...     provides=interfaces.IAuthenticatorPlugin, 
+  ...     name='My Group Plugin')
+
+After setup the group and group container, we will create a simple credentials 
+plugin and add them to the authentication utility:
+
+  >>> import zope.interface
+  >>> from z3c.authenticator import interfaces
+
+  >>> class MyCredentialsPlugin(object):
+  ...
+  ...     zope.interface.implements(interfaces.ICredentialsPlugin)
+  ...
+  ...     def extractCredentials(self, request):
+  ...         return {'login':request.get('login', ''), 
+  ...                 'password':request.get('password', '')}
+  ...
+  ...     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
+
+and configure and add the credential plugin to the Authenticator:
+
+  >>> myCredentialsPlugin = MyCredentialsPlugin()
+  >>> sau['credentials'] = myCredentialsPlugin
+  >>> sau.credentialsPlugins = ('credentials', )
+
+We also need a principal and a IAuthenticationPlugin:
+
+  >>> from z3c.authenticator.user import User
+  >>> p1 = User(u'p1', u'password', u'Principal 1')
+  >>> p2 = User(u'p2', u'password', u'Principal 2')
+  >>> p3 = User(u'p3', u'password', u'Principal 3')
+  >>> p4 = User(u'p4', u'password', u'Principal 4')
+
+  >>> from z3c.authenticator.user import UserContainer
+  >>> users = UserContainer()
+  >>> token1, p1 = users.add(p1)
+  >>> token2, p2 = users.add(p2)
+  >>> token3, p3 = users.add(p3)
+  >>> token4, p4 = users.add(p4)
+
+Add the GroupContainer and UserContainer to the Authenticator and 
+set the correct plugin names
+
+  >>> sau['users'] = users
+  >>> sau['groups'] = groups
+  >>> sau.authenticatorPlugins = ('users', 'groups')
+
+
+Adding users to groups
+----------------------
+
+Now we can set the users on the group but first we need to register the
+IFoundPrincipal adapter for groups. The GroupPrincipal adapter provides this
+interface:
+
+  >>> from z3c.authenticator.group import GroupPrincipal
+  >>> zope.component.provideAdapter(GroupPrincipal, 
+  ...     provides=interfaces.IFoundPrincipal)
+
+And we also need to provide the IFoundPrincipal and IAuthenticatedPrincipal
+adapter for IPrincipal objects:
+
+  >>> from z3c.authenticator.principal import AuthenticatedPrincipal
+  >>> from z3c.authenticator.principal import FoundPrincipal
+  >>> zope.component.provideAdapter(AuthenticatedPrincipal, 
+  ...     provides=interfaces.IAuthenticatedPrincipal)
+
+  >>> zope.component.provideAdapter(FoundPrincipal, 
+  ...     provides=interfaces.IFoundPrincipal)
+
+And we need the ``setGroupsForPrincipal`` subscriber:
+
+  >>> from z3c.authenticator.group import setGroupsForPrincipal
+  >>> zope.component.provideHandler(setGroupsForPrincipal, 
+  ...     [interfaces.IPrincipalCreated])
+
+  >>> g1.principals = [p1.__name__, p2.__name__]
+  >>> g1.principals
+  (..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p2.__name__
+  True
+
+Adding users fires an event.
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup [..., ...] u'groups.g1'>
+
+We can now look up groups for the users:
+
+  >>> groups.getGroupsForPrincipal(p1.__name__)
+  (u'groups.g1',)
+
+Note that the group id is a concatenation of the group-folder prefix
+and the name of the group object within the folder.
+
+If we delete a group:
+
+  >>> del groups['groups.g1']
+
+then the groups folder loses the group information for that group's users:
+
+  >>> groups.getGroupsForPrincipal('p1')
+  ()
+
+but the principal information on the group is unchanged:
+
+  >>> g1.principals
+  (..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p2.__name__
+  True
+
+It also fires an event showing that the users are removed from the groups.
+
+  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+  <PrincipalsRemovedFromGroup [..., ...] 'groups.g1'>
+
+Adding the group again within a different name will make the groups 
+available for the principal. Let's use a different group name:
+
+  >>> groups['groups.G1'] = g1
+
+  >>> groups.getGroupsForPrincipal(p1.__name__)
+  (u'groups.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 [..., ...] u'groups.G1'>
+
+In terms of member events (members added and removed from groups), we have
+now seen that events are fired when a group object is added and when it is 
+removed from a group container; and we have seen that events are fired
+when a principal is added to an already-registered groups.  Events are also
+fired when a principal is removed from an already-registered groups.  Let's
+quickly see some more examples.
+
+  >>> g1.principals = (p1.__name__, p3.__name__, p4.__name__)
+  >>> g1.principals
+  (..., ..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p3.__name__
+  True
+
+  >>> g1.principals[2] == p4.__name__
+  True
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup [..., ...] u'groups.G1'>
+
+  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+  <PrincipalsRemovedFromGroup [...] u'groups.G1'>
+
+  >>> g1.principals = (p1.__name__, p2.__name__)
+  >>> g1.principals
+  (..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p2.__name__
+  True
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup [...] u'groups.G1'>
+
+  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+  <PrincipalsRemovedFromGroup [..., ...] u'groups.G1'>
+
+  >>> groups.getGroupsForPrincipal(p2.__name__)
+  (u'groups.G1',)
+
+
+Groups in groups
+----------------
+
+Groups can contain groups:
+
+  >>> g2 = Group('Group Two')
+  >>> groups['groups.G2'] = g2
+  >>> g2.principals
+  ()
+  
+  >>> g2.principals = ['groups.G1']
+
+  >>> g2.principals
+  ('groups.G1',)
+
+  >>> groups.getGroupsForPrincipal('groups.G2')
+  ()
+
+  >>> g1.principals
+  (..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p2.__name__
+  True
+
+  >>> groups.getGroupsForPrincipal('groups.G1')
+  (u'groups.G2',)
+
+  >>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  >>> old
+  <PrincipalsAddedToGroup ['groups.G1'] u'groups.G2'>
+
+Groups cannot contain cycles:
+
+  >>> g1.principals
+  (..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p2.__name__
+  True
+
+  >>> g2.principals
+  ('groups.G1',)
+
+  >>> g1.principals = (p1.__name__, p2.__name__, 'groups.G2')
+  Traceback (most recent call last):
+  ...
+  GroupCycle: (...)
+
+  >>> g1.principals
+  (..., ...)
+
+  >>> g1.principals[0] == p1.__name__
+  True
+
+  >>> g1.principals[1] == p2.__name__
+  True
+
+Trying to do so does not fire an event.
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old
+  True
+
+They need not be hierarchical:
+
+  >>> ga = Group("Group A")
+  >>> groups['groups.GA'] = ga
+
+  >>> gb = Group("Group B")
+  >>> groups['groups.GB'] = gb
+  >>> gb.principals = ['groups.GA']
+
+  >>> gc = Group("Group C")
+  >>> groups['groups.GC'] = gc
+  >>> gc.principals = ['groups.GA']
+
+  >>> gd = Group("Group D")
+  >>> groups['groups.GD'] = gd
+  >>> gd.principals = ['groups.GA', 'groups.GB']
+
+  >>> ga.principals = [p1.__name__]
+
+Group containers provide a very simple search interface.  They perform
+simple string searches on group titles and descriptions.
+
+  >>> list(groups.search({'search': 'grou'}))
+  [u'groups.G1', u'groups.G2',
+   u'groups.GA', u'groups.GB', u'groups.GC', u'groups.GD']
+
+  >>> list(groups.search({'search': 'two'}))
+  [u'groups.G2']
+
+They also support batching:
+
+  >>> list(groups.search({'search': 'grou'}, 2, 3))
+  [u'groups.GA', u'groups.GB', u'groups.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:
+
+  >>> auth1 = sau.getPrincipal(p1.__name__)
+
+  >>> auth1.groups
+  [u'groups.G1', u'groups.GA']
+
+Of course, this applies to groups too:
+
+  >>> g1 = sau.getPrincipal('groups.G1')
+  >>> g1.id
+  u'groups.G1'
+
+  >>> g1.groups
+  [u'groups.G2']
+
+A GroupPrincipal provides IGroupPrincipal which is inherited from 
+IFoundPrincipal and IGroup:
+
+  >>> interfaces.IGroupPrincipal.providedBy(g1)
+  True
+
+  >>> interfaces.IFoundPrincipal.providedBy(g1)
+  True
+
+  >>> import zope.security.interfaces
+  >>> zope.security.interfaces.IGroup.providedBy(g1)
+  True
+
+
+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
+users if IAuthenticatedGroup, or IEveryoneGroup utilities are
+provided.
+
+If we notify the subscriber with the principal, nothing will happen
+because the groups haven't been defined:
+
+  >>> from z3c.authenticator.principal import FoundPrincipal
+  >>> from z3c.authenticator.event import FoundPrincipalCreated
+  >>> from z3c.authenticator.group import specialGroups
+  >>> x = User('x', 'password', 'X')
+  >>> found = FoundPrincipal(x)
+  >>> event = FoundPrincipalCreated(sau, found)
+  >>> specialGroups(event)
+  >>> found.groups
+  []
+
+Now, if we define the Everybody group:
+
+  >>> import zope.app.security.interfaces
+  >>> class EverybodyGroup(GroupPrincipal):
+  ...     zope.interface.implements(
+  ...        zope.app.security.interfaces.IEveryoneGroup)
+
+  >>> all = Group('groups.all')
+  >>> groups['groups.all'] = all
+  >>> everybody = EverybodyGroup(all)
+  >>> zope.component.provideUtility(everybody,
+  ...     zope.app.security.interfaces.IEveryoneGroup)
+
+Then the group will be added to the principal:
+
+  >>> specialGroups(event)
+  >>> found.groups
+  [u'groups.all']
+
+Similarly for the authenticated group:
+
+  >>> class AuthenticatedGroup(GroupPrincipal):
+  ...     zope.interface.implements(
+  ...         zope.app.security.interfaces.IAuthenticatedGroup)
+
+  >>> authenticated = Group('groups.authenticated')
+  >>> groups['groups.authenticated'] = authenticated
+  >>> authenticated = AuthenticatedGroup(authenticated)
+  >>> zope.component.provideUtility(authenticated,
+  ...     zope.app.security.interfaces.IAuthenticatedGroup)
+
+Then the group will be added to the principal:
+
+  >>> found.groups = []
+  >>> specialGroups(event)
+  >>> found.groups.sort()
+  >>> found.groups
+  [u'groups.all', u'groups.authenticated']
+
+These groups are only added to non-group principals:
+
+  >>> found.groups = []
+  >>> zope.interface.directlyProvides(found, zope.security.interfaces.IGroup)
+  >>> specialGroups(event)
+  >>> found.groups
+  []
+
+And they are only added to group aware principals:
+
+  >>> class SolitaryPrincipal:
+  ...     zope.interface.implements(zope.security.interfaces.IPrincipal)
+  ...     id = title = description = ''
+
+  >>> event = FoundPrincipalCreated(sau, SolitaryPrincipal())
+  >>> specialGroups(event)
+  >>> found.groups
+  []
+
+
+UserAwareGroup
+----------------
+
+We can manages groups via the group getUser and setUser methods:
+
+  >>> foundGroup = interfaces.IFoundPrincipal(g1)
+  >>> foundGroup.getUsers()
+  (..., ...)
+
+  >>> foundGroup.getUsers()[0] == p1.__name__
+  True
+
+  >>> foundGroup.getUsers()[1] == p2.__name__
+  True
+
+Let's remove a member from the group:
+
+  >>> foundGroup.setUsers((p2.__name__,))
+  >>> foundGroup.getUsers()
+  (...,)
+
+  >>> foundGroup.getUsers()[0] == p2.__name__
+  True
+
+  >>> foundGroup.members
+  (...,)
+
+  >>> foundGroup.getUsers()[0] == p2.__name__
+  True
+
+Ensure that such a group provides the IMemberAwareGroup interfaces.
+
+  >>> zope.security.interfaces.IMemberAwareGroup.providedBy(foundGroup)
+  True
+
+#User-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 groups...
+#
+#    >>> 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.setUserSubscriber(
+#    ...     interfaces.FoundPrincipalCreated(
+#    ...         'dummy auth (ignored)', principal, info))
+#    >>> principal.getUsers()
+#    ('joe', 'jane')
+#    >>> principal.setUsers(('joe', 'jane', 'jaimie'))
+#    >>> principal.getUsers()
+#    ('joe', 'jane', 'jaimie')
+#    >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal)
+#    True
+#
+#The two methods work with the value on the IGroupInformation object.
+#
+#    >>> i.principals == principal.getUsers()
+#    True


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/group.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/group.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/group.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/group.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,50 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <class class=".group.Group">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IGroup"
+        />
+    <require
+        permission="zope.ManageServices"
+        set_schema=".interfaces.IGroup"
+        />
+  </class>
+
+  <class class=".group.GroupContainer">
+    <implements
+        interface=".interfaces.IGroupContainer"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IGroupContainer
+                   zope.app.container.interfaces.INameChooser"
+        />
+  </class>
+
+  <adapter
+      factory=".group.GroupPrincipal"
+      />
+
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for=".interfaces.IGroupContainer"
+      factory="zope.app.authentication.idpicker.IdPicker"
+      />
+
+  <subscriber
+      for=".interfaces.IPrincipalCreated"
+      handler=".group.specialGroups"
+      />
+
+  <subscriber
+      for=".interfaces.IPrincipalCreated"
+      handler=".group.setGroupsForPrincipal"
+      />
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/group.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,473 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+
+from zope.security.interfaces import IGroupClosureAwarePrincipal
+from zope.security.interfaces import IMemberAwareGroup
+from zope.app.authentication.interfaces import ICredentialsPlugin
+from zope.app.container.interfaces import IContainer
+from zope.app.container.constraints import contains
+from zope.app.container.constraints import containers
+from zope.app.security.interfaces import ILogout
+from zope.app.security.vocabulary import PrincipalSource
+
+from z3c.i18n import MessageFactory as _
+
+
+class IPlugin(zope.interface.Interface):
+    """A plugin for IAuthenticator component."""
+
+
+# authenticator interfaces
+class IAuthenticatorPlugin(IPlugin):
+    """Authenticates and provides a principal using credentials."""
+
+    containers('z3c.authenticator.interfaces.IAuthenticator')
+
+    def authenticateCredentials(credentials):
+        """Authenticates credentials and return a IPrincipal object.
+
+        If the credentials can be authenticated, return an object that provides
+        IPrincipal. If the plugin cannot authenticate the credentials,
+        returns None.
+        """
+
+    def queryPrincipal(id, default=None):
+        """Returns an IPrincipal object for the given principal id or default.
+
+        If the plugin cannot find information for the id, returns None.
+        """
+
+
+class ICredentialsPlugin(IPlugin):
+    """Handles credentials extraction and challenges per request."""
+
+    containers('z3c.authenticator.interfaces.IAuthenticator')
+
+    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 IAuthenticator(ILogout, IContainer):
+    """Authentication utility.
+    
+    The Authenticator supports NOT IAuthenticatorPlugin plugins defined 
+    in zope.app.authentication.interfaces. Because they use and return a 
+    IPrincipalInfo object in the authenticateCredentials method.
+    
+    Note: you have to write your own authenticator plugins because we do not 
+    use the IPrincipalInfo implementation in this authentication module.
+    """
+
+    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 IAuthenticator, or names of registered
+        ICredentialsPlugins utilities.  Contained non-utility ids mask 
+        utility names."""),
+        value_type=zope.schema.Choice(vocabulary='Z3CCredentialsPlugins'),
+        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 IAuthenticator, or names of registered
+        IAuthenticatorPlugins utilities.  Contained non-utility ids mask 
+        utility names."""),
+        value_type=zope.schema.Choice(vocabulary='Z3CAuthenticatorPlugins'),
+        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.
+        """
+
+    def logout(request):
+        """Performs a logout by delegating to its authenticator plugins."""
+
+
+
+# user interfaces
+class IUser(zope.interface.Interface):
+    """User"""
+
+    containers('z3c.authenticator.interfaces.IUserContainer')
+
+    login = zope.schema.TextLine(
+        title=_("Login"),
+        description=_("The Login/Username of the principal. "
+                      "This value can change."))
+
+    password = zope.schema.Password(
+        title=_("Password"),
+        description=_("The password for the principal."))
+
+    passwordManagerName = zope.schema.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 = zope.schema.TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the principal."))
+
+    description = zope.schema.Text(
+        title=_("Description"),
+        description=_("Provides a description for the principal."),
+        required=False,
+        missing_value='',
+        default=u'')
+
+
+class IUserSearchCriteria(zope.interface.Interface):
+    """Search Interface for this Principal Provider"""
+
+    search = zope.schema.TextLine(
+        title=_("Search String"),
+        description=_("A Search String"),
+        required=False,
+        default=u'',
+        missing_value=u'')
+
+
+class IUserContainer(IContainer, IAuthenticatorPlugin):
+    """Principal container."""
+
+    contains(IUser)
+
+
+# principal interfaces
+class IFoundPrincipal(IGroupClosureAwarePrincipal):
+    """A factory adapting IUser and offering read access to the principal.
+    
+    A found principal gets created by the IAuthenticators search method
+    for users matching the search critaria.
+    """
+
+    id = zope.interface.Attribute("The principal id.")
+
+    title = zope.interface.Attribute("The principal title.")
+
+    description = zope.interface.Attribute("A description of the principal.")
+
+    groups = zope.schema.List(
+        title=_("Groups"),
+        description=_(
+            """Ids of groups to which the user 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 container in this package.)
+            """),
+        value_type=zope.schema.TextLine(),
+        required=False)
+
+
+class IAuthenticatedPrincipal(IGroupClosureAwarePrincipal):
+    """A factory adapting IInternalPrincipal and offering read access to the 
+    principal.
+    
+    A authenticated principal gets created by the IAuthenticators 
+    authenticateCredentials method for principals matching the credential 
+    criteria.
+    """
+
+    id = zope.interface.Attribute("The principal id.")
+
+    title = zope.interface.Attribute("The principal title.")
+
+    description = zope.interface.Attribute("A description of the principal.")
+
+    groups = zope.schema.List(
+        title=_("Groups"),
+        description=_(
+            """Ids of groups to which the user 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 container in this package.)
+            """),
+        value_type=zope.schema.TextLine(),
+        required=False)
+
+
+# group interfaces
+class IGroup(zope.interface.Interface):
+
+    containers('z3c.authenticato.interfaces.IGroupContainer')
+
+    title = zope.schema.TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the permission."),
+        required=True)
+
+    description = zope.schema.Text(
+        title=_("Description"),
+        description=_("Provides a description for the permission."),
+        required=False)
+
+    principals = zope.schema.List(
+        title=_("Principals"),
+        value_type=zope.schema.Choice(
+            source=PrincipalSource()),
+        description=_(
+        "List of ids of principals which belong to the group"),
+        required=False)
+
+
+class IGroupContainer(IContainer, IAuthenticatorPlugin):
+
+    contains(IGroup)
+
+    prefix = zope.schema.TextLine(
+        title=_('Prefix'),
+        description=_("Prefix added to IDs of groups in this container"),
+        default=u'',
+        required=True,
+        readonly=True,
+        )
+
+    def getGroupsForPrincipal(principalid):
+        """Get groups the given principal belongs to"""
+
+    def getPrincipalsForGroup(groupid):
+        """Get principals which belong to the group"""
+
+
+class IGroupSearchCriteria(zope.interface.Interface):
+
+    search = zope.schema.TextLine(
+        title=_("Group Search String"),
+        required=False,
+        missing_value=u'',
+        )
+
+
+class IGroupPrincipal(IFoundPrincipal, IMemberAwareGroup):
+    """IGroup that acts as a principal representing a group."""
+
+    members = zope.interface.Attribute('an iterable of members of the group')
+
+
+# principal event interfaces
+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")
+
+
+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 IFoundPrincipalCreated(IPrincipalCreated):
+    """A principal has been created by way of a search operation."""
+
+
+# group event interfaces
+class IGroupAdded(zope.interface.Interface):
+    """A group has been added."""
+
+    group = zope.interface.Attribute("""The group that was defined""")
+
+
+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')
+
+
+# credential
+class IHTTPBasicAuthRealm(zope.interface.Interface):
+    """HTTP Basic Auth Realm
+
+    Represents the realm string that is used during basic HTTP authentication
+    """
+
+    realm = zope.schema.TextLine(
+        title=u'Realm',
+        description=u'HTTP Basic Authentication Realm',
+        required=True,
+        default=u'ZAM')
+
+
+class ISessionCredentials(zope.interface.Interface):
+    """ Interface for storing and accessing credentials in a session.
+
+        We use a real class with interface here to prevent unauthorized
+        access to the credentials.
+    """
+
+    def __init__(login, password):
+        pass
+
+    def getLogin():
+        """Return login name."""
+
+    def getPassword():
+        """Return password."""
+
+
+class IBrowserFormChallenger(zope.interface.Interface):
+    """A challenger that uses a browser form to collect user credentials."""
+
+    loginpagename = zope.schema.TextLine(
+        title=u'Loginpagename',
+        description=u"""Name of the login form used by challenger.
+
+        The form must provide 'login' and 'password' input fields.
+        """,
+        default=u'loginForm.html')
+
+    loginfield = zope.schema.TextLine(
+        title=u'Loginfield',
+        description=u"Field of the login page in which is looked for the login user name.",
+        default=u"login")
+
+    passwordfield = zope.schema.TextLine(
+        title=u'Passwordfield',
+        description=u"Field of the login page in which is looked for the password.",
+        default=u"password")
+
+
+class IHTTPBasicAuthCredentialsPlugin(ICredentialsPlugin, IHTTPBasicAuthRealm):
+    """BAsic authentication credential plugin."""
+
+
+class ISessionCredentialsPlugin(ICredentialsPlugin, IBrowserFormChallenger):
+    """Session credential plugin."""
+
+
+class ILoginSchema(zope.interface.Interface):
+    """The subscription form."""
+
+    login = zope.schema.TextLine(
+        title=_(u'Login'),
+        description=_(u'Your login name.'),
+        required=True)
+
+    password = zope.schema.Password(
+        title=_(u'Password'),
+        description=_(u'Your password.'))
+
+    camefrom = zope.schema.TextLine(
+        title=_(u'Camefrom'),
+        description=_(u'The url which the user came form.'))
+
+
+# queriable search interfaces
+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.
+        """
+


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/principal.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/principal.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/principal.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+from zope.app.security.interfaces import IAuthentication
+
+from z3c.authenticator import interfaces
+
+
+class PrincipalBase(object):
+    """Base class for IAuthenticatedPrincipal and IFoundPrincipal principals.
+    """
+
+    title = None
+
+    def __init__(self, principal):
+        """We offer no access to the principal object itself."""
+        self.id = principal.__name__
+        self.title = principal.title
+        self.description = principal.description
+        self.groups = []
+
+    @property
+    def allGroups(self):
+        if self.groups:
+            seen = set()
+            auth = zope.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 = auth.getPrincipal(group_id)
+                        stack.append(iter(group.groups))
+
+    def __repr__(self):
+        return "<%s %s>" %(self.__class__.__name__, self.id)
+
+
+class AuthenticatedPrincipal(PrincipalBase):
+    """Default IAuthenticatedPrincipal principal."""
+
+    zope.component.adapts(interfaces.IUser)
+
+    zope.interface.implements(interfaces.IAuthenticatedPrincipal)
+
+
+class FoundPrincipal(PrincipalBase):
+    """Default IFoundPrincipal principal."""
+
+    zope.component.adapts(interfaces.IUser)
+
+    zope.interface.implements(interfaces.IFoundPrincipal)


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/principal.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/principal.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/principal.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/principal.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,9 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <adapter factory=".principal.FoundPrincipal" />
+
+  <adapter factory=".principal.AuthenticatedPrincipal" />
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/principal.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/testing.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/testing.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/testing.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,47 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.component
+from zope.app.authentication.interfaces import IPasswordManager
+from zope.app.authentication.password import PlainTextPasswordManager
+from zope.app.testing import setup
+
+###############################################################################
+#
+# setup helper
+#
+###############################################################################
+
+def setUpPasswordManager():
+    zope.component.provideUtility(PlainTextPasswordManager(), 
+        IPasswordManager, "Plain Text")
+
+
+###############################################################################
+#
+# placeful setup/teardown
+#
+###############################################################################
+
+def siteSetUp(test):
+    site = setup.placefulSetUp(site=True)
+    test.globs['rootFolder'] = site
+
+
+def siteTearDown(test):
+    setup.placefulTearDown()


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/testing.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/tests.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/tests.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/tests.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,174 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+from zope.testing import doctest
+from zope.app.testing import placelesssetup
+
+from z3c.testing import InterfaceBaseTest
+from z3c.testing import BaseTestIContainer
+from z3c.authenticator import interfaces
+from z3c.authenticator import authentication
+from z3c.authenticator import credential
+from z3c.authenticator import group
+from z3c.authenticator import user
+from z3c.authenticator import principal
+from z3c.authenticator import testing
+
+
+class AuthenticatorTest(BaseTestIContainer):
+
+    def getTestInterface(self):
+        return interfaces.IAuthenticator
+
+    def getTestClass(self):
+        return authentication.Authenticator
+
+
+class UserContainerTest(InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IUserContainer
+
+    def getTestClass(self):
+        return user.UserContainer
+
+
+class UserTest(InterfaceBaseTest):
+
+    def setUp(self):
+        testing.setUpPasswordManager()
+
+    def getTestInterface(self):
+        return interfaces.IUser
+
+    def getTestClass(self):
+        return user.User
+
+    def getTestPos(self):
+        return (u'login', u'password', u'Title')
+
+
+class AuthenticatedPrincipalTest(InterfaceBaseTest):
+
+    def setUp(self):
+        testing.setUpPasswordManager()
+
+    def getTestInterface(self):
+        return interfaces.IAuthenticatedPrincipal
+
+    def getTestClass(self):
+        return principal.AuthenticatedPrincipal
+
+    def makeTestObject(self):
+        usr =  user.User(u'login', u'password', u'Title')
+        return principal.AuthenticatedPrincipal(usr)
+
+
+class FoundPrincipalTest(InterfaceBaseTest):
+
+    def setUp(self):
+        testing.setUpPasswordManager()
+
+    def getTestInterface(self):
+        return interfaces.IFoundPrincipal
+
+    def getTestClass(self):
+        return principal.FoundPrincipal
+
+    def makeTestObject(self):
+        usr =  user.User(u'login', u'password', u'Title')
+        return principal.FoundPrincipal(usr)
+
+
+class GroupContainerTest(InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IGroupContainer
+
+    def getTestClass(self):
+        return group.GroupContainer
+
+
+class GroupTest(InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IGroup
+
+    def getTestClass(self):
+        return group.Group
+
+
+class SessionCredentialsTest(InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.ISessionCredentials
+
+    def getTestClass(self):
+        return credential.SessionCredentials
+
+    def getTestPos(self):
+        return (u'login', u'password')
+
+
+class SessionCredentialsPluginTest(InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.ICredentialsPlugin
+
+    def getTestClass(self):
+        return credential.SessionCredentialsPlugin
+
+
+class SessionCredentialsPluginFormTest(InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IBrowserFormChallenger
+
+    def getTestClass(self):
+        return credential.SessionCredentialsPlugin
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+            setUp=testing.siteSetUp, tearDown=testing.siteTearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+        doctest.DocFileSuite('group.txt',
+            setUp=testing.siteSetUp, tearDown=testing.siteTearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+        doctest.DocTestSuite('z3c.authenticator.credential',
+            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
+        doctest.DocTestSuite('z3c.authenticator.group',
+            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
+        doctest.DocFileSuite('vocabulary.txt',
+            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
+        unittest.makeSuite(AuthenticatorTest),
+        unittest.makeSuite(UserContainerTest),
+        unittest.makeSuite(UserTest),
+        unittest.makeSuite(AuthenticatedPrincipalTest),
+        unittest.makeSuite(FoundPrincipalTest),
+        unittest.makeSuite(GroupContainerTest),
+        unittest.makeSuite(GroupTest),
+        unittest.makeSuite(SessionCredentialsTest),
+        unittest.makeSuite(SessionCredentialsPluginTest),
+        unittest.makeSuite(SessionCredentialsPluginFormTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/user.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/user.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/user.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,255 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import md5
+import random
+import time
+import socket
+import persistent
+import zope.interface
+import zope.component
+from zope.app.container import contained
+from zope.app.container import btree
+from zope.app.container.interfaces import DuplicateIDError
+from zope.app.authentication.interfaces import IPasswordManager
+
+from z3c.authenticator import interfaces
+
+# get the IP addresss only once
+try: 
+  ip = socket.getaddrinfo(socket.gethostname(), 0)[-1][-1][0]
+except:
+  ip = '127.0.0.1'
+
+def generateUserIDToken(id):
+    """Generates a unique user id token."""
+    t = long(time.time() * 1000)
+    r = long(random.random()*100000000000000000L)
+    data = str(ip)+' '+str(t)+' '+str(r)+' '+str(id)
+    return unicode(md5.md5(data).hexdigest())
+
+
+class User(persistent.Persistent, contained.Contained):
+    """User stored in IUserContainer."""
+
+    zope.interface.implements(interfaces.IUser)
+
+    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 zope.component.getUtility(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 = self._getPasswordManager()
+        return passwordManager.checkPassword(self.password, 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 UserContainer(btree.BTreeContainer):
+    """A Persistent User Container and authenticator plugin.
+
+    See principalfolder.txt for details.
+    """
+
+    zope.interface.implements(interfaces.IUserContainer, 
+        interfaces.IQuerySchemaSearch)
+
+    schema = interfaces.IUserSearchCriteria
+
+    def __init__(self):
+        super(UserContainer, 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, user):
+        """Add a IPrincipal object within a correct id.
+
+        Create a UserContainer
+
+        >>> mc = UserContainer('users.')
+
+        Try to add something not providing IUser
+        >>> try:
+        ...     mc.__setitem__(u'max', object())
+        ... except Exception, e:
+        ...     print e
+        Item does not support IUser!
+
+        Create a user with no __name__, this should be added via the add
+        method
+
+        >>> user = User()
+        >>> try:
+        ...     mc.__setitem__(u'max', user)
+        ... except Exception, e:
+        ...     print e
+        There is no user id token given!
+
+        Probabl we do hav a __name__ during copy/paste, so we have to check 
+        if we get a __parent__ as well 
+
+        >>> user = User()
+        >>> user.__name__ = u'usertoken'
+        >>> try:
+        ...     mc.__setitem__(u'max', user)
+        ... except Exception, e:
+        ...     print e
+        Paste a object is not supported!
+
+        Try to use a user with no login:
+
+        >>> try:
+        ...     mc.__setitem__(u'max', user)
+        ... except Exception, e:
+        ...     print e
+        User does not provide a login value!
+
+        Add a login attr since __setitem__ is in need of one
+
+        >>> user.login = u'max'
+        >>> mc.__setitem__(u'users.max', principal)
+
+        Now try to use the same login:
+
+        >>> try:
+        ...     mc.__setitem__(u'max', user)
+        ... except Exception, e:
+        ...     print e
+        Login already taken!
+        """
+
+        # check if we store correct implementations
+        if not interfaces.IUser.providedBy(user):
+            raise TypeError('Item does not support IUser!')
+
+        # check if there is a user id given
+        if user.__name__ is not id:
+            raise ValueError('There is no user id token given!')
+
+        # check if there is a user id given in we store correct implementations
+        if user.__parent__ is not None:
+            raise ValueError('Paste a object is not supported!')
+
+        # The user doesn't provide a login
+        if not user.login:
+            raise ValueError('User does not provide a login value!')
+
+        # A user with the new login already exists
+        if user.login in self.__id_by_login:
+            raise DuplicateIDError('Login already taken!')
+
+        super(UserContainer, self).__setitem__(id, user)
+        self.__id_by_login[user.login] = id
+
+    def add(self, user):
+        token = generateUserIDToken(user.login)
+        # Pre set the user id like a ticket, so we can check it in __setitem__
+        user.__name__ = token
+        self[token] = user
+        return token, self[token]
+
+    def __delitem__(self, id):
+        """Remove principal information."""
+        user = self[id]
+        super(UserContainer, self).__delitem__(id)
+        del self.__id_by_login[user.login]
+
+    def authenticateCredentials(self, credentials):
+        """Return principal 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
+        user = self[id]
+        if not user.checkPassword(credentials["password"]):
+            return None
+        return user
+
+    def queryPrincipal(self, id, default=None):
+        user = self.get(id)
+        if user is not None:
+            return user
+        return default
+
+    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 value.__name__


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/user.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/user.zcml
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/user.zcml	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/user.zcml	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,23 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <class class=".user.User">
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IUser"
+        set_schema=".interfaces.IUser"
+        />
+  </class>
+
+  <class class=".user.UserContainer">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IUserContainer"
+        />
+  </class>
+
+</configure>


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/user.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.py	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.py	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.i18n
+import zope.dublincore.interfaces
+from zope.schema import vocabulary
+from zope.schema.interfaces import IVocabularyFactory
+
+from z3c.i18n import MessageFactory as _
+from z3c.authenticator import interfaces
+
+UTILITY_TITLE = _(
+    'z3c.authenticator.vocabulary-utility-plugin-title',
+    '${name} (a utility)')
+CONTAINED_TITLE = _(
+    'z3c.authenticator.vocabulary-contained-plugin-title',
+    '${name} (in contents)')
+MISSING_TITLE = _(
+    'z3c.authenticator.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 Authenticator).
+
+    These plugins may be objects contained within the Authenticator 
+    ("contained plugins"), or may be utilities registered for the specified
+    interface, found in the context of the Authenticator 
+    ("utility plugins"). Contained plugins mask utility plugins of the same 
+    name.
+
+    The vocabulary also includes the current values of the 
+    Authenticator even if they do not correspond to a contained or 
+    utility plugin.
+    """
+    terms = {}
+    auth = interfaces.IAuthenticator.providedBy(context)
+    if auth:
+        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(), zope.i18n.Message(
+                        CONTAINED_TITLE, mapping={'name': title}))
+    utils = zope.component.getUtilitiesFor(interface, context)
+    for nm, util in utils:
+        if nm not in terms:
+            terms[nm] = vocabulary.SimpleTerm(
+                nm, nm.encode('base64').strip(), zope.i18n.Message(
+                    UTILITY_TITLE, mapping={'name': nm}))
+    if auth:
+        for nm in set(getattr(context, attr_name)):
+            if nm not in terms:
+                terms[nm] = vocabulary.SimpleTerm(
+                    nm, nm.encode('base64').strip(), zope.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')
+
+zope.interface.alsoProvides(authenticatorPlugins, IVocabularyFactory)
+
+def credentialsPlugins(context):
+    return _pluginVocabulary(
+        context, interfaces.ICredentialsPlugin, 'credentialsPlugins')
+
+zope.interface.alsoProvides(credentialsPlugins, IVocabularyFactory)


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.txt
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.txt	                        (rev 0)
+++ z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.txt	2008-03-23 11:03:15 UTC (rev 84862)
@@ -0,0 +1,136 @@
+==========
+Vocabulary
+==========
+
+The vocabulary module provides vocabularies for the authenticatorPlugins.
+
+The options should include the unique names of all of the plugins that provide
+the appropriate interface (interfaces.IAuthentiatorPlugin, respectively) for 
+the current context-- which is expected to be a pluggable authentication 
+utility, hereafter referred to as a Authenticator.
+
+These names may be for objects contained within the Authenticator 
+("contained plugins"), or may be utilities registered for the specified 
+interface, found in the context of the Authenticator 
+("utility plugins"). Contained plugins mask utility plugins of the same name. 
+They also may be names currently selected in the Authenticator 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 Authenticator.
+
+    >>> from z3c.authenticator import interfaces
+    >>> import zope.interface
+    >>> import zope.component
+    >>> class DemoPlugin(object):
+    ...     zope.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():
+    ...     zope.component.provideUtility(p, name=p.name)
+    ...
+    >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1
+    [1, 2, 3, 4]
+    >>> class DemoAuth(dict):
+    ...     zope.interface.implements(interfaces.IAuthenticator)
+    ...     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())
+    
+    >>> @zope.component.adapter(zope.interface.Interface)
+    ... @zope.interface.implementer(zope.component.IComponentLookup)
+    ... def getSiteManager(context):
+    ...     return zope.component.getGlobalSiteManager()
+    ...
+    >>> zope.component.provideAdapter(getSiteManager)
+
+We are now ready to create a vocabulary that we can use.  The context is
+our faux authentication utility, `auth`.
+
+    >>> from z3c.authenticator 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(zope.interface.Interface):
+    ...     pass
+    ...
+    >>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)
+    >>> class DemoDCAdapter(object):
+    ...     zope.interface.implements(
+    ...         zope.dublincore.interfaces.IDCDescriptiveProperties)
+    ...     zope.component.adapts(ISpecial)
+    ...     def __init__(self, context):
+    ...         pass
+    ...     title = u'Special Title'
+    ...
+    >>> zope.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)']


Property changes on: z3c.authenticator/trunk/src/z3c/authenticator/vocabulary.txt
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list