[Checkins] SVN: zope.pluggableauth/trunk/src/zope/pluggableauth/ Restaured factories for authenticated principal.

Souheil CHELFOUH souheil at chelfouh.com
Sat Jan 23 19:15:31 EST 2010


Log message for revision 108414:
  Restaured factories for authenticated principal.
  Need to restaure Ifoundprincipalfactory too. Not sure if this should go away or not.
  

Changed:
  U   zope.pluggableauth/trunk/src/zope/pluggableauth/README.txt
  A   zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py
  U   zope.pluggableauth/trunk/src/zope/pluggableauth/interfaces.py
  U   zope.pluggableauth/trunk/src/zope/pluggableauth/tests.py

-=-
Modified: zope.pluggableauth/trunk/src/zope/pluggableauth/README.txt
===================================================================
--- zope.pluggableauth/trunk/src/zope/pluggableauth/README.txt	2010-01-23 23:32:38 UTC (rev 108413)
+++ zope.pluggableauth/trunk/src/zope/pluggableauth/README.txt	2010-01-24 00:15:31 UTC (rev 108414)
@@ -48,7 +48,7 @@
 To illustrate, we'll create a simple credentials plugin::
 
   >>> from zope import interface
-  >>> from zope.app.authentication import interfaces
+  >>> from zope.pluggableauth.authentication import interfaces
 
   >>> class MyCredentialsPlugin(object):
   ...
@@ -105,25 +105,12 @@
   >>> myAuthenticatorPlugin = MyAuthenticatorPlugin()
   >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin')
 
-Principal Factories
-~~~~~~~~~~~~~~~~~~~
-
-While authenticator plugins provide principal info, they are not responsible
-for creating principals. This function is performed by factory adapters. For
-these tests we'll borrow some factories from the principal folder::
-
-  >>> from zope.app.authentication import principalfolder
-  >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory)
-  >>> provideAdapter(principalfolder.FoundPrincipalFactory)
-
-For more information on these factories, see their docstrings.
-
 Configuring a PAU
 ~~~~~~~~~~~~~~~~~
 
 Finally, we'll create the PAU itself::
 
-  >>> from zope.app import authentication
+  >>> from zope.pluggableauth import authentication
   >>> pau = authentication.PluggableAuthentication('xyz_')
 
 and configure it with the two plugins::
@@ -134,6 +121,9 @@
 Using the PAU to Authenticate
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+  >>> from zope.pluggableauth.factory import AuthenticatedPrincipalFactory
+  >>> provideAdapter(AuthenticatedPrincipalFactory)
+
 We can now use the PAU to authenticate a sample request::
 
   >>> from zope.publisher.browser import TestRequest
@@ -155,56 +145,6 @@
 
 we get an authenticated principal.
 
-Authenticated Principal Creates Events
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We can verify that the appropriate event was published::
-
-  >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
-  >>> event.principal is principal
-  True
-  >>> event.info
-  PrincipalInfo('bob')
-  >>> event.request is request
-  True
-
-The info object has the id, title, and description of the principal.  The info
-object is also generated by the authenticator plugin, so the plugin may
-itself have provided additional information on the info object::
-
-  >>> event.info.title
-  'Bob'
-  >>> event.info.id # does not include pau prefix
-  'bob'
-  >>> event.info.description
-  ''
-
-It is also decorated with two other attributes, credentialsPlugin and
-authenticatorPlugin: these are the plugins used to extract credentials for and
-authenticate this principal.  These attributes can be useful for subscribers
-that want to react to the plugins used.  For instance, subscribers can
-determine that a given credential plugin does or does not support logout, and
-provide information usable to show or hide logout user interface::
-
-  >>> event.info.credentialsPlugin is myCredentialsPlugin
-  True
-  >>> event.info.authenticatorPlugin is myAuthenticatorPlugin
-  True
-
-Normally, we provide subscribers to these events that add additional
-information to the principal. For example, we'll add one that sets
-the title::
-
-  >>> def add_info(event):
-  ...     event.principal.title = event.info.title
-  >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated])
-
-Now, if we authenticate a principal, its title is set::
-
-  >>> principal = pau.authenticate(request)
-  >>> principal.title
-  'Bob'
-
 Multiple Authenticator Plugins
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -354,32 +294,14 @@
   - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and
     cheers!)
 
+Multiple Authenticator Plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Principal Searching
--------------------
+As with the other operations we've seen, the PAU uses multiple plugins to
+find a principal. If the first authenticator plugin can't find the requested
+principal, the next plugin is used, and so on.
 
-As a component that provides IAuthentication, a PAU lets you lookup a
-principal with a principal ID. The PAU looks up a principal by delegating to
-its authenticators. In our example, none of the authenticators implement this
-search capability, so when we look for a principal::
-
-  >>> print pau.getPrincipal('xyz_bob')
-  Traceback (most recent call last):
-  PrincipalLookupError: bob
-
-  >>> print pau.getPrincipal('white')
-  Traceback (most recent call last):
-  PrincipalLookupError: white
-
-  >>> print pau.getPrincipal('black')
-  Traceback (most recent call last):
-  PrincipalLookupError: black
-
-For a PAU to support search, it needs to be configured with one or more
-authenticator plugins that support search. To illustrate, we'll create a new
-authenticator::
-
-  >>> class SearchableAuthenticatorPlugin:
+  >>> class AnotherAuthenticatorPlugin:
   ...
   ...     interface.implements(interfaces.IAuthenticatorPlugin)
   ...
@@ -399,93 +321,25 @@
   ...         self.infos[id] = PrincipalInfo(id, title, description)
   ...         self.ids[credentials] = id
 
-This class is typical of an authenticator plugin. It can both authenticate
-principals and find principals given a ID. While there are cases
-where an authenticator may opt to not perform one of these two functions, they
-are less typical.
 
-As with any plugin, we need to register it as a utility::
+To illustrate, we'll create and register two authenticators::
 
-  >>> searchable = SearchableAuthenticatorPlugin()
-  >>> provideUtility(searchable, name='Searchable Authentication Plugin')
+  >>> authenticator1 = AnotherAuthenticatorPlugin()
+  >>> provideUtility(authenticator1, name='Authentication Plugin 1')
 
-We'll now configure the PAU to use only the searchable authenticator::
+  >>> authenticator2 = AnotherAuthenticatorPlugin()
+  >>> provideUtility(authenticator2, name='Authentication Plugin 2')
 
-  >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',)
-
-and add some principals to the authenticator::
-
-  >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b')
-  >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack')
-
-Now when we ask the PAU to find a principal::
-
-  >>> pau.getPrincipal('xyz_bob')
-  Principal('xyz_bob')
-
-but only those it knows about::
-
-  >>> print pau.getPrincipal('black')
-  Traceback (most recent call last):
-  PrincipalLookupError: black
-
-Found Principal Creates Events
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As evident in the authenticator's 'createFoundPrincipal' method (see above),
-a FoundPrincipalCreatedEvent is published when the authenticator finds a
-principal on behalf of PAU's 'getPrincipal'::
-
-  >>> clearEvents()
-  >>> principal = pau.getPrincipal('xyz_white')
-  >>> principal
-  Principal('xyz_white')
-
-  >>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
-  >>> event.principal is principal
-  True
-  >>> event.info
-  PrincipalInfo('white')
-
-The info has an authenticatorPlugin, but no credentialsPlugin, since none was
-used::
-
-  >>> event.info.credentialsPlugin is None
-  True
-  >>> event.info.authenticatorPlugin is searchable
-  True
-
-As we have seen with authenticated principals, it is common to subscribe to
-principal created events to add information to the newly created principal.
-In this case, we need to subscribe to IFoundPrincipalCreated events::
-
-  >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated])
-
-Now when a principal is created as a result of a search, it's title and
-description will be set (by the add_info handler function).
-
-Multiple Authenticator Plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As with the other operations we've seen, the PAU uses multiple plugins to
-find a principal. If the first authenticator plugin can't find the requested
-principal, the next plugin is used, and so on.
-
-To illustrate, we'll create and register a second searchable authenticator::
-
-  >>> searchable2 = SearchableAuthenticatorPlugin()
-  >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2')
-
 and add a principal to it::
 
-  >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite')
+  >>> authenticator1.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite')
 
 When we configure the PAU to use both searchable authenticators (note the
 order)::
 
   >>> pau.authenticatorPlugins = (
-  ...     'Searchable Authentication Plugin 2',
-  ...     'Searchable Authentication Plugin')
+  ...     'Authentication Plugin 2',
+  ...     'Authentication Plugin 1')
 
 we see how the PAU uses both plugins::
 
@@ -499,15 +353,15 @@
 used and the remaining are not delegated to. To illustrate, we'll add
 another principal with the same ID as an existing principal::
 
-  >>> searchable2.add('white', 'White Rider', '', 'r1der')
+  >>> authenticator2.add('white', 'White Rider', '', 'r1der')
   >>> pau.getPrincipal('xyz_white').title
   'White Rider'
 
 If we change the order of the plugins::
 
   >>> pau.authenticatorPlugins = (
-  ...     'Searchable Authentication Plugin',
-  ...     'Searchable Authentication Plugin 2')
+  ...     'Authentication Plugin 1',
+  ...     'Authentication Plugin 2')
 
 we get a different principal for ID 'white'::
 

Added: zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py
===================================================================
--- zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py	                        (rev 0)
+++ zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py	2010-01-24 00:15:31 UTC (rev 108414)
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+
+from zope.interface import implements
+from zope.component import adapts
+from zope.event import notify
+from zope.pluggableauth import interfaces
+from zope.publisher.interfaces import IRequest
+from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
+
+
+class Principal(object):
+    """A group-aware implementation of zope.security.interfaces.IPrincipal.
+
+    A principal is created with an ID:
+
+      >>> p = Principal(1)
+      >>> p
+      Principal(1)
+      >>> p.id
+      1
+
+    title and description may also be provided:
+
+      >>> p = Principal('george', 'George', 'A site member.')
+      >>> p
+      Principal('george')
+      >>> p.id
+      'george'
+      >>> p.title
+      'George'
+      >>> p.description
+      'A site member.'
+
+    The `groups` is a simple list, filled in by plugins.
+
+      >>> p.groups
+      []
+
+    The `allGroups` attribute is a readonly iterable of the full closure of the
+    groups in the `groups` attribute--that is, if the principal is a direct
+    member of the 'Administrators' group, and the 'Administrators' group is
+    a member of the 'Reviewers' group, then p.groups would be 
+    ['Administrators'] and list(p.allGroups) would be
+    ['Administrators', 'Reviewers'].
+
+    To illustrate this, we'll need to set up a dummy authentication utility,
+    and a few principals.  Our main principal will also gain some groups, as if
+    plugins had added the groups to the list.  This is all setup--skip to the
+    next block to actually see `allGroups` in action.
+    
+      >>> p.groups.extend(
+      ...     ['content_administrators', 'zope_3_project',
+      ...      'list_administrators', 'zpug'])
+      >>> editor = Principal('editors', 'Content Editors')
+      >>> creator = Principal('creators', 'Content Creators')
+      >>> reviewer = Principal('reviewers', 'Content Reviewers')
+      >>> reviewer.groups.extend(['editors', 'creators'])
+      >>> usermanager = Principal('user_managers', 'User Managers')
+      >>> contentAdmin = Principal(
+      ...     'content_administrators', 'Content Administrators')
+      >>> contentAdmin.groups.extend(['reviewers', 'user_managers'])
+      >>> zope3Dev = Principal('zope_3_project', 'Zope 3 Developer')
+      >>> zope3ListAdmin = Principal(
+      ...     'zope_3_list_admin', 'Zope 3 List Administrators')
+      >>> zope3ListAdmin.groups.append('zope_3_project') # duplicate, but
+      ... # should only appear in allGroups once
+      >>> listAdmin = Principal('list_administrators', 'List Administrators')
+      >>> listAdmin.groups.append('zope_3_list_admin')
+      >>> zpugMember = Principal('zpug', 'ZPUG Member')
+      >>> martians = Principal('martians', 'Martians') # not in p's allGroups
+      >>> group_data = dict((p.id, p) for p in (
+      ...     editor, creator, reviewer, usermanager, contentAdmin,
+      ...     zope3Dev, zope3ListAdmin, listAdmin, zpugMember, martians))
+      >>> class DemoAuth(object):
+      ...     interface.implements(IAuthentication)
+      ...     def getPrincipal(self, id):
+      ...         return group_data[id]
+      ...
+      >>> demoAuth = DemoAuth()
+      >>> component.provideUtility(demoAuth)
+
+    Now, we have a user with the following groups (lowest level are p's direct
+    groups, and lines show membership):
+
+      editors  creators
+         \------/
+             |                                     zope_3_project (duplicate)
+          reviewers  user_managers                          |
+               \---------/                           zope_3_list_admin
+                    |                                       |
+          content_administrators   zope_3_project   list_administrators   zpug
+
+    The allGroups value includes all of the shown groups, and with
+    'zope_3_project' only appearing once.
+
+      >>> p.groups # doctest: +NORMALIZE_WHITESPACE
+      ['content_administrators', 'zope_3_project', 'list_administrators',
+       'zpug']
+      >>> list(p.allGroups) # doctest: +NORMALIZE_WHITESPACE
+      ['content_administrators', 'reviewers', 'editors', 'creators',
+       'user_managers', 'zope_3_project', 'list_administrators',
+       'zope_3_list_admin', 'zpug']
+    """
+    implements(IPrincipal)
+
+    def __init__(self, id, title=u'', description=u''):
+        self.id = id
+        self.title = title
+        self.description = description
+        self.groups = []
+
+    def __repr__(self):
+        return 'Principal(%r)' % self.id
+
+    @property
+    def allGroups(self):
+        if self.groups:
+            seen = set()
+            principals = component.getUtility(IAuthentication)
+            stack = [iter(self.groups)]
+            while stack:
+                try:
+                    group_id = stack[-1].next()
+                except StopIteration:
+                    stack.pop()
+                else:
+                    if group_id not in seen:
+                        yield group_id
+                        seen.add(group_id)
+                        group = principals.getPrincipal(group_id)
+                        stack.append(iter(group.groups))
+    
+
+class AuthenticatedPrincipalFactory(object):
+    """Creates 'authenticated' principals.
+
+    An authenticated principal is created as a result of an authentication
+    operation.
+
+    To use the factory, create it with the info (interfaces.IPrincipalInfo) of
+    the principal to create and a request:
+
+      >>> info = PrincipalInfo('users.mary', 'mary', 'Mary', 'The site admin.')
+      >>> from zope.publisher.base import TestRequest
+      >>> request = TestRequest('/')
+      >>> factory = AuthenticatedPrincipalFactory(info, request)
+
+    The factory must be called with a pluggable-authentication object:
+
+      >>> class Auth:
+      ...     prefix = 'auth.'
+      >>> auth = Auth()
+
+      >>> principal = factory(auth)
+
+    The factory uses the pluggable authentication and the info to
+    create a principal with the same ID, title, and description:
+
+      >>> principal.id
+      'auth.users.mary'
+      >>> principal.title
+      'Mary'
+      >>> principal.description
+      'The site admin.'
+
+    It also fires an AuthenticatedPrincipalCreatedEvent:
+
+      >>> from zope.component.eventtesting import getEvents
+      >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
+      >>> event.principal is principal, event.authentication is auth
+      (True, True)
+      >>> event.info
+      PrincipalInfo('users.mary')
+      >>> event.request is request
+      True
+
+    Listeners can subscribe to this event to perform additional operations
+    when the authenticated principal is created.
+
+    For information on how factories are used in the authentication process,
+    see README.txt.
+    """
+    adapts(interfaces.IPrincipalInfo, IRequest)
+    implements(interfaces.IAuthenticatedPrincipalFactory)
+
+    def __init__(self, info, request):
+        self.info = info
+        self.request = request
+
+    def __call__(self, authentication):
+        principal = Principal(authentication.prefix + self.info.id,
+                              self.info.title,
+                              self.info.description)
+        notify(interfaces.AuthenticatedPrincipalCreated(
+            authentication, principal, self.info, self.request))
+        return principal

Modified: zope.pluggableauth/trunk/src/zope/pluggableauth/interfaces.py
===================================================================
--- zope.pluggableauth/trunk/src/zope/pluggableauth/interfaces.py	2010-01-23 23:32:38 UTC (rev 108413)
+++ zope.pluggableauth/trunk/src/zope/pluggableauth/interfaces.py	2010-01-24 00:15:31 UTC (rev 108414)
@@ -170,3 +170,62 @@
         Optional.  Should be set in IPluggableAuthentication.authenticate and
         IPluggableAuthentication.getPrincipal.
         """)
+
+
+class IPrincipalFactory(zope.interface.Interface):
+    """A principal factory."""
+
+    def __call__(authentication):
+        """Creates a principal.
+
+        The authentication utility that called the factory is passed
+        and should be included in the principal-created event.
+        """
+
+
+class IFoundPrincipalFactory(IPrincipalFactory):
+    """A found principal factory."""
+
+
+class IAuthenticatedPrincipalFactory(IPrincipalFactory):
+    """An authenticated principal factory."""
+
+
+class IPrincipalCreated(zope.interface.Interface):
+    """A principal has been created."""
+
+    principal = zope.interface.Attribute("The principal that was created")
+
+    authentication = zope.interface.Attribute(
+        "The authentication utility that created the principal")
+
+    info = zope.interface.Attribute("An object providing IPrincipalInfo.")
+
+
+class IAuthenticatedPrincipalCreated(IPrincipalCreated):
+    """A principal has been created by way of an authentication operation."""
+
+    request = zope.interface.Attribute(
+        "The request the user was authenticated against")
+
+
+class AuthenticatedPrincipalCreated:
+    """
+    >>> from zope.interface.verify import verifyObject
+    >>> event = AuthenticatedPrincipalCreated("authentication", "principal",
+    ...     "info", "request")
+    >>> verifyObject(IAuthenticatedPrincipalCreated, event)
+    True
+    """
+
+    zope.interface.implements(IAuthenticatedPrincipalCreated)
+
+    def __init__(self, authentication, principal, info, request):
+        self.authentication = authentication
+        self.principal = principal
+        self.info = info
+        self.request = request
+
+
+class IQueriableAuthenticator(zope.interface.Interface):
+    """Indicates the authenticator provides a search UI for principals."""

Modified: zope.pluggableauth/trunk/src/zope/pluggableauth/tests.py
===================================================================
--- zope.pluggableauth/trunk/src/zope/pluggableauth/tests.py	2010-01-23 23:32:38 UTC (rev 108413)
+++ zope.pluggableauth/trunk/src/zope/pluggableauth/tests.py	2010-01-24 00:15:31 UTC (rev 108414)
@@ -110,24 +110,25 @@
     suite.addTest(doctest.DocTestSuite(
         'zope.pluggableauth.plugins.httpplugins'))
     
-    session = doctest.DocTestSuite('zope.pluggableauth.plugins.session',
-                                   setUp=siteSetUp,
-                                   tearDown=siteTearDown)
-    
-    suite.addTest(session)
+    suite.addTest(doctest.DocTestSuite('zope.pluggableauth.plugins.session',
+                                       setUp=siteSetUp,
+                                       tearDown=siteTearDown))
+
+    suite.addTest(
+        doctest.DocFileSuite('README.txt',
+                            setUp=siteSetUp,
+                             tearDown=siteTearDown,
+                             globs={'provideUtility': provideUtility,
+                                    'provideAdapter': provideAdapter,
+                                    'provideHandler': provideHandler,
+                                    'getEvents': getEvents,
+                                    'clearEvents': clearEvents,
+                                    }))
+
     suite.addTest(unittest.makeSuite(NonHTTPSessionTestCase))
-    
     return suite
 
-        #doctest.DocFileSuite('README.txt',
-        #                     setUp=siteSetUp,
-        #                     tearDown=siteTearDown,
-        #                     globs={'provideUtility': provideUtility,
-        #                            'provideAdapter': provideAdapter,
-        #                            'provideHandler': provideHandler,
-        #                            'getEvents': getEvents,
-        #                            'clearEvents': clearEvents,
-        #                            }),
+ 
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')



More information about the checkins mailing list