[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/pas/ Reimplemented the persistent (ZODB) authentication and search plugin. We

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Oct 13 17:46:32 EDT 2004


Log message for revision 28138:
  Reimplemented the persistent (ZODB) authentication and search plugin. We 
  now support the full IContainer interface and use real internal principal 
  classes. Improved the UI and make sure it works with PAS.
  
  

Changed:
  U   Zope3/trunk/src/zope/app/pas/authenticationplugins.zcml
  U   Zope3/trunk/src/zope/app/pas/browser/configure.zcml
  D   Zope3/trunk/src/zope/app/pas/browser/zodb.py
  U   Zope3/trunk/src/zope/app/pas/zodb.py

-=-
Modified: Zope3/trunk/src/zope/app/pas/authenticationplugins.zcml
===================================================================
--- Zope3/trunk/src/zope/app/pas/authenticationplugins.zcml	2004-10-13 21:33:12 UTC (rev 28137)
+++ Zope3/trunk/src/zope/app/pas/authenticationplugins.zcml	2004-10-13 21:46:30 UTC (rev 28138)
@@ -3,9 +3,16 @@
     xmlns:browser="http://namespaces.zope.org/browser"
     i18n_domain="zope"
     >
-  
-  <localUtility class=".zodb.PersistentPrincipalStorage">
+  <content class=".zodb.InternalPrincipal">
+    <require
+        permission="zope.ManageServices"
+        interface=".zodb.IInternalPrincipal"
+        set_schema=".zodb.IInternalPrincipal" 
+        />
+  </content>
 
+  <localUtility class=".zodb.PersistentPrincipalFolder">
+
     <implements
         interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
 
@@ -15,10 +22,16 @@
 
   </localUtility>
 
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for=".zodb.IInternalPrincipalContainer"
+      factory=".zodb.NameChooser"
+      />
+
   <browser:addMenuItem
-      title="PAS Authentication Plugin"
-      description="A PAS Authentication Plugin"
-      class="zope.app.pas.zodb.PersistentPrincipalStorage"
+      title="PAS Persistent Authentication Plugin"
+      description="A PAS Persistent Authentication Plugin"
+      class="zope.app.pas.zodb.PersistentPrincipalFolder"
       permission="zope.ManageServices"
       />
       

Modified: Zope3/trunk/src/zope/app/pas/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/configure.zcml	2004-10-13 21:33:12 UTC (rev 28137)
+++ Zope3/trunk/src/zope/app/pas/browser/configure.zcml	2004-10-13 21:46:30 UTC (rev 28138)
@@ -8,19 +8,40 @@
       template="loginform.pt"
       permission="zope.Public" />
 
-  <pages 
-      permission="zope.ManageServices" 
-      for="..zodb.IPersistentPrincipalStorage"
-      class=".zodb.PrincipalManagement">
-    
-    <page name="editForm.html" template="zodb_overview.pt"
-          menu="zmi_views" title="Managament" />
-    <page name="add.html" attribute="add" />
-    <page name="delete.html" attribute="delete" />
 
-  </pages>
+  <addform
+      schema="..zodb.IInternalPrincipal"
+      label="Add Internal Principal"
+      content_factory="..zodb.InternalPrincipal"
+      arguments="id login password title"
+      keyword_arguments="description"
+      name="AddInternalPrincipalForm.html"
+      permission="zope.ManageServices"
+      />
 
+  <addMenuItem
+      title="Persistent Principal" 
+      class="..zodb.InternalPrincipal"
+      permission="zope.ManageServices"
+      view="AddInternalPrincipalForm.html"
+      />
+
   <editform
+      schema="..zodb.IInternalPrincipal"
+      label="Change Internal Principal"
+      name="edit.html"
+      fields="login password title description"
+      permission="zope.ManageServices"
+      menu="zmi_views" title="Edit" />
+
+  <containerViews
+      for="..zodb.IInternalPrincipalContainer"
+      add="zope.ManageServices"
+      contents="zope.ManageServices"
+      index="zope.ManageServices"
+      />
+
+  <editform
       schema="..httpplugins.IHTTPBasicAuthRealm"
       label="Change Realm"
       name="edit.html"

Deleted: Zope3/trunk/src/zope/app/pas/browser/zodb.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/zodb.py	2004-10-13 21:33:12 UTC (rev 28137)
+++ Zope3/trunk/src/zope/app/pas/browser/zodb.py	2004-10-13 21:46:30 UTC (rev 28138)
@@ -1,39 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-"""Persistent Principal Storage and Authentication Plugin Views
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-
-
-class PrincipalManagement(object):
-    """ """
-
-    def add(self, login, password):
-        if len(self.context) >= 1:
-            id = max(self.context.keys())+1
-        else:
-            id = 1
-        self.context[id] = (login, password)
-        self.request.response.redirect('@@editForm.html')
-
-    def delete(self, ids):
-        for id in ids:
-            del self.context[int(id)]
-        self.request.response.redirect('@@editForm.html')
-
-    def list(self):
-        return [{'id': id, 'login': principal[0]}
-                for id, principal in self.context.items()]

Modified: Zope3/trunk/src/zope/app/pas/zodb.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/zodb.py	2004-10-13 21:33:12 UTC (rev 28137)
+++ Zope3/trunk/src/zope/app/pas/zodb.py	2004-10-13 21:46:30 UTC (rev 28138)
@@ -17,186 +17,197 @@
 """
 __docformat__ = "reStructuredText"
 from persistent import Persistent
-from BTrees.IOBTree import IOBTree
-from BTrees.OIBTree import OIBTree
+from BTrees.OOBTree import OOBTree
+from UserDict import DictMixin
 
-from zope.interface import implements
+from zope.interface import implements, Interface
+from zope.schema import Text, TextLine, Password, Field
 
+from zope.app.container.btree import BTreeContainer
+from zope.app.container.contained import Contained
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.container.constraints import ContainerTypesConstraint
 from zope.app.container.interfaces import IContainer
-from zope.app.container.contained import Contained
+from zope.app.container.interfaces import INameChooser
+from zope.app.exception.interfaces import UserError
+from zope.app.i18n import ZopeMessageIDFactory as _
 
 import interfaces
 
+class IInternalPrincipal(Interface):
+    """Principal information"""
 
-class IPersistentPrincipalStorage(IContainer):
-    """Marker Interface"""
+    id = TextLine(
+        title=_("Id"),
+        description=_("Id as which this principal will be known and used."),
+        readonly=True,
+        required=True)
 
-class PersistentPrincipalStorage(Persistent, Contained):
-    """A Persistent Principal Storage and Authentication plugin
+    login = TextLine(
+        title=_("Login"),
+        description=_("The Login/Username of the principal. "
+                      "This value can change."),
+        required=True)
 
-    Whenever the following code refers to `principal`, we mean a tuple of the
-    form (login, password). Since we try not to expose the password, password
-    is always `None` in any output.
-    """
-    implements(interfaces.IAuthenticationPlugin, IPersistentPrincipalStorage)
+    password = Password(
+        title=_(u"Password"),
+        description=_("The password for the principal."),
+        required=True)
 
-    def __init__(self):
-        self._principal_by_id = IOBTree()
-        self._id_by_login = OIBTree()
+    title = TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the principal."),
+        required=True)
 
-    def __setitem__(self, id, principal):
-        """ See `IContainerNamesContainer`
+    description = Text(
+        title=_("Description"),
+        description=_("Provides a description for the principal."),
+        required=False)
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> pps[1] = ('foo', 'bar')
-        >>> pps._principal_by_id[1]
-        ('foo', 'bar')
-        >>> pps._id_by_login['foo']
-        1
-        """
-        self._principal_by_id[id] = principal
-        self._id_by_login[principal[0]] = id
 
-    def __delitem__(self, id):
-        """ See `IContainer`.
+class IInternalPrincipalContainer(Interface):
+    """A container that contains internal principals."""
+    
+    def __setitem__(id, principal_source):
+        """Add to object"""
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> pps[1] = ('foo', 'bar')
-        >>> del pps[1]
-        >>> pps[1]
-        Traceback (most recent call last):
-        ...
-        KeyError: 1
-        """
-        login = self._principal_by_id[id][0]
-        del self._principal_by_id[id]
-        del self._id_by_login[login]
+    __setitem__.precondition = ItemTypePrecondition(IInternalPrincipal)
 
-    def keys(self):
-        """ See `IContainer`.
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> list(pps.keys())
-        []
-        >>> pps[1] = ('foo', 'bar')
-        >>> list(pps.keys())
-        [1]
-        >>> del pps[1]
-        >>> list(pps.keys())
-        []
-        """
-        return self._principal_by_id.keys()
+class IInternalPrincipalContained(Interface):
+    """Principal information"""
 
-    def __iter__(self):
-        """ See `IContainer`.
+    __parent__= Field(
+        constraint = ContainerTypesConstraint(IInternalPrincipalContainer))
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> list(pps.keys())
-        []
-        >>> pps[1] = ('foo', 'bar')
-        >>> pps[2] = ('blah', 'baz')
-        >>> ids = [i for i in pps]
-        >>> ids.sort()
-        >>> ids
-        [1, 2]
-        """
-        return iter(self.keys())
 
-    def __getitem__(self, id):
-        """ See `IContainer`
+class ISearchSchema(Interface):
+    """Search Interface for this Principal Provider"""
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> pps[1] = ('foo', 'bar')
+    search = TextLine(
+        title=_("Search String"),
+        description=_("A Search String"),
+        required=False,
+        default=u'')
 
-        Never expose the password!
+class InternalPrincipal(Persistent, Contained, DictMixin):
+    """An internal principal for Persistent Principal Folder.
 
-        >>> pps[1]
-        ('foo', None)
-        """
-        try:
-            return self._principal_by_id[id][0], None
-        except TypeError:
-            # We were not passed an integer id, for instance
-            # because traversal to a view was attempted.
-            raise KeyError, id
+    Make sure the folder gets notified for login changes.
 
-    def get(self, id, default=None):
-        """ See `IContainer`
+    >>> class Folder:
+    ...     def notifyLoginChanged(self, old, principal):
+    ...         self.old = old
+    ...         self.principal = principal
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> marker = object()
-        >>> pps.get(1, default=marker) is marker
-        True
-        >>> pps[1] = ('foo', 'bar')
+    >>> folder = Folder()
+    >>> principal = InternalPrincipal('1', 'foo', 'bar', 'Foo Bar')
+    >>> principal.__parent__ = folder
 
-        Never expose the password!
+    >>> principal.login = 'blah'
 
-        >>> pps.get(1)
-        ('foo', None)
-        """
-        try:
-            return self[id]
-        except KeyError:
-            return default
+    >>> folder.old
+    'foo'
+    >>> folder.principal is principal
+    True
+    """
+    implements(IInternalPrincipal, IInternalPrincipalContained)
 
-    def values(self):
-        """ See `IContainer`.
+    def __init__(self, id, login, password, title, description=u''):
+        self.id = id
+        self._login = login
+        self.password = password
+        self.title = title
+        self.description = description
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> pps.values()
-        []
-        >>> pps[1] = ('foo', 'bar')
-        >>> pps.values()
-        [('foo', None)]
-        """
-        return [self[id] for id in self]
+    def getLogin(self):
+        return self._login
 
-    def __len__(self):
-        """ See `IContainer`
+    def setLogin(self, login):
+        oldLogin = self._login
+        self._login = login
+        if self.__parent__ is not None:
+            self.__parent__.notifyLoginChanged(oldLogin, self)
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> len(pps)
-        0
-        >>> pps[1] = ('foo', 'bar')
-        >>> len(pps)
-        1
+    login = property(getLogin, setLogin)
+
+    def __getitem__(self, attr):
+        if attr in ('title', 'description'):
+            return getattr(self, attr)
+
+    
+        
+
+
+class PersistentPrincipalFolder(BTreeContainer):
+    """A Persistent Principal Folder and Authentication plugin
+
+    Whenever the following code refers to `principal`, we mean a tuple of the
+    form (login, password). Since we try not to expose the password, password
+    is always `None` in any output.
+    """
+    implements(interfaces.IAuthenticationPlugin,
+               interfaces.IQuerySchemaSearch,
+               IInternalPrincipalContainer)
+
+    def __init__(self):
+        super(PersistentPrincipalFolder, 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.
         """
-        return len(self._id_by_login)
+        # A user with the new login already exists
+        if principal.login in self.__id_by_login:
+            raise ValueError, 'Principal Login already taken!'
 
-    def items(self):
-        """ See `IContainer`.
+        del self.__id_by_login[oldLogin]
+        self.__id_by_login[principal.login] = principal.id
+        
+    def __setitem__(self, id, principal):
+        """ See `IContainerNamesContainer`
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> pps.items()
-        []
-        >>> pps[1] = ('foo', 'bar')
-        >>> pps.items()
-        [(1, ('foo', None))]
+        >>> pps = PersistentPrincipalFolder()
+        >>> principal = InternalPrincipal('1', 'foo', 'bar', u'Foo Bar')
+        >>> pps['1'] = principal
+        >>> pps['1'] is principal
+        True
+        >>> pps._PersistentPrincipalFolder__id_by_login['foo']
+        '1'
         """
-        return [(id, self[id]) for id in self]
+        principal.id = id
+        # A user with the new login already exists
+        if principal.login in self.__id_by_login:
+            raise ValueError, 'Principal Login already taken!'
 
-    def __contains__(self, id):
+        super(PersistentPrincipalFolder, self).__setitem__(id, principal)
+        self.__id_by_login[principal.login] = id
+
+    def __delitem__(self, id):
         """ See `IContainer`.
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> 1 in pps
-        False
-        >>> pps[1] = ('foo', 'bar')
-        >>> 1 in pps
-        True
+        >>> pps = PersistentPrincipalFolder()
+        >>> pps['1'] = InternalPrincipal('1', 'foo', 'bar', u'Foo Bar')
+        >>> del pps['1']
+        >>> pps['1']
+        Traceback (most recent call last):
+        ...
+        KeyError: '1'
         """
-        return id in self.keys()
+        principal = self[id]
+        super(PersistentPrincipalFolder, self).__delitem__(id)
+        del self.__id_by_login[principal.login]
 
-    has_key = __contains__
 
-
     def authenticateCredentials(self, credentials):
         """See zope.app.pas.interfaces.IAuthenticationPlugin
 
         Create an authentication plugin and add a principal to it.
 
-        >>> pps = PersistentPrincipalStorage()
-        >>> pps[1] = ('foo', 'bar')
+        >>> pps = PersistentPrincipalFolder()
+        >>> pps['1'] = InternalPrincipal('1', 'foo', 'bar', u'Foo Bar')
 
         >>> pps.authenticateCredentials(1) is None
         True
@@ -212,8 +223,12 @@
         >>> pps.authenticateCredentials({'login': 'foo',
         ...                              'password': 'bar1'}) is None
         True
-        >>> pps.authenticateCredentials({'login': 'foo', 'password': 'bar'})
-        ('1', {'login': 'foo'})
+        >>> res=pps.authenticateCredentials({'login': 'foo', 'password': 'bar'})
+
+        >>> import pprint
+        >>> pp = pprint.PrettyPrinter(width=65)
+        >>> pp.pprint(res)
+        ('1', {'login': 'foo', 'description': u'', 'title': u'Foo Bar'})
         """
         if not isinstance(credentials, dict):
             return None
@@ -221,11 +236,79 @@
         if not ('login' in credentials and 'password' in credentials):
             return None
 
-        id = self._id_by_login.get(credentials['login'])
+        id = self.__id_by_login.get(credentials['login'])
         if id is None:
             return None
 
-        if self._principal_by_id[id][1] != credentials['password']:
+        principal = self[id]
+        if principal.password != credentials['password']:
             return None
 
-        return str(id), {'login': credentials['login']}
+        return id, {'login': principal.login, 'title': principal.title,
+                    'description': principal.description}
+
+    schema = ISearchSchema
+
+    def search(self, query, start=None, batch_size=None):
+        """Search through this principal provider.
+
+        >>> pps = PersistentPrincipalFolder()
+        >>> pps['1'] = InternalPrincipal('1', 'foo1', 'bar', u'Foo Bar 1')
+        >>> pps['2'] = InternalPrincipal('2', 'foo2', 'bar', u'Foo Bar 2')
+        >>> pps['3'] = InternalPrincipal('3', 'foo3', 'bar', u'Foo Bar 3')
+
+        >>> list(pps.search({'search': 'foo'}))
+        ['1', '2', '3']
+        >>> list(pps.search({'search': '1'}))
+        ['1']
+
+        >>> list(pps.search({'search': 'foo'}, 1))
+        ['2', '3']
+        >>> list(pps.search({'search': 'foo'}, 1, 1))
+        ['2']
+        """
+        search = query.get('search', u'') or u''
+        i = 0
+        n = 1
+        for value in self.values():
+            if (search in value.title or
+                search in value.description or
+                search in value.login):
+                if not ((start is not None and i < start)
+                        or
+                        (batch_size is not None and n > batch_size)):
+                    n += 1
+                    yield value.id
+                i += 1
+        
+
+class NameChooser(object):
+    """A name chosser for the principal provider.
+
+    >>> folder = PersistentPrincipalFolder()
+    >>> chooser = NameChooser(folder)
+
+    >>> folder['test'] = InternalPrincipal('test', 'foo', 'bar', 'Foo Bar')
+
+    >>> chooser.checkName('test1', None)
+    'test1'
+    >>> chooser.checkName('test', None)
+    Traceback (most recent call last):
+    ...
+    UserError: Name already taken.
+
+    >>> chooser.chooseName('', InternalPrincipal('foo', 'foo', 'bar', ''))
+    'foo'
+    """
+    implements(INameChooser)
+
+    def __init__(self, container):
+        self.container = container
+
+    def checkName(self, name, object):
+        if name in self.container:
+            raise UserError, 'Name already taken.'
+        return name
+
+    def chooseName(self, name, object):
+        return object.id



More information about the Zope3-Checkins mailing list