[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/authentication/ Adapting a principal-aware authenticator to a delegator that knows the PAU prefix and prepends it to principal ID search results. This addresses the bug when searching for principals in a PAU that has a prefix.

Garrett Smith garrett at mojave-corp.com
Fri Jul 29 17:14:34 EDT 2005


Log message for revision 37571:
  Adapting a principal-aware authenticator to a delegator that knows the PAU prefix and prepends it to principal ID search results. This addresses the bug when searching for principals in a PAU that has a prefix.
  
  Also added a UI for specifying a prefix when adding a PAU.
  
  Delete IQueriableAuthenticator because it's now WHUI.

Changed:
  U   Zope3/trunk/src/zope/app/authentication/README.txt
  U   Zope3/trunk/src/zope/app/authentication/authentication.py
  U   Zope3/trunk/src/zope/app/authentication/browser/configure.zcml
  U   Zope3/trunk/src/zope/app/authentication/browser/principalfolder.zcml
  U   Zope3/trunk/src/zope/app/authentication/configure.zcml
  U   Zope3/trunk/src/zope/app/authentication/groupfolder.py
  U   Zope3/trunk/src/zope/app/authentication/interfaces.py
  U   Zope3/trunk/src/zope/app/authentication/principalfolder.py

-=-
Modified: Zope3/trunk/src/zope/app/authentication/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/README.txt	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/README.txt	2005-07-29 21:14:34 UTC (rev 37571)
@@ -117,7 +117,7 @@
 Finally, we'll create the PAU itself:
 
   >>> from zope.app import authentication
-  >>> pau = authentication.PluggableAuthentication()
+  >>> pau = authentication.PluggableAuthentication('xyz_')
 
 and configure it with the two plugins:
 
@@ -144,7 +144,7 @@
   >>> request = TestRequest(credentials='secretcode')
   >>> principal = pau.authenticate(request)
   >>> principal
-  Principal('bob')
+  Principal('xyz_bob')
 
 we get an authenticated principal.
 
@@ -203,7 +203,7 @@
 Then it will be given the first opportunity to authenticate a request:
 
   >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('black')
+  Principal('xyz_black')
 
 If neither plugins can authenticate, pau returns None:
 
@@ -219,12 +219,12 @@
 we see that our original plugin is now acting first:
 
   >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('bob')
+  Principal('xyz_bob')
 
 The second plugin, however, gets a chance to authenticate if first does not:
 
   >>> pau.authenticate(TestRequest(credentials='hiddenkey'))
-  Principal('white')
+  Principal('xyz_white')
 
 Multiple Credentials Plugins
 ----------------------------
@@ -260,7 +260,7 @@
 
   >>> pau.authenticate(TestRequest(credentials='secretcode',
   ...                              form={'my_credentials': 'hiddenkey'}))
-  Principal('white')
+  Principal('xyz_white')
 
 In this case, the first credentials plugin succeeded in getting credentials
 from the form and the second authenticator was able to authenticate the
@@ -279,7 +279,7 @@
 Let's try a different scenario:
 
   >>> pau.authenticate(TestRequest(credentials='secretcode'))
-  Principal('bob')
+  Principal('xyz_bob')
 
 In this case, the PAU went through these steps:
 
@@ -297,7 +297,7 @@
 
   >>> pau.authenticate(TestRequest(credentials='hiddenkey',
   ...                              form={'my_credentials': 'bogusvalue'}))
-  Principal('white')
+  Principal('xyz_white')
 
 This highlights PAU's ability to use multiple plugins for authentication:
 
@@ -333,7 +333,7 @@
 its authenticators. In out example, none of the authenticators implement this
 search capability, so when we look for a principal:
 
-  >>> print pau.getPrincipal('bob')
+  >>> print pau.getPrincipal('xyz_bob')
   Traceback (most recent call last):
   PrincipalLookupError: 'bob'
 
@@ -390,8 +390,8 @@
 
 Now when we ask the PAU to find a principal:
 
-  >>> pau.getPrincipal('bob')
-  Principal('bob')
+  >>> pau.getPrincipal('xyz_bob')
+  Principal('xyz_bob')
 
 but only those it knows about:
 
@@ -407,9 +407,9 @@
 principal on behalf of PAU's 'getPrincipal':
 
   >>> clearEvents()
-  >>> principal = pau.getPrincipal('white')
+  >>> principal = pau.getPrincipal('xyz_white')
   >>> principal
-  Principal('white')
+  Principal('xyz_white')
 
   >>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
   >>> event.principal is principal
@@ -451,18 +451,18 @@
 
 we see how the PAU uses both plugins:
 
-  >>> pau.getPrincipal('white')
-  Principal('white')
+  >>> pau.getPrincipal('xyz_white')
+  Principal('xyz_white')
 
-  >>> pau.getPrincipal('black')
-  Principal('black')
+  >>> pau.getPrincipal('xyz_black')
+  Principal('xyz_black')
 
 If more than one plugin know about the same principal ID, the first plugin is
 used and the remaining are not delegated to. To illustrate, we'll add
 another principal with the same ID as an existing principal:
 
   >>> searchable2.add('white', 'White Rider', '', 'r1der')
-  >>> pau.getPrincipal('white').title
+  >>> pau.getPrincipal('xyz_white').title
   'White Rider'
 
 If we change the order of the plugins:
@@ -473,7 +473,7 @@
 
 we get a different principal for ID 'white':
 
-  >>> pau.getPrincipal('white').title
+  >>> pau.getPrincipal('xyz_white').title
   'White Spy'
 
 
@@ -653,7 +653,7 @@
 PAU implements ISourceQueriables:
 
   >>> from zope.schema.interfaces import ISourceQueriables
-  >>> ISourceQueriables.implementedBy(authentication.PluggableAuthentication)
+  >>> ISourceQueriables.providedBy(pau)
   True
 
 This means a PAU can be used in a principal source vocabulary (Zope provides a
@@ -661,7 +661,8 @@
 
 As we've seen, a PAU uses each of its authenticator plugins to locate a
 principal with a given ID. However, plugins may also provide the interface
-IQueriableAuthenticator to indicate they can be used as PAU 'queriables'.
+IQuerySchemaSearch to indicate they can be used in the PAU's principal search
+scheme.
 
 Currently, our list of authenticators:
 
@@ -674,18 +675,52 @@
   >>> list(pau.getQueriables())
   []
 
-If we install a queriable plugin:
+Before we illustrate how an authenticator is used by the PAU to search for
+principals, we need to setup an adapter used by PAU:
 
+  >>> provideAdapter(
+  ...     authentication.authentication.PluggableAuthenticationQueriable,
+  ...     provides=interfaces.IQuerySchemaSearch)
+
+This adapter delegates search responsibility to an authenticator, but prepends
+the PAU prefix to any principal IDs returned in a search.
+
+Next, we'll create a plugin that provides a search interface:
+
   >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin):
   ...
-  ...     interface.implements(interfaces.IQueriableAuthenticator)
+  ...     interface.implements(interfaces.IQuerySchemaSearch)
   ...
-  >>> provideUtility(QueriableAuthenticatorPlugin(),
+  ...     schema = None
+  ...
+  ...     def search(self, query, start=None, batch_size=None):
+  ...         yield 'foo'
+  ...
+
+and install it as a plugin:
+
+  >>> plugin = QueriableAuthenticatorPlugin()
+  >>> provideUtility(plugin,
   ...                provides=interfaces.IAuthenticatorPlugin,
   ...                name='Queriable')
   >>> pau.authenticatorPlugins += ('Queriable',)
 
-the PAU will provide it as a queriable:
+Now, the PAU provides a single queriable:
 
   >>> list(pau.getQueriables()) # doctest: +ELLIPSIS
-  [('Queriable', ...QueriableAuthenticatorPlugin object...)]
+  [('Queriable', ...PluggableAuthenticationQueriable object...)]
+
+We can use this queriable to search for our principal:
+
+  >>> queriable = list(pau.getQueriables())[0][1]
+  >>> list(queriable.search('not-used'))
+  ['mypau_foo']
+
+Note that the resulting principal ID includes the PAU prefix. Were we to sarch
+the plugin directly:
+
+  >>> list(plugin.search('not-used'))
+  ['foo']
+
+The result does not include the PAU prefix. The prepending of the prefix is
+handled by the PluggableAuthenticationQueriable.

Modified: Zope3/trunk/src/zope/app/authentication/authentication.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/authentication.py	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/authentication.py	2005-07-29 21:14:34 UTC (rev 37571)
@@ -22,6 +22,7 @@
 from zope import component
 from zope.schema.interfaces import ISourceQueriables
 from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
+from zope.app.location.interfaces import ILocation
 from zope.app.component import queryNextUtility
 from zope.app.component.site import SiteManagementFolder
 
@@ -67,7 +68,9 @@
     def getPrincipal(self, id):
         if not id.startswith(self.prefix):
             next = queryNextUtility(self, IAuthentication)
-            return (next is not None) and next.getPrincipal(id) or None
+            if next is None:
+                raise PrincipalLookupError(id)
+            return next.getPrincipal(id)
         id = id[len(self.prefix):]
         for name in self.authenticatorPlugins:
             authplugin = component.queryUtility(
@@ -87,15 +90,14 @@
 
     def getQueriables(self):
         for name in self.authenticatorPlugins:
-            authplugin = component.queryUtility(
-                interfaces.IAuthenticatorPlugin,
+            authplugin = component.queryUtility(interfaces.IAuthenticatorPlugin,
                 name, context=self)
             if authplugin is None:
                 continue
-            queriable = interfaces.IQueriableAuthenticator(authplugin, None)
-            if queriable is None:
-                continue
-            yield name, queriable
+            queriable = component.queryMultiAdapter((authplugin, self),
+                interfaces.IQuerySchemaSearch)
+            if queriable is not None:
+                yield name, queriable
 
     def unauthenticatedPrincipal(self):
         return None
@@ -141,3 +143,27 @@
             next = queryNextUtility(self, IAuthentication)
             if next is not None:
                 next.logout(request)
+
+
+class PluggableAuthenticationQueriable(object):
+    """Performs principal searches on behald of a PAU.
+
+    Delegates the search to the authenticator but prepends the PAU prefix to
+    the resulting principal IDs.
+    """
+    component.adapts(
+        interfaces.IQuerySchemaSearch,
+        interfaces.IPluggableAuthentication)
+
+    zope.interface.implements(interfaces.IQuerySchemaSearch, ILocation)
+
+    def __init__(self, queriable, pau):
+        self.__parent__ = pau
+        self.__name__ = ''
+        self.queriable = queriable
+        self.pau = pau
+        self.schema = queriable.schema
+
+    def search(self, query, start=None, batch_size=None):
+        for id in self.queriable.search(query, start, batch_size):
+            yield self.pau.prefix + id

Modified: Zope3/trunk/src/zope/app/authentication/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/configure.zcml	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/browser/configure.zcml	2005-07-29 21:14:34 UTC (rev 37571)
@@ -1,10 +1,29 @@
-<zope:configure 
+<zope:configure
     xmlns:zope="http://namespaces.zope.org/zope"
     xmlns="http://namespaces.zope.org/browser"
     i18n_domain="zope">
 
+  <addform
+      schema="..interfaces.IPluggableAuthentication"
+      label="Add Pluggable Authentication"
+      content_factory="..authentication.PluggableAuthentication"
+      fields="prefix"
+      keyword_arguments="prefix"
+      name="AddPluggableAuthentication.html"
+      permission="zope.ManageServices">
+
+      <widget
+          field="prefix"
+          class="zope.app.form.browser.TextWidget"
+          required="False"
+          convert_missing_value="False"
+          />
+
+  </addform>
+
   <addMenuItem
        class="..authentication.PluggableAuthentication"
+       view="AddPluggableAuthentication.html"
        title="Pluggable Authentication Utility"
        description="New-style pluggable authentication utility"
        permission="zope.ManageServices"
@@ -14,23 +33,23 @@
       schema="..interfaces.IPluggableAuthentication"
       label="Edit Pluggable Authentication Utility"
       name="configure.html"
-      fields="credentialsPlugins authenticatorPlugins"
+      fields="prefix credentialsPlugins authenticatorPlugins"
       menu="zmi_views" title="Configure"
       permission="zope.ManageServices" />
-      
+
   <page
       name="plugins.html"
       for="..interfaces.IPluggableAuthentication"
       menu="zmi_views" title="Plugins"
       permission="zope.ManageSite"
       class="zope.app.container.browser.contents.Contents"
-      attribute="contents" 
+      attribute="contents"
       />
 
-  <menuItem 
+  <menuItem
       menu="zmi_views"
       for="..interfaces.IPluggableAuthentication"
-      title="Contents" 
+      title="Contents"
       action=""
       filter="python:False" />
 
@@ -70,6 +89,13 @@
       fields="name component status permission"
       />
 
+  <zope:adapter
+      for="..interfaces.IQuerySchemaSearch
+           zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.app.form.browser.interfaces.ISourceQueryView"
+      factory=".schemasearch.QuerySchemaSearchView"
+      />
+
   <tool
       interface="..interfaces.ICredentialsPlugin"
       title="Credentials Plugin"

Modified: Zope3/trunk/src/zope/app/authentication/browser/principalfolder.zcml
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/principalfolder.zcml	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/browser/principalfolder.zcml	2005-07-29 21:14:34 UTC (rev 37571)
@@ -59,11 +59,4 @@
       permission="zope.ManageServices"
       menu="zmi_views" title="Prefix" />
 
-  <zope:adapter
-      for="..interfaces.IQuerySchemaSearch
-           zope.publisher.interfaces.browser.IBrowserRequest"
-      provides="zope.app.form.browser.interfaces.ISourceQueryView"
-      factory=".schemasearch.QuerySchemaSearchView"
-      />
-
 </zope:configure>

Modified: Zope3/trunk/src/zope/app/authentication/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/authentication/configure.zcml	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/configure.zcml	2005-07-29 21:14:34 UTC (rev 37571)
@@ -16,6 +16,10 @@
         />
   </localUtility>
 
+  <adapter
+    provides=".interfaces.IQuerySchemaSearch"
+    factory=".authentication.PluggableAuthenticationQueriable" />
+
   <!-- This explicit declaration is needed indirectly by vocabulary to make
     the interface available as an IInterface utility. This is bogus...the
     vocabulary directive should make sure this registration happens. -->

Modified: Zope3/trunk/src/zope/app/authentication/groupfolder.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/groupfolder.py	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/groupfolder.py	2005-07-29 21:14:34 UTC (rev 37571)
@@ -121,7 +121,6 @@
 
     interface.implements(
         interfaces.IAuthenticatorPlugin,
-        interfaces.IQueriableAuthenticator,
         interfaces.IQuerySchemaSearch,
         IGroupFolder)
 

Modified: Zope3/trunk/src/zope/app/authentication/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/interfaces.py	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/interfaces.py	2005-07-29 21:14:34 UTC (rev 37571)
@@ -187,23 +187,21 @@
         self.info = info
 
 
-class IQueriableAuthenticator(zope.interface.Interface):
-    """Indicates the authenticator provides a search UI for principals."""
-
-
 class IQuerySchemaSearch(zope.interface.Interface):
-    """The schema used to search for principals."""
+    """An interface for searching using schema-constrained input."""
 
-    schema = zope.interface.Attribute("""Search Schema
+    schema = zope.interface.Attribute("""
+        The schema that constrains the input provided to the search method.
 
-        A schema specifying search parameters.
+        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):
-        """Search for principals.
+        """Returns an iteration of principal IDs matching the query.
 
-        The query argument is a mapping object with items defined by
-        the plugins.  An iterable of principal ids should be returned.
+        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

Modified: Zope3/trunk/src/zope/app/authentication/principalfolder.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/principalfolder.py	2005-07-29 21:14:20 UTC (rev 37570)
+++ Zope3/trunk/src/zope/app/authentication/principalfolder.py	2005-07-29 21:14:34 UTC (rev 37571)
@@ -66,7 +66,6 @@
         description=_(
         "Prefix to be added to all principal ids to assure "
         "that all ids are unique within the authentication service"),
-        required=False,
         missing_value=u"",
         default=u'',
         readonly=True)
@@ -159,7 +158,6 @@
     """
 
     interface.implements(interfaces.IAuthenticatorPlugin,
-                         interfaces.IQueriableAuthenticator,
                          interfaces.IQuerySchemaSearch,
                          IInternalPrincipalContainer)
 



More information about the Zope3-Checkins mailing list