[Checkins] SVN: z3c.authentication/trunk/src/z3c/authentication/simple/ Added simple authentication implementation

Roger Ineichen roger at projekt01.ch
Wed Jan 17 20:27:11 EST 2007


Log message for revision 72089:
  Added simple authentication implementation

Changed:
  A   z3c.authentication/trunk/src/z3c/authentication/simple/
  A   z3c.authentication/trunk/src/z3c/authentication/simple/README.txt
  A   z3c.authentication/trunk/src/z3c/authentication/simple/SETUP.cfg
  A   z3c.authentication/trunk/src/z3c/authentication/simple/TODO.txt
  A   z3c.authentication/trunk/src/z3c/authentication/simple/__init__.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/authentication.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/__init__.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/add.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/adding.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/configure.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/edit.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/group.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/member.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/register.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/browser/schemasearch.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/configure.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/event.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/group.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/group.txt
  A   z3c.authentication/trunk/src/z3c/authentication/simple/group.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/interfaces.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/member.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/member.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/principal.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/principal.zcml
  A   z3c.authentication/trunk/src/z3c/authentication/simple/tests.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.py
  A   z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.txt
  A   z3c.authentication/trunk/src/z3c/authentication/simple/z3c.authentication.simple-configure.zcml

-=-
Added: z3c.authentication/trunk/src/z3c/authentication/simple/README.txt
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/README.txt	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/README.txt	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,451 @@
+=============================
+Simple Authentication Utility
+=============================
+
+The Simple 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 IMember implementations.
+
+
+Security
+--------
+
+The SimpleAuthentication 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. 
+Beacuse 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 IMember. This adapters do not offer
+it's context which is the real IMember.
+
+The SimpleAuthentication doesn't use a prefix. The usage of a prefix is only
+implemented in the IMemberContainer. 
+
+We do not use a prefix in the IMemberContainer 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 IMember instances anymore. We heavily restricted the
+usage of this method. See the inline doc tests in __setitem__ for more info.
+
+The ICredentialsPlugin used in the IPluggableAuthentication are 100% 
+compatible with our SimpleAuthentication implementation.
+
+
+Authentication
+==============
+
+The primary job of SimpleAuthentication 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 SimpleAuthentication returns a principal object, 
+if it can. The SimpleAutentication 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 
+SimpleAuthentication 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.authentication.simple import interfaces
+  >>> from z3c.authentication.simple.member import Member
+  >>> login = u'bob'
+  >>> password = u'secret'
+  >>> title = u'Bob'
+  >>> p = Member(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 IMember:
+
+  >>> interfaces.IMember.providedBy(p)
+  True
+
+
+Authenticator Plugin
+--------------------
+
+First setup a MemberContainer which will store the principals:
+
+  >>> from z3c.authentication.simple.member import MemberContainer
+  >>> authPlugin = MemberContainer()
+  
+Now we have a MemberContainer that provides a IMemberContainer:
+
+  >>> interfaces.IMemberContainer.providedBy(authPlugin)
+  True
+
+Now we will add the created principal to the principal container using the 
+containers method ``add``:
+
+  >>> uid, member = authPlugin.add(p)
+
+The method returns the member id and the member object. The id get generated
+by the host IP address, the time, a random string and the member 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.
+
+  >>> member.__name__ == uid
+  True
+
+The returned member still is our previous added IMember
+
+  >>> member is p
+  True
+
+  >>> len(member.__name__)
+  32
+
+  >>> member.login
+  u'bob'
+
+  >>> member.password
+  u'secret'
+
+  >>> member.title
+  u'Bob'
+
+let's register the MemberContainer as a named IAuthenticatorPlugin 
+utility:
+
+  >>> import zope.component
+  >>> zope.component.provideUtility(authPlugin, 
+  ...     provides=interfaces.IAuthenticatorPlugin, 
+  ...     name='My Authenticator Plugin')
+ 
+
+Credentials Plugin
+------------------
+
+After setup the member and member container, we'll create a simple 
+credentials plugin:
+
+  >>> import zope.interface
+  >>> import zope.component
+  >>> from zope.app.authentication.interfaces import ICredentialsPlugin
+
+  >>> class MyCredentialsPlugin(object):
+  ...
+  ...     zope.interface.implements(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 SimpleAuthentication 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 member, they are not responsible
+for creating principals. This function is performed by the 
+SimpleAuthentication:
+
+  >>> from z3c.authentication.simple.principal import AuthenticatedPrincipal
+  >>> from z3c.authentication.simple.principal import FoundPrincipal
+  >>> zope.component.provideAdapter(AuthenticatedPrincipal, 
+  ...     provides=interfaces.IAuthenticatedPrincipal)
+
+  >>> zope.component.provideAdapter(FoundPrincipal, 
+  ...     provides=interfaces.IFoundPrincipal)
+
+
+Configuring SimpleAuthentication
+--------------------------------
+
+Finally, we'll create the SimpleAuthentication itself:
+
+  >>> from z3c.authentication.simple import authentication
+  >>> sau = authentication.SimpleAuthentication()
+
+and configure it with the two plugins:
+
+  >>> sau.credentialsPlugins = ('My Credentials Plugin', )
+  >>> sau.authenticatorPlugins = ('My Authenticator Plugin', )
+
+
+Authenticate
+------------
+
+We can now use the SimpleAuthentication to authenticate a sample request:
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> print sau.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 sau.authenticate(request)
+  None
+
+However, if we provide the proper credentials:
+
+  >>> request = TestRequest(form={'login':'bob', 'password':'secret'})
+  >>> bob = sau.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 MemberContainer and change the login on the IMember
+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 = sau.authenticate(request)
+  >>> bob2
+  <AuthenticatedPrincipal ...>
+
+  >>> bob2.title
+  u'Bob'
+
+But not with the old one:
+
+  >>> request = TestRequest(form={'login':'bob', 'password':'secret'})
+  >>> sau.authenticate(request) == None
+  True
+
+The member 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 = sau.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 = sau.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 IMyMember(IMyEmail, interfaces.IMember):
+  ...     """Custom IMember 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 MyMember(Member):
+  ...     zope.interface.implements(IMyMember)
+  ...     def __init__(self, login, password, title, description, email):
+  ...         super(MyMember, self).__init__(login, password, title, 
+  ...                                           description)
+  ...         self.email = email
+
+Now we have to define the AuthenticatedPrincipal for MyMember:
+
+  >>> 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 MyMember:
+
+  >>> 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 = MyMember(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 = sau.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.authentication/trunk/src/z3c/authentication/simple/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/SETUP.cfg
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/SETUP.cfg	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/SETUP.cfg	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,3 @@
+<data-files zopeskel/etc/package-includes>
+  z3c.authentication.simple-*.zcml
+</data-files>


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/SETUP.cfg
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/TODO.txt
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/TODO.txt	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/TODO.txt	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,9 @@
+====
+TODO
+====
+
+- Add timestamp to principal id generation
+
+- Add configure for everything
+
+- Add configurator for the new IAuthentication utility


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/__init__.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/__init__.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/__init__.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,16 @@
+###############################################################################
+#
+# Copyright (c) 2006 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$
+"""
\ No newline at end of file


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/authentication.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/authentication.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/authentication.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,174 @@
+###############################################################################
+#
+# Copyright (c) 2006 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.authentication.interfaces import ICredentialsPlugin
+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.authentication.simple import interfaces
+from z3c.authentication.simple import event
+
+
+class SimpleAuthentication(btree.BTreeContainer):
+    """See z3c.authentication.interfaces.ISimpleAuthentication."""
+
+    zope.interface.implements(IAuthentication, 
+        interfaces.ISimpleAuthentication, 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, ICredentialsPlugin)
+
+    def authenticate(self, request):
+        authenticatorPlugins = [p for n, p in self.getAuthenticatorPlugins()]
+        for name, credplugin in self.getCredentialsPlugins():
+            credentials = credplugin.extractCredentials(request)
+            for authplugin in authenticatorPlugins:
+                if authplugin is None:
+                    continue
+                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.ISimpleAuthentication)
+
+    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.authentication/trunk/src/z3c/authentication/simple/authentication.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/__init__.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/__init__.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/__init__.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,16 @@
+##############################################################################
+#
+# Copyright (c) 2006 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$
+"""


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/add.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/add.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/add.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,106 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Add forms
+
+$Id$
+"""
+
+import zope.interface
+import zope.schema
+from zope.formlib import form
+
+from z3c.authentication.simple import interfaces
+from z3c.authentication.simple import group
+from z3c.authentication.simple import member
+
+
+class IContentName(zope.interface.Interface):
+    """Object name."""
+
+    __name__ = zope.schema.TextLine( 
+        title=u'Object Name',
+        description=u'Object Name',
+        required=True)
+
+
+class MemberContainerAddForm(form.AddForm):
+    """MemberContainer add form."""
+
+    label = _('Add Member Container.')
+
+    form_fields = form.Fields(IContentName)
+
+    def create(self, data):
+        obj = member.MemberContainer()
+        self.context.contentName = data.get('__name__', u'')
+        return obj
+
+
+class MemberAddForm(form.AddForm):
+    """Member add form."""
+
+    label = _('Add Member.')
+
+    form_fields = form.Fields(interfaces.IMember).select('login', 'password', 
+        'title', 'description', 'passwordManagerName')
+    form_fields += form.Fields(IContentName)
+
+    def create(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 = member.Member(login, password, title, description, 
+            passwordManagerName)
+        name = data.get('__name__', u'')
+        self.context.contentName = name
+        return obj
+
+
+
+class GroupContainerAddForm(form.AddForm):
+    """GroupContainer add form."""
+
+    label = _('Add Group Container.')
+
+    form_fields = form.Fields(interfaces.IGroupContainer).select(
+        'prefix')
+    form_fields += form.Fields(IContentName)
+
+    def create(self, data):
+        obj = group.GroupContainer()
+        obj.prefix = data.get('prefix', u'')
+        self.context.contentName = data.get('__name__', u'')
+        return obj
+
+
+class GroupAddForm(form.AddForm):
+    """Group add form."""
+
+    label = _('Add Group.')
+
+    form_fields = form.Fields(interfaces.IGroup).select('title', 'description')
+    form_fields += form.Fields(IContentName)
+
+    def create(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.context.prefix
+        if not name.startswith(prefix):
+            name = prefix + name
+        self.context.contentName = name
+        return obj
\ No newline at end of file


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/browser/add.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/adding.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/adding.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/adding.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Adding that redirects to plugins.html.
+
+$Id$
+"""
+
+from zope.app import zapi
+
+import zope.app.container.browser.adding
+
+class Adding(zope.app.container.browser.adding.Adding):
+    
+    def nextURL(self):
+        return zapi.absoluteURL(self.context, self.request
+                                ) + '/@@contents.html'


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/browser/adding.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/configure.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/configure.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/configure.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,82 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="z3c">
+
+  <addform
+      schema="..interfaces.ISimpleAuthentication"
+      label="Add Simple Authentication"
+      content_factory="..authentication.SimpleAuthentication"
+      name="AddSimpleAuthentication.html"
+      permission="zope.ManageServices"
+      />
+
+  <addMenuItem
+      class="..authentication.SimpleAuthentication"
+      view="AddSimpleAuthentication.html"
+      title="Simple Authentication Utility"
+      description="Simple authentication utility"
+      permission="zope.ManageServices"
+      />
+
+  <page
+      for="..interfaces.ISimpleAuthentication"
+      name="addRegistration.html"
+      permission="zope.ManageSite"
+      class=".register.AddAuthenticationRegistration" 
+      />
+
+  <editform
+      schema="..interfaces.ISimpleAuthentication"
+      label="Edit Simple Authentication Utility"
+      name="configure.html"
+      fields="credentialsPlugins authenticatorPlugins"
+      menu="zmi_views" title="Configure"
+      permission="zope.ManageServices"
+      />
+
+  <page
+      name="contents.html"
+      for="..interfaces.ISimpleAuthentication"
+      menu="zmi_views" title="Plugins"
+      permission="zope.ManageSite"
+      class="zope.app.container.browser.contents.Contents"
+      attribute="contents"
+      />
+
+  <view
+      name="+"
+      menu="zmi_actions" title="Add"
+      for="..interfaces.ISimpleAuthentication"
+      permission="zope.ManageSite"
+      class=".adding.Adding"
+      >
+    <page
+        name="index.html"
+        attribute="index"
+        />
+    <page
+        name="action.html"
+        attribute="action"
+        />
+  </view>
+
+  <menuItem
+      menu="zmi_views"
+      for="..interfaces.ISimpleAuthentication"
+      title="Contents"
+      action=""
+      filter="python:False"
+      />
+
+  <zope:adapter
+      for="..interfaces.IQuerySchemaSearch
+           zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.app.form.browser.interfaces.ISourceQueryView"
+      factory=".schemasearch.QuerySchemaSearchView"
+      />
+
+  <include file="group.zcml" />
+  <include file="member.zcml" />
+
+</configure>


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/edit.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/edit.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/edit.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Adding that redirects to plugins.html.
+
+$Id$
+"""
+
+from zope.formlib import form
+
+from z3c.authentication.simple import interfaces
+
+
+class EditMember(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit Member.')
+
+    form_fields = form.Fields(interfaces.IMember).select('login', 'password', 
+        'title', 'description', 'passwordManagerName')
+
+
+class EditGroup(form.EditForm):
+    """Group edit form."""
+
+    label = _('Edit Group.')
+
+    form_fields = form.Fields(interfaces.IGroup).select('title', 
+        'description', 'principals')


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/group.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/group.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/group.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,53 @@
+<configure 
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="z3c">
+
+  <!-- Group -->
+  <page
+      for="zope.app.container.interfaces.IAdding"
+      name="AddGroup.html"
+      class=".add.GroupAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <addMenuItem
+      title="Simple Group"
+      description="A simple group"
+      class="..group.Group"
+      permission="zope.ManageServices"
+      view="AddGroup.html"
+      />
+
+  <page
+      name="edit.html"
+      for="..interfaces.IGroup"
+      class=".edit.EditGroup"
+      menu="zmi_views" title="Edit"
+      permission="zope.ManageServices"
+      />
+
+
+  <!-- GroupContainer -->
+  <page
+      for="zope.app.container.interfaces.IAdding"
+      name="AddGroupContainer.html"
+      class=".add.GroupContainerAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <addMenuItem
+      title="Group Container"
+      description="A Group container"
+      class="..group.GroupContainer"
+      permission="zope.ManageServices"
+      view="AddGroupContainer.html"
+      />
+
+  <containerViews
+      for="..interfaces.IGroupContainer"
+      contents="zope.ManageServices"
+      index="zope.ManageServices"
+      add="zope.ManageServices"
+      />
+
+</configure> 


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/member.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/member.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/member.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,61 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser">
+
+  <!-- Member -->
+  <page
+      for="zope.app.container.interfaces.IAdding"
+      name="AddSimpleMember.html"
+      class=".add.MemberAddForm"
+      permission="zope.ManageServices"
+      />
+  <addMenuItem
+      title="Member"
+      class="..member.Member"
+      permission="zope.ManageServices"
+      view="AddSimpleMember.html"
+      />
+
+  <page
+      name="edit.html"
+      for="..interfaces.IMember"
+      class=".edit.EditMember"
+      menu="zmi_views" title="Edit"
+      permission="zope.ManageServices"
+      />
+
+  <containerViews
+      for="..interfaces.IMemberContainer"
+      add="zope.ManageServices"
+      contents="zope.ManageServices"
+      index="zope.ManageServices"
+      />
+
+
+  <!-- MemberContainer -->
+  <page
+      for="zope.app.container.interfaces.IAdding"
+      name="AddSimpleMemberContainer.html"
+      class=".add.MemberContainerAddForm"
+      permission="zope.ManageServices"
+      />
+
+  <addMenuItem
+      title="Member Container"
+      description="A Pluggable Persistent Authentication Plugin"
+      class="..member.MemberContainer"
+      permission="zope.ManageServices"
+      view="AddSimpleMemberContainer.html"
+      />
+
+  <addform
+      schema="..interfaces.IMember"
+      label="Add Member"
+      content_factory="..member.Member"
+      arguments="login password title"
+      keyword_arguments="passwordManagerName description"
+      fields="login passwordManagerName password title description"
+      name="AddMember.html"
+      permission="zope.ManageServices"
+      />
+
+</configure>


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/register.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/register.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/register.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Improved registration UI for registering pluggable authentication utilities
+
+$Id$
+"""
+
+from zope.app.i18n import ZopeMessageFactory as _
+import zope.app.component.browser.registration
+import zope.app.security.interfaces
+
+class AddAuthenticationRegistration(
+    zope.app.component.browser.registration.AddUtilityRegistration,
+    ):
+    label = _("Register a pluggable authentication utility")
+    name = ''
+    provided = zope.app.security.interfaces.IAuthentication


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/browser/register.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/browser/schemasearch.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/browser/schemasearch.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/browser/schemasearch.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Search interface for queriables.
+
+$Id$
+"""
+__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.authentication/trunk/src/z3c/authentication/simple/browser/schemasearch.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/configure.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/configure.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/configure.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,43 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <class class=".authentication.SimpleAuthentication">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.ManageSite"
+        interface=".interfaces.ISimpleAuthentication"
+        set_schema=".interfaces.ISimpleAuthentication"
+        />
+    <require
+        permission="zope.ManageServices"
+        attributes="registrationManager"
+        />
+  </class>
+
+  <adapter
+      for=".interfaces.IQuerySchemaSearch
+           .interfaces.ISimpleAuthentication"
+      provides=".interfaces.IQueriableAuthenticator"
+      factory=".authentication.QuerySchemaSearchAdapter"
+      />
+
+  <utility
+      component=".vocabulary.authenticatorPlugins"
+      name="SimpleAuthenticatorPlugins"
+      />
+
+  <utility
+      component=".vocabulary.credentialsPlugins"
+      name="SimpleCredentialsPlugins"
+      />
+
+  <include file="principal.zcml" />
+  <include file="group.zcml" />
+  <include file="member.zcml" />
+
+  <include package=".browser" />
+
+</configure>


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/event.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/event.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/event.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,92 @@
+###############################################################################
+#
+# Copyright (c) 2006 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.authentication.simple 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 AbstractMembersChanged(object):
+
+    def __init__(self, principal_ids, group_id):
+        self.principal_ids = principal_ids
+        self.group_id = group_id
+
+    def __repr__(self):
+        return "<%s %r %r>" % (
+            self.__class__.__name__, sorted(self.principal_ids), self.group_id)
+
+
+class PrincipalsAddedToGroup(AbstractMembersChanged):
+    zope.interface.implements(interfaces.IPrincipalsAddedToGroup)
+
+
+class PrincipalsRemovedFromGroup(AbstractMembersChanged):
+    zope.interface.implements(interfaces.IPrincipalsRemovedFromGroup)


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/event.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/group.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/group.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/group.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,313 @@
+###############################################################################
+#
+# Copyright (c) 2006 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.authentication.simple import interfaces
+from z3c.authentication.simple 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'members')
+        >>> 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.members', 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 getMembers(self):
+        return self._group.principals
+
+    def setMembers(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.authentication/trunk/src/z3c/authentication/simple/group.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/group.txt
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/group.txt	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/group.txt	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,582 @@
+=====
+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 members, groups are created when they are needed.
+
+  >>> from z3c.authentication.simple import interfaces
+  >>> from z3c.authentication.simple.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.authentication.simple.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 SimpleAuthentication utility:
+
+  >>> from z3c.authentication.simple.authentication import SimpleAuthentication
+  >>> sau = SimpleAuthentication()
+
+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 zope.app.authentication.interfaces import ICredentialsPlugin
+
+  >>> class MyCredentialsPlugin(object):
+  ...
+  ...     zope.interface.implements(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 SimpleAuthentication:
+
+  >>> myCredentialsPlugin = MyCredentialsPlugin()
+  >>> sau['credentials'] = myCredentialsPlugin
+  >>> sau.credentialsPlugins = ('credentials', )
+
+We also need a principal and a IAuthenticationPlugin:
+
+  >>> from z3c.authentication.simple.member import Member
+  >>> p1 = Member(u'p1', u'password', u'Principal 1')
+  >>> p2 = Member(u'p2', u'password', u'Principal 2')
+  >>> p3 = Member(u'p3', u'password', u'Principal 3')
+  >>> p4 = Member(u'p4', u'password', u'Principal 4')
+
+  >>> from z3c.authentication.simple.member import MemberContainer
+  >>> members = MemberContainer()
+  >>> token1, p1 = members.add(p1)
+  >>> token2, p2 = members.add(p2)
+  >>> token3, p3 = members.add(p3)
+  >>> token4, p4 = members.add(p4)
+
+Add the GroupContainer and MemberContainer to the SimpleAuthentication and 
+set the correct plugin names
+
+  >>> sau['members'] = members
+  >>> sau['groups'] = groups
+  >>> sau.authenticatorPlugins = ('members', 'groups')
+
+
+Adding members to groups
+------------------------
+
+Now we can set the members on the group but first we need to register the
+IFoundPrincipal adapter for groups. The GroupPrincipal adapter provides this
+interface:
+
+  >>> from z3c.authentication.simple.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.authentication.simple.principal import AuthenticatedPrincipal
+  >>> from z3c.authentication.simple.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.authentication.simple.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 members fires an event.
+
+  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+  <PrincipalsAddedToGroup [..., ...] u'groups.g1'>
+
+We can now look up groups for the members:
+
+  >>> 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
+members:
+
+  >>> 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 members 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
+
+  >>> from zope.security.interfaces import IGroup
+  >>> 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
+members 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.authentication.simple.principal import FoundPrincipal
+  >>> from z3c.authentication.simple.event import FoundPrincipalCreated
+  >>> from z3c.authentication.simple.group import specialGroups
+  >>> x = Member('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
+  []
+
+
+MemberAwareGroup
+----------------
+
+We can manages groups via the group getMember and setMember methods:
+
+  >>> foundGroup = interfaces.IFoundPrincipal(g1)
+  >>> foundGroup.getMembers()
+  (..., ...)
+
+  >>> foundGroup.getMembers()[0] == p1.__name__
+  True
+
+  >>> foundGroup.getMembers()[1] == p2.__name__
+  True
+
+Let's remove a member:
+
+  >>> foundGroup.setMembers((p2.__name__,))
+  >>> foundGroup.getMembers()
+  (...,)
+
+  >>> foundGroup.getMembers()[0] == p2.__name__
+  True
+
+  >>> foundGroup.members
+  (...,)
+
+  >>> foundGroup.getMembers()[0] == p2.__name__
+  True
+
+Ensure that such a member provides the IMemberAwareGroup interfaces.
+
+  >>> zope.security.interfaces.IMemberAwareGroup.providedBy(foundGroup)
+  True
+
+#Member-aware groups
+#-------------------
+#The groupfolder includes a subscriber that gives group principals the
+#zope.security.interfaces.IGroupAware interface and an implementation thereof.
+#This allows groups to be able to get and set their members.
+#
+#Given an info object and a 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.setMemberSubscriber(
+#    ...     interfaces.FoundPrincipalCreated(
+#    ...         'dummy auth (ignored)', principal, info))
+#    >>> principal.getMembers()
+#    ('joe', 'jane')
+#    >>> principal.setMembers(('joe', 'jane', 'jaimie'))
+#    >>> principal.getMembers()
+#    ('joe', 'jane', 'jaimie')
+#    >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal)
+#    True
+#
+#The two methods work with the value on the IGroupInformation object.
+#
+#    >>> i.principals == principal.getMembers()
+#    True


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

Added: z3c.authentication/trunk/src/z3c/authentication/simple/group.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/group.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/group.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -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.authentication/trunk/src/z3c/authentication/simple/group.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/interfaces.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/interfaces.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/interfaces.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,362 @@
+###############################################################################
+#
+# Copyright (c) 2006 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 _
+
+
+# authenticator interfaces
+class IAuthenticatorPlugin(zope.interface.Interface):
+    """Authenticates and provides a principal using credentials."""
+
+    containers('z3c.authentication.simple.interfaces.ISimpleAuthentication')
+
+    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 ISimpleAuthentication(ILogout, IContainer):
+    """Simple authentication supporting zope 3 credential plugins.
+    
+    The SimpleAuthentication supports ICredentialsPlugin plugins defined in
+    zope.app.authentication.interfaces.
+    
+    The SimpleAuthentication 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 authentication plugins because we do not 
+    use the IPrincipalInfo implementation in this authentication module.
+    """
+
+    contains(IAuthenticatorPlugin, ICredentialsPlugin)
+
+    credentialsPlugins = zope.schema.List(
+        title=_('Credentials Plugins'),
+        description=_("""Used for extracting credentials.
+        Names may be of ids of non-utility ICredentialsPlugins contained in
+        the ISimpleAuthentication, or names of registered
+        ICredentialsPlugins utilities.  Contained non-utility ids mask 
+        utility names."""),
+        value_type=zope.schema.Choice(vocabulary='SimpleCredentialsPlugins'),
+        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 ISimpleAuthentication, or names of registered
+        IAuthenticatorPlugins utilities.  Contained non-utility ids mask 
+        utility names."""),
+        value_type=zope.schema.Choice(vocabulary='SimpleAuthenticatorPlugins'),
+        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."""
+
+
+
+# member interfaces
+class IMember(zope.interface.Interface):
+    """User"""
+
+    containers('z3c.authentication.simple.interfaces.IMemberContainer')
+
+    login = zope.schema.TextLine(
+        title=_("Login"),
+        description=_("The Login/Username of the principal. "
+                      "This value can change."))
+
+    def setPassword(password, passwordManagerName=None):
+        pass
+
+    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 IMemberSearchCriteria(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 IMemberContainer(IContainer, IAuthenticatorPlugin):
+    """Principal container."""
+
+    contains(IMember)
+
+
+
+# principal interfaces
+class IFoundPrincipal(IGroupClosureAwarePrincipal):
+    """A factory adapting IMember and offering read access to the principal.
+    
+    A found principal gets created by the ISimpleAuthentications search method
+    for members 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 member 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 ISimpleAuthentications 
+    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 member 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.authentication.simple.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')
+
+
+# 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.authentication/trunk/src/z3c/authentication/simple/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/member.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/member.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/member.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,255 @@
+###############################################################################
+#
+# Copyright (c) 2006 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.authentication.simple 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 md5.md5(data).hexdigest()
+
+
+class Member(persistent.Persistent, contained.Contained):
+    """User stored in IMemberContainer."""
+
+    zope.interface.implements(interfaces.IMember)
+
+    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 MemberContainer(btree.BTreeContainer):
+    """A Persistent Member Container and authenticator plugin.
+
+    See principalfolder.txt for details.
+    """
+
+    zope.interface.implements(interfaces.IMemberContainer, 
+        interfaces.IQuerySchemaSearch)
+
+    schema = interfaces.IMemberSearchCriteria
+
+    def __init__(self):
+        super(MemberContainer, 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, member):
+        """Add a IPrincipal object within a correct id.
+
+        Create a MemberContainer
+
+        >>> mc = MemberContainer('members.')
+
+        Try to add something not providing IMember
+        >>> try:
+        ...     mc.__setitem__(u'max', object())
+        ... except Exception, e:
+        ...     print e
+        Item does not support IMember!
+
+        Create a member with no __name__, this should be added via the add
+        method
+
+        >>> member = Member()
+        >>> try:
+        ...     mc.__setitem__(u'max', member)
+        ... 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 
+
+        >>> member = Member()
+        >>> member.__name__ = u'usertoken'
+        >>> try:
+        ...     mc.__setitem__(u'max', member)
+        ... except Exception, e:
+        ...     print e
+        Paste a object is not supported!
+
+        Try to use a member with no login:
+
+        >>> try:
+        ...     mc.__setitem__(u'max', member)
+        ... except Exception, e:
+        ...     print e
+        Member does not provide a login value!
+
+        Add a login attr since __setitem__ is in need of one
+
+        >>> member.login = u'max'
+        >>> mc.__setitem__(u'members.max', principal)
+
+        Now try to use the same login:
+
+        >>> try:
+        ...     mc.__setitem__(u'max', member)
+        ... except Exception, e:
+        ...     print e
+        Login already taken!
+        """
+
+        # check if we store correct implementations
+        if not interfaces.IMember.providedBy(member):
+            raise TypeError('Item does not support IMember!')
+
+        # check if there is a user id given
+        if member.__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 member.__parent__ is not None:
+            raise ValueError('Paste a object is not supported!')
+
+        # The member doesn't provide a login
+        if not member.login:
+            raise ValueError('Member does not provide a login value!')
+
+        # A user with the new login already exists
+        if member.login in self.__id_by_login:
+            raise DuplicateIDError('Login already taken!')
+
+        super(MemberContainer, self).__setitem__(id, member)
+        self.__id_by_login[member.login] = id
+
+    def add(self, member):
+        token = generateUserIDToken(member.login)
+        # Pre set the user id like a ticket, so we can check it in __setitem__
+        member.__name__ = token
+        self[token] = member
+        return token, self[token]
+
+    def __delitem__(self, id):
+        """Remove principal information."""
+        member = self[id]
+        super(MemberContainer, self).__delitem__(id)
+        del self.__id_by_login[member.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
+        member = self[id]
+        if not member.checkPassword(credentials["password"]):
+            return None
+        return member
+
+    def queryPrincipal(self, id, default=None):
+        member = self.get(id)
+        if member is not None:
+            return member
+        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.authentication/trunk/src/z3c/authentication/simple/member.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/member.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/member.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/member.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,37 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <class class=".member.Member">
+    <require
+        permission="zope.ManageServices"
+        interface=".interfaces.IMember"
+        set_schema=".interfaces.IMember"
+        />
+  </class>
+
+  <class class=".member.MemberContainer">
+
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable"
+        />
+
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.container.interfaces.IContainer"
+        />
+
+    <require
+        permission="zope.ManageServices"
+        attributes="prefix"
+        />
+
+  </class>
+
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for=".interfaces.IMemberContainer"
+      factory="zope.app.authentication.idpicker.IdPicker"
+      />
+
+</configure>


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/member.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/principal.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/principal.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/principal.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,74 @@
+###############################################################################
+#
+# Copyright (c) 2006 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.authentication.simple 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.IMember)
+
+    zope.interface.implements(interfaces.IAuthenticatedPrincipal)
+
+
+class FoundPrincipal(PrincipalBase):
+    """Default IFoundPrincipal principal."""
+
+    zope.component.adapts(interfaces.IMember)
+
+    zope.interface.implements(interfaces.IFoundPrincipal)


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/principal.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/principal.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/principal.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/principal.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -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.authentication/trunk/src/z3c/authentication/simple/principal.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/tests.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/tests.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/tests.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,51 @@
+###############################################################################
+#
+# Copyright (c) 2006 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 setup
+from zope.app.testing import placelesssetup
+
+
+def siteSetUp(test):
+    site = setup.placefulSetUp(site=True)
+    test.globs['rootFolder'] = site
+
+
+def siteTearDown(test):
+    setup.placefulTearDown()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+            setUp=siteSetUp, tearDown=siteTearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+        doctest.DocFileSuite('group.txt',
+            setUp=siteSetUp, tearDown=siteTearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+        doctest.DocTestSuite('z3c.authentication.simple.principal',
+            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
+        doctest.DocTestSuite('z3c.authentication.simple.group',
+            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
+        doctest.DocFileSuite('vocabulary.txt',
+            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.py
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.py	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.py	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,100 @@
+###############################################################################
+#
+# Copyright (c) 2006 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 zope.app.authentication.interfaces import ICredentialsPlugin
+
+from z3c.i18n import MessageFactory as _
+from z3c.authentication.simple import interfaces
+
+UTILITY_TITLE = _(
+    'zope.app.authentication.vocabulary-utility-plugin-title',
+    '${name} (a utility)')
+CONTAINED_TITLE = _(
+    'zope.app.authentication.vocabulary-contained-plugin-title',
+    '${name} (in contents)')
+MISSING_TITLE = _(
+    'zope.app.authentication.vocabulary-missing-plugin-title',
+    '${name} (not found; deselecting will remove)')
+
+
+def _pluginVocabulary(context, interface, attr_name):
+    """Vocabulary that provides names of plugins of a specified interface.
+
+    Given an interface, the options should include the unique names of all of
+    the plugins that provide the specified interface for the current context--
+    which is expected to be a pluggable authentication utility, hereafter
+    referred to as a SimpleAuthentication).
+
+    These plugins may be objects contained within the SimpleAuthentication 
+    ("contained plugins"), or may be utilities registered for the specified
+    interface, found in the context of the SimpleAuthentication 
+    ("utility plugins"). Contained plugins mask utility plugins of the same 
+    name.
+
+    The vocabulary also includes the current values of the 
+    SimpleAuthentication even if they do not correspond to a contained or 
+    utility plugin.
+    """
+    terms = {}
+    auth = interfaces.ISimpleAuthentication.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, ICredentialsPlugin, 'credentialsPlugins')
+
+zope.interface.alsoProvides(credentialsPlugins, IVocabularyFactory)


Property changes on: z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.txt
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.txt	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/vocabulary.txt	2007-01-18 01:27:07 UTC (rev 72089)
@@ -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 SimpleAuthentication.
+
+These names may be for objects contained within the SimpleAuthentication 
+("contained plugins"), or may be utilities registered for the specified 
+interface, found in the context of the SimpleAuthentication 
+("utility plugins"). Contained plugins mask utility plugins of the same name. 
+They also may be names currently selected in the SimpleAuthentication 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 SimpleAuthentication.
+
+    >>> from z3c.authentication.simple 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.ISimpleAuthentication)
+    ...     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.authentication.simple 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.authentication/trunk/src/z3c/authentication/simple/vocabulary.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.authentication/trunk/src/z3c/authentication/simple/z3c.authentication.simple-configure.zcml
===================================================================
--- z3c.authentication/trunk/src/z3c/authentication/simple/z3c.authentication.simple-configure.zcml	2007-01-18 01:09:33 UTC (rev 72088)
+++ z3c.authentication/trunk/src/z3c/authentication/simple/z3c.authentication.simple-configure.zcml	2007-01-18 01:27:07 UTC (rev 72089)
@@ -0,0 +1,6 @@
+<configure
+    xmlns:zcml="http://namespaces.zope.org/zcml">
+
+  <include package="z3c.authentication.simple" />
+
+</configure>


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



More information about the Checkins mailing list