[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/pas/ Restructured
principal folders:
Jim Fulton
jim at zope.com
Sun Oct 31 14:57:07 EST 2004
Log message for revision 28305:
Restructured principal folders:
- renamed from zodb to principalfoler
- No longer collect (and ignore) ids in input forms.
- Provided better automatic naming
- Redid tests as documentation
- Provided a functional test
Changed:
U Zope3/trunk/src/zope/app/pas/authenticationplugins.zcml
U Zope3/trunk/src/zope/app/pas/browser/configure.zcml
A Zope3/trunk/src/zope/app/pas/browser/ftests.py
A Zope3/trunk/src/zope/app/pas/browser/principalfolder.txt
A Zope3/trunk/src/zope/app/pas/idpicker.py
A Zope3/trunk/src/zope/app/pas/idpicker.txt
A Zope3/trunk/src/zope/app/pas/principalfolder.py
A Zope3/trunk/src/zope/app/pas/principalfolder.txt
U Zope3/trunk/src/zope/app/pas/tests.py
D 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-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/authenticationplugins.zcml 2004-10-31 18:01:29 UTC (rev 28305)
@@ -3,15 +3,16 @@
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="zope"
>
- <content class=".zodb.InternalPrincipal">
+
+ <content class=".principalfolder.PrincipalInformation">
<require
permission="zope.ManageServices"
- interface=".zodb.IInternalPrincipal"
- set_schema=".zodb.IInternalPrincipal"
+ interface=".principalfolder.IInternalPrincipal"
+ set_schema=".principalfolder.IInternalPrincipal"
/>
</content>
- <localUtility class=".zodb.PersistentPrincipalFolder">
+ <localUtility class=".principalfolder.PrincipalFolder">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
@@ -24,19 +25,11 @@
<adapter
provides="zope.app.container.interfaces.INameChooser"
- for=".zodb.IInternalPrincipalContainer"
- factory=".zodb.NameChooser"
+ for=".principalfolder.IInternalPrincipalContainer"
+ factory=".idpicker.IdPicker"
/>
-
- <browser:addMenuItem
- title="PAS Persistent Authentication Plugin"
- description="A PAS Persistent Authentication Plugin"
- class="zope.app.pas.zodb.PersistentPrincipalFolder"
- permission="zope.ManageServices"
- />
-
- <localUtility class=".sql.SQLAuthenticationPlugin">
+ <localUtility class=".sql.SQLAuthenticationPlugin">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
Modified: Zope3/trunk/src/zope/app/pas/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/configure.zcml 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/browser/configure.zcml 2004-10-31 18:01:29 UTC (rev 28305)
@@ -6,28 +6,45 @@
name="loginForm.html"
for="*"
template="loginform.pt"
- permission="zope.Public" />
+ permission="zope.Public"
+ />
+ <addform
+ schema="..principalfolder.IInternalPrincipalContainer"
+ label="Add Principal Folder"
+ content_factory="..principalfolder.PrincipalFolder"
+ keyword_arguments="prefix"
+ name="AddPrincipalFolder.html"
+ permission="zope.ManageServices"
+ />
+ <addMenuItem
+ title="Principal Folder"
+ description="A PAS Persistent Authentication Plugin"
+ class="zope.app.pas.principalfolder.PrincipalFolder"
+ permission="zope.ManageServices"
+ view="AddPrincipalFolder.html"
+ />
+
<addform
- schema="..zodb.IInternalPrincipal"
- label="Add Internal Principal"
- content_factory="..zodb.InternalPrincipal"
- arguments="id login password title"
+ schema="..principalfolder.IInternalPrincipal"
+ label="Add Principal Information"
+ content_factory="..principalfolder.PrincipalInformation"
+ arguments="login password title"
keyword_arguments="description"
- name="AddInternalPrincipalForm.html"
+ name="AddPrincipalInformation.html"
permission="zope.ManageServices"
/>
<addMenuItem
- title="Persistent Principal"
- class="..zodb.InternalPrincipal"
+ title="Principal Information"
+ class="..principalfolder.PrincipalInformation"
permission="zope.ManageServices"
- view="AddInternalPrincipalForm.html"
+ view="AddPrincipalInformation.html"
/>
<editform
- schema="..zodb.IInternalPrincipal"
+ schema="..principalfolder.IInternalPrincipal"
label="Change Internal Principal"
name="edit.html"
fields="login password title description"
@@ -35,7 +52,7 @@
menu="zmi_views" title="Edit" />
<containerViews
- for="..zodb.IInternalPrincipalContainer"
+ for="..principalfolder.IInternalPrincipalContainer"
add="zope.ManageServices"
contents="zope.ManageServices"
index="zope.ManageServices"
Added: Zope3/trunk/src/zope/app/pas/browser/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/ftests.py 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/browser/ftests.py 2004-10-31 18:01:29 UTC (rev 28305)
@@ -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.0 (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.
+#
+##############################################################################
+"""Functional tests
+
+$Id$
+"""
+import unittest
+
+def test_suite():
+ from zope.app.tests import functional
+ return unittest.TestSuite((
+ functional.FunctionalDocFileSuite('principalfolder.txt'),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
Property changes on: Zope3/trunk/src/zope/app/pas/browser/ftests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/app/pas/browser/principalfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/principalfolder.txt 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/browser/principalfolder.txt 2004-10-31 18:01:29 UTC (rev 28305)
@@ -0,0 +1,262 @@
+Using Principal Folders
+=======================
+
+Principal folders are PAS plugins that manage principal information,
+especially authentication credentials. To use a principal folder, you
+need to create a principal folder in a site management folder and then
+configure it in a PAS. Let's look at an example, in which we'll
+define a new manager named Bob. Initially, attempts to log in as Bob
+fail:
+
+ >>> print http(r"""
+ ... GET /manage HTTP/1.1
+ ... Authorization: Basic Ym9iOjEyMw==
+ ... """)
+ HTTP/1.1 401 Unauthorized
+ ...
+
+To allow Bob to log in, we'll start by adding a principal folder:
+
+
+(The following request is a bit weird. It is part of the current
+ tools UI. It arranges for a tools site-management folder to be
+ created. We really need to rethink how we manage TTW utilities.)
+
+ >>> print http(r"""
+ ... GET /++etc++site/AddISearchableAuthenticationPluginTool HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Referer: http://localhost:8081/++etc++site/@@manageISearchableAuthenticationPluginTool.html
+ ... """)
+ HTTP/1.1 200 Ok
+ ...
+
+ >>> print http(r"""
+ ... POST /++etc++site/AddISearchableAuthenticationPluginTool/AddPrincipalFolder.html%3D HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Length: 434
+ ... Content-Type: multipart/form-data; boundary=---------------------------190685539214643056941988788830
+ ... Referer: http://localhost:8081/++etc++site/AddISearchableAuthenticationPluginTool/AddPrincipalFolder.html=
+ ...
+ ... -----------------------------190685539214643056941988788830
+ ... Content-Disposition: form-data; name="field.prefix"
+ ...
+ ... users.
+ ... -----------------------------190685539214643056941988788830
+ ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+ ...
+ ... Add
+ ... -----------------------------190685539214643056941988788830
+ ... Content-Disposition: form-data; name="add_input_name"
+ ...
+ ... users
+ ... -----------------------------190685539214643056941988788830--
+ ... """)
+ HTTP/1.1 303 See Other
+ ...
+ Location: ../@@manageISearchableAuthenticationPluginTool.html
+ ...
+
+We specify a prefix, `users.`. This is used to make sure that ids
+used by this plugin don't conflict with ids of other plugins. We also
+name ths plugin `users`. This is the name we'll use when we configure
+the pluggable authentiaction service.
+
+Next we'll view the contents page of the principal folder:
+
+ >>> print http(r"""
+ ... GET /++etc++site/tools/users/@@contents.html HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Referer: http://localhost:8081/++etc++site/@@manageISearchableAuthenticationPluginTool.html
+ ... """)
+ HTTP/1.1 200 Ok
+ ...
+
+And we'll add a principal, Bob:
+
+ >>> print http(r"""
+ ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Length: 777
+ ... Content-Type: multipart/form-data; boundary=---------------------------7243003661505678908829226317
+ ... Referer: http://localhost:8081/++etc++site/tools/users/+/AddPrincipalInformation.html=
+ ...
+ ... -----------------------------7243003661505678908829226317
+ ... Content-Disposition: form-data; name="field.login"
+ ...
+ ... bob
+ ... -----------------------------7243003661505678908829226317
+ ... Content-Disposition: form-data; name="field.password"
+ ...
+ ... 123
+ ... -----------------------------7243003661505678908829226317
+ ... Content-Disposition: form-data; name="field.title"
+ ...
+ ... Bob
+ ... -----------------------------7243003661505678908829226317
+ ... Content-Disposition: form-data; name="field.description"
+ ...
+ ...
+ ... -----------------------------7243003661505678908829226317
+ ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+ ...
+ ... Add
+ ... -----------------------------7243003661505678908829226317
+ ... Content-Disposition: form-data; name="add_input_name"
+ ...
+ ...
+ ... -----------------------------7243003661505678908829226317--
+ ... """)
+ HTTP/1.1 303 See Other
+ ...
+ Location: http://localhost/++etc++site/tools/users/@@contents.html
+ ...
+
+Note that we didn't pick a name. The name, together with the folder
+prefix. If we don't choose a name, a numeric id is chosen.
+
+Now we have a principal folder with a principal. We need to create a
+pluggable authentication service:
+
+
+ >>> print http(r"""
+ ... POST /++etc++site/default/AddService/action.html HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Length: 61
+ ... Content-Type: application/x-www-form-urlencoded
+ ... Referer: http://localhost:8081/++etc++site/default/AddService
+ ...
+ ... type_name=BrowserAdd__zope.app.pas.pas.LocalPAS&id=&add=+Add+""")
+ HTTP/1.1 303 See Other
+ ...
+ Location: http://localhost/++etc++site/default/LocalPAS/@@registration.html
+ ...
+
+and configure it to use the principal folder:
+
+ >>> print http(r"""
+ ... POST /++etc++site/default/LocalPAS/@@edit.html HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Length: 1818
+ ... Content-Type: multipart/form-data; boundary=---------------------------11831623361211414588608810327
+ ... Referer: http://localhost:8081/++etc++site/default/LocalPAS/@@edit.html
+ ...
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.extractors.to"
+ ...
+ ... HTTP Basic
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.authenticators.to"
+ ...
+ ... users
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.challengers.to"
+ ...
+ ... No Challenge if Authenticated
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.challengers.to"
+ ...
+ ... Zope Realm HTTP Basic
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.factories.to"
+ ...
+ ... Default
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.searchers.to"
+ ...
+ ... users
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+ ...
+ ... Change
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.extractors"
+ ...
+ ... HTTP Basic
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.authenticators"
+ ...
+ ... users
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.challengers"
+ ...
+ ... No Challenge if Authenticated
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.challengers"
+ ...
+ ... Zope Realm HTTP Basic
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.factories"
+ ...
+ ... Default
+ ... -----------------------------11831623361211414588608810327
+ ... Content-Disposition: form-data; name="field.searchers"
+ ...
+ ... users
+ ... -----------------------------11831623361211414588608810327--
+ ... """)
+ HTTP/1.1 200 Ok
+ ...
+
+We also tell it:
+
+ - to use HTTP Basic authentication with the Zope realm,
+
+ - not to challenge authenticated principals, and
+
+ - to use the default principal factory
+
+Now, with this in place, Bob can log in, but he isn't allowed to
+access the management interface:
+
+
+ >>> print http(r"""
+ ... GET /manage HTTP/1.1
+ ... Authorization: Basic Ym9iOjEyMw==
+ ... """)
+ HTTP/1.1 403 Forbidden
+ ...
+
+We go to the granting interface and search for and find a principal named Bob:
+
+ >>> print http(r"""
+ ... POST /@@grant.html HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Length: 226
+ ... Content-Type: application/x-www-form-urlencoded
+ ... Referer: http://localhost:8081/@@grant.html
+ ...
+ ... field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MC51c2Vycw__.query.search=Search&field.principal.MA__.query.searchstring=&field.principal.MA__.selection=em9wZS5zYW1wbGVfbWFuYWdlcg__""")
+ HTTP/1.1 200 Ok
+ ...
+ <select name="field.principal.MC51c2Vycw__.selection">
+ <option value="dXNlcnMuMQ__">Bob</option>
+ </select>
+ ...
+
+We select Bob and grant him the Manager role:
+
+ >>> print http(r"""
+ ... POST /@@grant.html HTTP/1.1
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Length: 5316
+ ... Content-Type: application/x-www-form-urlencoded
+ ... Referer: http://localhost:8081/@@grant.html
+ ...
+ ... field.principal=dXNlcnMuMQ__"""
+ ... """&field.principal.displayed=y"""
+ ... """&field.principal.MC51c2Vycw__.query.field.search=bob"""
+ ... """&field.principal.MA__.query.searchstring="""
+ ... """&GRANT_SUBMIT=Change"""
+ ... """&field.dXNlcnMuMQ__.role.zope.Manager=allow"""
+ ... """&field.dXNlcnMuMQ__.role.zope.Manager-empty-marker=1""")
+ HTTP/1.1 200 Ok
+ ...
+
+At which point, Bob can access the management interface:
+
+ >>> print http(r"""
+ ... GET /@@contents.html HTTP/1.1
+ ... Authorization: Basic Ym9iOjEyMw==
+ ... """)
+ HTTP/1.1 200 Ok
+ ...
Added: Zope3/trunk/src/zope/app/pas/idpicker.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/idpicker.py 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/idpicker.py 2004-10-31 18:01:29 UTC (rev 28305)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Helper base class that picks principal ids
+
+$Id$
+"""
+
+from zope.app.container.contained import NameChooser
+
+
+class IdPicker(NameChooser):
+ """Helper base class that picks principal ids
+
+ Add numbers to ids given by users to make them unique.
+ """
+
+ def chooseName(self, name, object):
+ i = 0
+ name = unicode(name)
+ orig = name
+ while (not name) or (name in self.context):
+ i += 1
+ name = orig+str(i)
+
+ self.checkName(name, object)
+ return name
+
Property changes on: Zope3/trunk/src/zope/app/pas/idpicker.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/app/pas/idpicker.txt
===================================================================
--- Zope3/trunk/src/zope/app/pas/idpicker.txt 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/idpicker.txt 2004-10-31 18:01:29 UTC (rev 28305)
@@ -0,0 +1,22 @@
+Id Pickler
+----------
+
+The Id pickler is a variation on the name chooser that picks numeric
+ids when no name is given.
+
+ >>> from zope.app.pas.idpicker import IdPicker
+ >>> IdPicker({}).chooseName('', None)
+ u'1'
+
+ >>> IdPicker({'1': 1}).chooseName('', None)
+ u'2'
+
+ >>> IdPicker({'2': 1}).chooseName('', None)
+ u'1'
+
+ >>> IdPicker({'1': 1}).chooseName('bob', None)
+ u'bob'
+
+ >>> IdPicker({'bob': 1}).chooseName('bob', None)
+ u'bob1'
+
Property changes on: Zope3/trunk/src/zope/app/pas/idpicker.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Copied: Zope3/trunk/src/zope/app/pas/principalfolder.py (from rev 28289, Zope3/trunk/src/zope/app/pas/zodb.py)
===================================================================
--- Zope3/trunk/src/zope/app/pas/zodb.py 2004-10-29 21:33:26 UTC (rev 28289)
+++ Zope3/trunk/src/zope/app/pas/principalfolder.py 2004-10-31 18:01:29 UTC (rev 28305)
@@ -0,0 +1,224 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""ZODB-based Authentication Source
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import persistent
+
+import zope.interface
+import zope.schema
+
+import zope.app.container.btree
+import zope.app.container.contained
+import zope.app.container.constraints
+import zope.app.container.interfaces
+
+from zope.app.i18n import ZopeMessageIDFactory as _
+
+from zope.app.pas import interfaces
+
+class IInternalPrincipal(zope.interface.Interface):
+ """Principal information"""
+
+ login = zope.schema.TextLine(
+ title=_("Login"),
+ description=_("The Login/Username of the principal. "
+ "This value can change."),
+ required=True)
+
+ password = zope.schema.Password(
+ title=_(u"Password"),
+ description=_("The password for the principal."),
+ required=True)
+
+ title = zope.schema.TextLine(
+ title=_("Title"),
+ description=_("Provides a title for the principal."),
+ required=True)
+
+ description = zope.schema.Text(
+ title=_("Description"),
+ description=_("Provides a description for the principal."),
+ required=False,
+ missing_value='',
+ default=u'',
+ )
+
+
+class IInternalPrincipalContainer(zope.interface.Interface):
+ """A container that contains internal principals."""
+
+ prefix = zope.schema.TextLine(
+ title=_("Prefix"),
+ 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,
+ )
+
+ zope.app.container.constraints.contains(IInternalPrincipal)
+
+
+class IInternalPrincipalContained(zope.interface.Interface):
+ """Principal information"""
+
+ zope.app.container.constraints.containers(IInternalPrincipalContainer)
+
+
+class ISearchSchema(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 PrincipalInformation(
+ persistent.Persistent,
+ zope.app.container.contained.Contained,
+ ):
+ """An internal principal for Persistent Principal Folder.
+ """
+ zope.interface.implements(IInternalPrincipal, IInternalPrincipalContained)
+
+ def __init__(self, login, password, title, description=u''):
+ self._login = login
+ self.password = password
+ self.title = title
+ self.description = description
+
+ 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)
+
+ def __getitem__(self, attr):
+ if attr in ('title', 'description'):
+ return getattr(self, attr)
+
+class PrincipalFolder(zope.app.container.btree.BTreeContainer):
+ """A Persistent Principal Folder and Authentication plugin
+ """
+ zope.interface.implements(interfaces.ISearchableAuthenticationPlugin,
+ interfaces.IQuerySchemaSearch,
+ IInternalPrincipalContainer)
+
+ def __init__(self, prefix=''):
+ self.prefix = unicode(prefix)
+ super(PrincipalFolder, 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, principal):
+ """Add principal information
+ """
+ # A user with the new login already exists
+ if principal.login in self.__id_by_login:
+ raise ValueError, 'Principal Login already taken!'
+
+ super(PrincipalFolder, self).__setitem__(id, principal)
+ self.__id_by_login[principal.login] = id
+
+ def __delitem__(self, id):
+ """Remove principal information
+ """
+ principal = self[id]
+ super(PrincipalFolder, self).__delitem__(id)
+ del self.__id_by_login[principal.login]
+
+
+ def authenticateCredentials(self, credentials):
+ """Return principal info 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
+
+ principal = self[id]
+ if principal.password != credentials['password']:
+ return None
+
+ id = self.prefix+id
+
+ return id, {'login': principal.login, 'title': principal.title,
+ 'description': principal.description}
+
+ def principalInfo(self, principal_id):
+ if principal_id.startswith(self.prefix):
+ principal = self.get(principal_id[len(self.prefix):])
+ if principal is not None:
+ return {
+ '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.
+ """
+ search = query.get('search')
+ if search is None:
+ return
+ 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 self.prefix+value.__name__
+ i += 1
Added: Zope3/trunk/src/zope/app/pas/principalfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/pas/principalfolder.txt 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/principalfolder.txt 2004-10-31 18:01:29 UTC (rev 28305)
@@ -0,0 +1,168 @@
+Principal Folder
+================
+
+Principal folders contain principal-information objects that contain
+principal information. We create principal information using the
+`PrincipalInformation` class:
+
+ >>> import zope.app.pas.principalfolder
+ >>> p1 = zope.app.pas.principalfolder.PrincipalInformation(
+ ... 'login1', '123', "Principal 1")
+ >>> p2 = zope.app.pas.principalfolder.PrincipalInformation(
+ ... 'login2', '456', "The other one")
+
+ >>> principals = zope.app.pas.principalfolder.PrincipalFolder('principal.')
+ >>> principals['p1'] = p1
+ >>> principals['p2'] = p2
+
+Authentication
+--------------
+
+Principal folders provide the `IAuthenticationPlugin' interface.
+When we provide suitable credentials:
+
+ >>> from zope.testing.doctestunit import pprint
+ >>> pprint(principals.authenticateCredentials({'login': 'login1',
+ ... 'password': '123'}))
+ (u'principal.p1',
+ {'description': u'',
+ 'login': 'login1',
+ 'title': 'Principal 1'})
+
+we get back a principal id and supplimentary information, including the
+principal title and description. Note that the principal id is a
+concatination of the principal-folder prefix and the name of the
+principal-information object within the folder.
+
+None is returned if the credentials are invalid:
+
+ >>> principals.authenticateCredentials({'login': 'login1',
+ ... 'password': '1234'})
+ >>> principals.authenticateCredentials(42)
+
+Search
+------
+
+Principal folders also provide the IQuerySchemaSearch interface. This
+supports both finding principal information based on their ids:
+
+ >>> pprint(principals.principalInfo('principal.p1'))
+ {'description': u'',
+ 'login': 'login1',
+ 'title': 'Principal 1'}
+
+ >>> principals.principalInfo('p1')
+
+and searching for principals based on a search string:
+
+ >>> list(principals.search({'search': 'other'}))
+ [u'principal.p2']
+
+ >>> list(principals.search({'search': ''}))
+ [u'principal.p1', u'principal.p2']
+
+ >>> list(principals.search({'search': 'eek'}))
+ []
+
+ >>> list(principals.search({}))
+ []
+
+If there are a large number of matches:
+
+ >>> for i in range(20):
+ ... i = str(i)
+ ... p = zope.app.pas.principalfolder.PrincipalInformation(
+ ... 'l'+i, i, "Dude "+i)
+ ... principals[i] = p
+
+ >>> pprint(list(principals.search({'search': 'D'})))
+ [u'principal.0',
+ u'principal.1',
+ u'principal.10',
+ u'principal.11',
+ u'principal.12',
+ u'principal.13',
+ u'principal.14',
+ u'principal.15',
+ u'principal.16',
+ u'principal.17',
+ u'principal.18',
+ u'principal.19',
+ u'principal.2',
+ u'principal.3',
+ u'principal.4',
+ u'principal.5',
+ u'principal.6',
+ u'principal.7',
+ u'principal.8',
+ u'principal.9']
+
+We can use batching parameters to specify a subset of results:
+
+ >>> pprint(list(principals.search({'search': 'D'}, start=17)))
+ [u'principal.7',
+ u'principal.8',
+ u'principal.9']
+
+ >>> pprint(list(principals.search({'search': 'D'}, batch_size=5)))
+ [u'principal.0',
+ u'principal.1',
+ u'principal.10',
+ u'principal.11',
+ u'principal.12']
+
+ >>> pprint(list(principals.search({'search': 'D'}, start=5, batch_size=5)))
+ [u'principal.13',
+ u'principal.14',
+ u'principal.15',
+ u'principal.16',
+ u'principal.17']
+
+Changing credentials
+--------------------
+
+Creedentials can be changed by modifying principal-information
+objects:
+
+ >>> p1.login = 'bob'
+ >>> p1.password = 'eek'
+
+ >>> pprint(principals.authenticateCredentials({'login': 'bob',
+ ... 'password': 'eek'}))
+ (u'principal.p1',
+ {'description': u'',
+ 'login': 'bob',
+ 'title': 'Principal 1'})
+
+ >>> principals.authenticateCredentials({'login': 'login1',
+ ... 'password': 'eek'})
+
+ >>> principals.authenticateCredentials({'login': 'bob',
+ ... 'password': '123'})
+
+
+It is an error to try to pick a login name that is already taken:
+
+ >>> p1.login = 'login2'
+ Traceback (most recent call last):
+ ...
+ ValueError: Principal Login already taken!
+
+If such an attempt is made, the data are unchanged:
+
+ >>> pprint(principals.authenticateCredentials({'login': 'bob',
+ ... 'password': 'eek'}))
+ (u'principal.p1',
+ {'description': u'',
+ 'login': 'bob',
+ 'title': 'Principal 1'})
+
+Removing principals
+-------------------
+
+If course, if a principal is removed, we can no-longer authenticate
+it:
+
+ >>> del principals['p1']
+ >>> principals.authenticateCredentials({'login': 'bob',
+ ... 'password': 'eek'})
Property changes on: Zope3/trunk/src/zope/app/pas/principalfolder.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/pas/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/tests.py 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/tests.py 2004-10-31 18:01:29 UTC (rev 28305)
@@ -58,7 +58,8 @@
return unittest.TestSuite((
doctest.DocTestSuite('zope.app.pas.generic'),
doctest.DocTestSuite('zope.app.pas.httpplugins'),
- doctest.DocTestSuite('zope.app.pas.zodb'),
+ doctest.DocFileSuite('principalfolder.txt'),
+ doctest.DocFileSuite('idpicker.txt'),
doctest.DocTestSuite('zope.app.pas.principalplugins'),
doctest.DocTestSuite('zope.app.pas.browserplugins',
setUp=formAuthSetUp,
Deleted: Zope3/trunk/src/zope/app/pas/zodb.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/zodb.py 2004-10-31 17:59:45 UTC (rev 28304)
+++ Zope3/trunk/src/zope/app/pas/zodb.py 2004-10-31 18:01:29 UTC (rev 28305)
@@ -1,317 +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.
-#
-##############################################################################
-"""ZODB-based Authentication Source
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-from persistent import Persistent
-from BTrees.OOBTree import OOBTree
-from UserDict import DictMixin
-
-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.interfaces import INameChooser
-from zope.app.exception.interfaces import UserError
-from zope.app.i18n import ZopeMessageIDFactory as _
-
-import interfaces
-
-class IInternalPrincipal(Interface):
- """Principal information"""
-
- id = TextLine(
- title=_("Id"),
- description=_("Id as which this principal will be known and used."),
- readonly=True,
- required=True)
-
- login = TextLine(
- title=_("Login"),
- description=_("The Login/Username of the principal. "
- "This value can change."),
- required=True)
-
- password = Password(
- title=_(u"Password"),
- description=_("The password for the principal."),
- required=True)
-
- title = TextLine(
- title=_("Title"),
- description=_("Provides a title for the principal."),
- required=True)
-
- description = Text(
- title=_("Description"),
- description=_("Provides a description for the principal."),
- required=False)
-
-
-class IInternalPrincipalContainer(Interface):
- """A container that contains internal principals."""
-
- def __setitem__(id, principal_source):
- """Add to object"""
-
- __setitem__.precondition = ItemTypePrecondition(IInternalPrincipal)
-
-
-class IInternalPrincipalContained(Interface):
- """Principal information"""
-
- __parent__= Field(
- constraint = ContainerTypesConstraint(IInternalPrincipalContainer))
-
-
-class ISearchSchema(Interface):
- """Search Interface for this Principal Provider"""
-
- search = TextLine(
- title=_("Search String"),
- description=_("A Search String"),
- required=False,
- default=u'')
-
-class InternalPrincipal(Persistent, Contained, DictMixin):
- """An internal principal for Persistent Principal Folder.
-
- Make sure the folder gets notified for login changes.
-
- >>> class Folder:
- ... def notifyLoginChanged(self, old, principal):
- ... self.old = old
- ... self.principal = principal
-
- >>> folder = Folder()
- >>> principal = InternalPrincipal('1', 'foo', 'bar', 'Foo Bar')
- >>> principal.__parent__ = folder
-
- >>> principal.login = 'blah'
-
- >>> folder.old
- 'foo'
- >>> folder.principal is principal
- True
- """
- implements(IInternalPrincipal, IInternalPrincipalContained)
-
- def __init__(self, id, login, password, title, description=u''):
- self.id = id
- self._login = login
- self.password = password
- self.title = title
- self.description = description
-
- def getLogin(self):
- return self._login
-
- def setLogin(self, login):
- oldLogin = self._login
- self._login = login
- if self.__parent__ is not None:
- self.__parent__.notifyLoginChanged(oldLogin, self)
-
- 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.
- """
- # 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.id
-
- def __setitem__(self, id, principal):
- """ See `IContainerNamesContainer`
-
- >>> 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'
- """
- 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!'
-
- super(PersistentPrincipalFolder, self).__setitem__(id, principal)
- self.__id_by_login[principal.login] = id
-
- def __delitem__(self, id):
- """ See `IContainer`.
-
- >>> pps = PersistentPrincipalFolder()
- >>> pps['1'] = InternalPrincipal('1', 'foo', 'bar', u'Foo Bar')
- >>> del pps['1']
- >>> pps['1']
- Traceback (most recent call last):
- ...
- KeyError: '1'
- """
- principal = self[id]
- super(PersistentPrincipalFolder, self).__delitem__(id)
- del self.__id_by_login[principal.login]
-
-
- def authenticateCredentials(self, credentials):
- """See zope.app.pas.interfaces.IAuthenticationPlugin
-
- Create an authentication plugin and add a principal to it.
-
- >>> pps = PersistentPrincipalFolder()
- >>> pps['1'] = InternalPrincipal('1', 'foo', 'bar', u'Foo Bar')
-
- >>> pps.authenticateCredentials(1) is None
- True
- >>> pps.authenticateCredentials({'blah': 2}) is None
- True
- >>> pps.authenticateCredentials({'login': 'foo'}) is None
- True
- >>> pps.authenticateCredentials({'password': 'bar'}) is None
- True
- >>> pps.authenticateCredentials({'login': 'foo1',
- ... 'password': 'bar'}) is None
- True
- >>> pps.authenticateCredentials({'login': 'foo',
- ... 'password': 'bar1'}) is None
- True
- >>> 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
-
- 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
-
- principal = self[id]
- if principal.password != credentials['password']:
- return None
-
- return id, {'login': principal.login, 'title': principal.title,
- 'description': principal.description}
-
- def principalInfo(self, principal_id):
- return self.get(principal_id)
-
- 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