[Zope3-checkins] SVN: Zope3/trunk/src/zope/ Commit changes to
implement the following proposals:
Gary Poster
gary at zope.com
Thu Jan 19 23:19:09 EST 2006
Log message for revision 41374:
Commit changes to implement the following proposals:
http://www.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/AllowContainedNonUtilityPluggableAuthenticationUtilityPlugins
http://www.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/FireEventsWhenPrincipalsAreAddedToAndRemovedFromGroupFolders
http://www.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/DefineInterfaceForGettingFullClosureOfAPrincipalsGroupsImplementInPAU
http://www.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/DefineInterfaceForGettingAGroupsMembersImplementInPAU
Includes a new zope.app generation to evolve legacy databases.
Changed:
U Zope3/trunk/src/zope/app/authentication/authentication.py
U Zope3/trunk/src/zope/app/authentication/browser/group_searching_with_empty_string.txt
U Zope3/trunk/src/zope/app/authentication/browser/groupfolder.txt
U Zope3/trunk/src/zope/app/authentication/browser/pau_prefix_and_searching.txt
U Zope3/trunk/src/zope/app/authentication/browser/principalfolder.txt
U Zope3/trunk/src/zope/app/authentication/browser/special-groups.txt
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/groupfolder.txt
U Zope3/trunk/src/zope/app/authentication/groupfolder.zcml
U Zope3/trunk/src/zope/app/authentication/interfaces.py
U Zope3/trunk/src/zope/app/authentication/principalfolder.py
U Zope3/trunk/src/zope/app/authentication/tests.py
A Zope3/trunk/src/zope/app/authentication/vocabulary.py
A Zope3/trunk/src/zope/app/authentication/vocabulary.txt
U Zope3/trunk/src/zope/app/zopeappgenerations/__init__.py
A Zope3/trunk/src/zope/app/zopeappgenerations/evolve3.py
U Zope3/trunk/src/zope/security/interfaces.py
-=-
Modified: Zope3/trunk/src/zope/app/authentication/authentication.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/authentication.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/authentication.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -41,16 +41,25 @@
super(PluggableAuthentication, self).__init__()
self.prefix = prefix
+ def _plugins(self, names, interface):
+ for name in names:
+ plugin = self.get(name)
+ if not interface.providedBy(plugin):
+ plugin = component.queryUtility(interface, name, context=self)
+ if plugin is not None:
+ yield name, plugin
+
+ def getAuthenticatorPlugins(self):
+ return self._plugins(
+ self.authenticatorPlugins, interfaces.IAuthenticatorPlugin)
+
+ def getCredentialsPlugins(self):
+ return self._plugins(
+ self.credentialsPlugins, interfaces.ICredentialsPlugin)
+
def authenticate(self, request):
- authenticatorPlugins = [
- component.queryUtility(interfaces.IAuthenticatorPlugin,
- name, context=self)
- for name in self.authenticatorPlugins]
- for name in self.credentialsPlugins:
- credplugin = component.queryUtility(
- interfaces.ICredentialsPlugin, name, context=self)
- if credplugin is None:
- continue
+ authenticatorPlugins = [p for n, p in self.getAuthenticatorPlugins()]
+ for name, credplugin in self.getCredentialsPlugins():
credentials = credplugin.extractCredentials(request)
for authplugin in authenticatorPlugins:
if authplugin is None:
@@ -73,11 +82,7 @@
raise PrincipalLookupError(id)
return next.getPrincipal(id)
id = id[len(self.prefix):]
- for name in self.authenticatorPlugins:
- authplugin = component.queryUtility(
- interfaces.IAuthenticatorPlugin, name, context=self)
- if authplugin is None:
- continue
+ for name, authplugin in self.getAuthenticatorPlugins():
info = authplugin.principalInfo(id)
if info is None:
continue
@@ -92,11 +97,7 @@
raise PrincipalLookupError(id)
def getQueriables(self):
- for name in self.authenticatorPlugins:
- authplugin = component.queryUtility(
- interfaces.IAuthenticatorPlugin, name, context=self)
- if authplugin is None:
- continue
+ for name, authplugin in self.getAuthenticatorPlugins():
queriable = component.queryMultiAdapter((authplugin, self),
interfaces.IQueriableAuthenticator)
if queriable is not None:
@@ -108,11 +109,7 @@
def unauthorized(self, id, request):
challengeProtocol = None
- for name in self.credentialsPlugins:
- credplugin = component.queryUtility(interfaces.ICredentialsPlugin,
- name)
- if credplugin is None:
- continue
+ for name, credplugin in self.getCredentialsPlugins():
protocol = getattr(credplugin, 'challengeProtocol', None)
if challengeProtocol is None or protocol == challengeProtocol:
if credplugin.challenge(request):
@@ -129,11 +126,7 @@
def logout(self, request):
challengeProtocol = None
- for name in self.credentialsPlugins:
- credplugin = component.queryUtility(interfaces.ICredentialsPlugin,
- name)
- if credplugin is None:
- continue
+ for name, credplugin in self.getCredentialsPlugins():
protocol = getattr(credplugin, 'challengeProtocol', None)
if challengeProtocol is None or protocol == challengeProtocol:
if credplugin.logout(request):
Modified: Zope3/trunk/src/zope/app/authentication/browser/group_searching_with_empty_string.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/group_searching_with_empty_string.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/browser/group_searching_with_empty_string.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -77,45 +77,6 @@
...
-Register group folder pulgin.
-
- >>> print http(r"""
- ... POST /++etc++site/default/PAU/groups/addRegistration.html HTTP/1.1
- ... Authorization: Basic bWdyOm1ncnB3
- ... Content-Length: 807
- ... Content-Type: multipart/form-data; boundary=---------------------------6689874747253728091673221069
- ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/addRegistration.html
- ...
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.name"
- ...
- ... groups
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.status"
- ...
- ... Active
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.status-empty-marker"
- ...
- ... 1
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.permission"
- ...
- ...
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.permission-empty-marker"
- ...
- ... 1
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
- ...
- ... Add
- ... -----------------------------6689874747253728091673221069--
- ... """)
- HTTP/1.1 303 See Other
- ...
-
-
And add some groups:
@@ -191,7 +152,7 @@
... -----------------------------1786480431902757372789659730
... Content-Disposition: form-data; name="field.credentialsPlugins.to"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------1786480431902757372789659730
... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
...
@@ -199,7 +160,7 @@
... -----------------------------1786480431902757372789659730
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... groups
+ ... Z3JvdXBz
... -----------------------------1786480431902757372789659730
... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
...
@@ -211,11 +172,11 @@
... -----------------------------1786480431902757372789659730
... Content-Disposition: form-data; name="field.credentialsPlugins"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------1786480431902757372789659730
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... groups
+ ... Z3JvdXBz
... -----------------------------1786480431902757372789659730--
... """)
HTTP/1.1 200 Ok
Modified: Zope3/trunk/src/zope/app/authentication/browser/groupfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/groupfolder.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/browser/groupfolder.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -463,45 +463,6 @@
...
-Register group folder pulgin.
-
- >>> print http(r"""
- ... POST /++etc++site/default/PAU/groups/addRegistration.html HTTP/1.1
- ... Authorization: Basic bWdyOm1ncnB3
- ... Content-Length: 807
- ... Content-Type: multipart/form-data; boundary=---------------------------6689874747253728091673221069
- ... Referer: http://localhost:8081/++etc++site/default/PAU/groups/addRegistration.html
- ...
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.name"
- ...
- ... groups
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.status"
- ...
- ... Active
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.status-empty-marker"
- ...
- ... 1
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.permission"
- ...
- ...
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="field.permission-empty-marker"
- ...
- ... 1
- ... -----------------------------6689874747253728091673221069
- ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
- ...
- ... Add
- ... -----------------------------6689874747253728091673221069--
- ... """)
- HTTP/1.1 303 See Other
- ...
-
-
Next we'll select the credentials and authenticators for the PAU:
>>> print http(r"""
@@ -514,7 +475,7 @@
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.credentialsPlugins.to"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
...
@@ -522,11 +483,11 @@
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... users
+ ... dXNlcnM=
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... groups
+ ... Z3JvdXBz
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
...
@@ -538,15 +499,15 @@
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.credentialsPlugins"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... users
+ ... dXNlcnM=
... -----------------------------2026736768606413562109112352
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... groups
+ ... Z3JvdXBz
... -----------------------------2026736768606413562109112352--
... """)
HTTP/1.1 200 Ok
Modified: Zope3/trunk/src/zope/app/authentication/browser/pau_prefix_and_searching.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/pau_prefix_and_searching.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/browser/pau_prefix_and_searching.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -187,41 +187,6 @@
HTTP/1.1 303 See Other
...
- >>> print http(r"""
- ... POST /++etc++site/default/PAU1/Groups/addRegistration.html HTTP/1.1
- ... Authorization: Basic bWdyOm1ncnB3
- ... Content-Length: 709
- ... Content-Type: multipart/form-data; boundary=---------------------------27244279644818
- ...
- ... -----------------------------27244279644818
- ... Content-Disposition: form-data; name="field.name"
- ...
- ... Groups
- ... -----------------------------27244279644818
- ... Content-Disposition: form-data; name="field.status"
- ...
- ... Active
- ... -----------------------------27244279644818
- ... Content-Disposition: form-data; name="field.status-empty-marker"
- ...
- ... 1
- ... -----------------------------27244279644818
- ... Content-Disposition: form-data; name="field.permission"
- ...
- ...
- ... -----------------------------27244279644818
- ... Content-Disposition: form-data; name="field.permission-empty-marker"
- ...
- ... 1
- ... -----------------------------27244279644818
- ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
- ...
- ... Add
- ... -----------------------------27244279644818--
- ... """)
- HTTP/1.1 303 See Other
- ...
-
and add a group to search for:
>>> print http(r"""
@@ -270,11 +235,11 @@
... -----------------------------610310492754
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... Groups
+ ... R3JvdXBz
... -----------------------------610310492754
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... Users
+ ... VXNlcnM=
... -----------------------------610310492754
... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
...
@@ -286,11 +251,11 @@
... -----------------------------610310492754
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... Groups
+ ... R3JvdXBz
... -----------------------------610310492754
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... Users
+ ... VXNlcnM=
... -----------------------------610310492754--
... """)
HTTP/1.1 200 Ok
Modified: Zope3/trunk/src/zope/app/authentication/browser/principalfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/principalfolder.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/browser/principalfolder.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -224,7 +224,7 @@
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.credentialsPlugins.to"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
...
@@ -232,7 +232,7 @@
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... users
+ ... dXNlcnM=
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
...
@@ -244,11 +244,11 @@
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.credentialsPlugins"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... users
+ ... dXNlcnM=
... -----------------------------6519411471194050603270010787--
... """)
HTTP/1.1 200 Ok
Modified: Zope3/trunk/src/zope/app/authentication/browser/special-groups.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/browser/special-groups.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/browser/special-groups.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -187,7 +187,7 @@
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.credentialsPlugins.to"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker"
...
@@ -195,7 +195,7 @@
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.authenticatorPlugins.to"
...
- ... users
+ ... dXNlcnM=
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker"
...
@@ -207,11 +207,11 @@
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.credentialsPlugins"
...
- ... Session Credentials
+ ... U2Vzc2lvbiBDcmVkZW50aWFscw==
... -----------------------------6519411471194050603270010787
... Content-Disposition: form-data; name="field.authenticatorPlugins"
...
- ... users
+ ... dXNlcnM=
... -----------------------------6519411471194050603270010787--
... """)
HTTP/1.1 200 Ok
Modified: Zope3/trunk/src/zope/app/authentication/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/authentication/configure.zcml 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/configure.zcml 2006-01-20 04:19:08 UTC (rev 41374)
@@ -22,22 +22,14 @@
provides=".interfaces.IQueriableAuthenticator"
factory=".authentication.QuerySchemaSearchAdapter" />
- <!-- 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. -->
- <interface interface=".interfaces.IAuthenticatorPlugin" />
<vocabulary
name="CredentialsPlugins"
- factory="zope.app.component.vocabulary.UtilityVocabulary"
- interface="zope.app.authentication.interfaces.ICredentialsPlugin"
- nameOnly="True"
+ factory=".vocabulary.credentialsPlugins"
/>
<vocabulary
name="AuthenticatorPlugins"
- factory="zope.app.component.vocabulary.UtilityVocabulary"
- interface="zope.app.authentication.interfaces.IAuthenticatorPlugin"
- nameOnly="True"
+ factory=".vocabulary.authenticatorPlugins"
/>
<utility
Modified: Zope3/trunk/src/zope/app/authentication/groupfolder.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/groupfolder.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/groupfolder.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -16,18 +16,13 @@
$Id$
"""
-
-try:
- set
-except NameError:
- from sets import Set as set
-
import BTrees.OOBTree
import persistent
from zope import interface, event, schema, component
from zope.interface import alsoProvides
-from zope.security.interfaces import IGroup, IGroupAwarePrincipal
+from zope.security.interfaces import (
+ IGroup, IGroupAwarePrincipal, IMemberAwareGroup)
from zope.app import zapi
from zope.app.container.btree import BTreeContainer
@@ -81,7 +76,6 @@
zope.app.container.constraints.containers(IGroupFolder)
-
class IGroupSearchCriteria(interface.Interface):
search = schema.TextLine(
@@ -90,13 +84,25 @@
missing_value=u'',
)
+class IGroupPrincipalInfo(interfaces.IPrincipalInfo):
+ members = interface.Attribute('an iterable of members of the group')
class GroupInfo(object):
"""An implementation of IPrincipalInfo used by the group folder.
A group info is created with id, title, and description:
- >>> info = GroupInfo('groups.managers', 'Managers', 'Taskmasters')
+ >>> class DemoGroupInformation(object):
+ ... interface.implements(IGroupInformation)
+ ... def __init__(self, title, description, principals):
+ ... self.title = title
+ ... self.description = description
+ ... self.principals = principals
+ ...
+ >>> i = DemoGroupInformation(
+ ... 'Managers', 'Taskmasters', ('joe', 'jane'))
+ ...
+ >>> info = GroupInfo('groups.managers', i)
>>> info
GroupInfo('groups.managers')
>>> info.id
@@ -105,15 +111,35 @@
'Managers'
>>> info.description
'Taskmasters'
+ >>> info.members
+ ('joe', 'jane')
+ >>> info.members = ('joe', 'jane', 'jaime')
+ >>> info.members
+ ('joe', 'jane', 'jaime')
"""
- interface.implements(interfaces.IPrincipalInfo)
+ interface.implements(IGroupPrincipalInfo)
- def __init__(self, id, title, description):
+ def __init__(self, id, information):
self.id = id
- self.title = title
- self.description = description
+ self._information = information
+ @property
+ def title(self):
+ return self._information.title
+
+ @property
+ def description(self):
+ return self._information.description
+
+ @apply
+ def members():
+ def get(self):
+ return self._information.principals
+ def set(self, value):
+ self._information.principals = value
+ return property(get, set)
+
def __repr__(self):
return 'GroupInfo(%r)' % self.id
@@ -136,35 +162,43 @@
def __setitem__(self, name, value):
BTreeContainer.__setitem__(self, name, value)
group_id = self._groupid(value)
- for principal_id in value.principals:
- self._addPrincipalToGroup(principal_id, group_id)
+ self._addPrincipalsToGroup(value.principals, group_id)
+ if value.principals:
+ event.notify(
+ interfaces.PrincipalsAddedToGroup(
+ value.principals, self.__parent__.prefix + group_id))
group = principalfolder.Principal(self.prefix + name)
event.notify(interfaces.GroupAdded(group))
def __delitem__(self, name):
value = self[name]
group_id = self._groupid(value)
- for principal_id in value.principals:
- self._removePrincipalFromGroup(principal_id, group_id)
+ self._removePrincipalsFromGroup(value.principals, group_id)
+ if value.principals:
+ event.notify(
+ interfaces.PrincipalsRemovedFromGroup(
+ value.principals, self.__parent__.prefix + group_id))
BTreeContainer.__delitem__(self, name)
def _groupid(self, group):
return self.prefix+group.__name__
- def _addPrincipalToGroup(self, principal_id, group_id):
- self.__inverseMapping[principal_id] = (
- self.__inverseMapping.get(principal_id, ())
- + (group_id,))
+ def _addPrincipalsToGroup(self, principal_ids, group_id):
+ for principal_id in principal_ids:
+ self.__inverseMapping[principal_id] = (
+ self.__inverseMapping.get(principal_id, ())
+ + (group_id,))
- def _removePrincipalFromGroup(self, principal_id, group_id):
- groups = self.__inverseMapping.get(principal_id)
- if groups is None:
- return
- new = tuple([id for id in groups if id != group_id])
- if new:
- self.__inverseMapping[principal_id] = new
- else:
- del self.__inverseMapping[principal_id]
+ def _removePrincipalsFromGroup(self, principal_ids, group_id):
+ for principal_id in principal_ids:
+ groups = self.__inverseMapping.get(principal_id)
+ if groups is None:
+ return
+ new = tuple([id for id in groups if id != group_id])
+ if new:
+ self.__inverseMapping[principal_id] = new
+ else:
+ del self.__inverseMapping[principal_id]
def getGroupsForPrincipal(self, principalid):
"""Get groups the given principal belongs to"""
@@ -200,7 +234,7 @@
info = self.get(id)
if info is not None:
return GroupInfo(
- self.prefix+id, info.title, info.description)
+ self.prefix+id, info)
class GroupCycle(Exception):
"""There is a cyclic relationship among groups
@@ -236,6 +270,7 @@
self.description = description
def setPrincipals(self, prinlist, check=True):
+ # method is not a part of the interface
parent = self.__parent__
old = self._principals
self._principals = tuple(prinlist)
@@ -244,19 +279,18 @@
oldset = set(old)
new = set(prinlist)
group_id = parent._groupid(self)
+ removed = oldset - new
+ added = new - oldset
+ try:
+ parent._removePrincipalsFromGroup(removed, group_id)
+ except AttributeError:
+ removed = None
- for principal_id in oldset - new:
- try:
- parent._removePrincipalFromGroup(principal_id, group_id)
- except AttributeError:
- pass
+ try:
+ parent._addPrincipalsToGroup(added, group_id)
+ except AttributeError:
+ added = None
- for principal_id in new - oldset:
- try:
- parent._addPrincipalToGroup(principal_id, group_id)
- except AttributeError:
- pass
-
if check:
try:
nocycles(new, [], zapi.principals().getPrincipal)
@@ -264,8 +298,16 @@
# abort
self.setPrincipals(old, False)
raise
+ # now that we've gotten past the checks, fire the events.
+ if removed:
+ event.notify(
+ interfaces.PrincipalsRemovedFromGroup(
+ removed, self.__parent__.__parent__.prefix + group_id))
+ if added:
+ event.notify(
+ interfaces.PrincipalsAddedToGroup(
+ added, self.__parent__.__parent__.prefix + group_id))
-
principals = property(lambda self: self._principals, setPrincipals)
@@ -293,8 +335,7 @@
authentication = event.authentication
- plugins = zapi.getUtilitiesFor(interfaces.IAuthenticatorPlugin)
- for name, plugin in plugins:
+ for name, plugin in authentication.getAuthenticatorPlugins():
if not IGroupFolder.providedBy(plugin):
continue
groupfolder = plugin
@@ -306,3 +347,16 @@
prefix = authentication.prefix + groupfolder.prefix
if id.startswith(prefix) and id[len(prefix):] in groupfolder:
alsoProvides(principal, IGroup)
+
+ at component.adapter(interfaces.IFoundPrincipalCreated)
+def setMemberSubscriber(event):
+ """adds `getMembers`, `setMembers` to groups made from IGroupPrincipalInfo.
+ """
+ info = event.info
+ if IGroupPrincipalInfo.providedBy(info):
+ principal = event.principal
+ principal.getMembers = lambda : info.members
+ def setMembers(value):
+ info.members = value
+ principal.setMembers = setMembers
+ alsoProvides(principal, IMemberAwareGroup)
Modified: Zope3/trunk/src/zope/app/authentication/groupfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/groupfolder.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/groupfolder.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -2,7 +2,8 @@
Group Folders
=============
-Group folders provide support for groups information stored in the ZODB.
+Group folders provide support for groups information stored in the ZODB. They
+are persistent, and must be contained within the PAUs that use them.
Like other principals, groups are created when they are needed.
@@ -57,9 +58,15 @@
... self.principals = {
... 'p1': principalfolder.PrincipalInfo('p1', '', '', ''),
... 'p2': principalfolder.PrincipalInfo('p2', '', '', ''),
+ ... 'p3': principalfolder.PrincipalInfo('p3', '', '', ''),
+ ... 'p4': principalfolder.PrincipalInfo('p4', '', '', ''),
... }
... self.groups = groups
+ ... groups.__parent__ = self
...
+ ... def getAuthenticatorPlugins(self):
+ ... return [('principals', self.principals), ('groups', self.groups)]
+ ...
... def getPrincipal(self, id):
... if not id.startswith(self.prefix):
... raise PrincipalLookupError(id)
@@ -96,8 +103,13 @@
>>> g1.principals
('auth.p1', 'auth.p2')
-This allows us to look up groups for the principals:
+Adding principals fires an event.
+ >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+ <PrincipalsAddedToGroup ['auth.p1', 'auth.p2'] u'auth.group.g1'>
+
+We can now look up groups for the principals:
+
>>> groups.getGroupsForPrincipal('auth.p1')
(u'group.g1',)
@@ -119,6 +131,12 @@
>>> g1.principals
('auth.p1', 'auth.p2')
+It also fires an event showing that the principals are removed from the group
+(g1 is group information, not a zope.security.interfaces.IGroup).
+
+ >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+ <PrincipalsRemovedFromGroup ['auth.p1', 'auth.p2'] u'auth.group.g1'>
+
Adding the group sets the folder principal information. Let's use a
different group name:
@@ -129,6 +147,29 @@
Here we see that the new name is reflected in the group information.
+An event is fired, as usual.
+
+ >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+ <PrincipalsAddedToGroup ['auth.p1', 'auth.p2'] u'auth.group.G1'>
+
+In terms of member events (principals added and removed from groups), we have
+now seen that events are fired when a group information object is added and
+when it is removed from a group folder; and we have seen that events are fired
+when a principal is added to an already-registered group. Events are also
+fired when a principal is removed from an already-registered group. Let's
+quickly see some more examples.
+
+ >>> g1.principals = ('auth.p1', 'auth.p3', 'auth.p4')
+ >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+ <PrincipalsAddedToGroup ['auth.p3', 'auth.p4'] u'auth.group.G1'>
+ >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+ <PrincipalsRemovedFromGroup ['auth.p2'] u'auth.group.G1'>
+ >>> g1.principals = ('auth.p1', 'auth.p2')
+ >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+ <PrincipalsAddedToGroup ['auth.p2'] u'auth.group.G1'>
+ >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
+ <PrincipalsRemovedFromGroup ['auth.p3', 'auth.p4'] u'auth.group.G1'>
+
Groups can contain groups:
>>> g2 = zope.app.authentication.groupfolder.GroupInformation("Group Two")
@@ -138,6 +179,10 @@
>>> groups.getGroupsForPrincipal('auth.group.G1')
(u'group.G2',)
+ >>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
+ >>> old
+ <PrincipalsAddedToGroup ['auth.group.G1'] u'auth.group.G2'>
+
Groups cannot contain cycles:
>>> g1.principals = ('auth.p1', 'auth.p2', 'auth.group.G2')
@@ -147,6 +192,11 @@
GroupCycle: (u'auth.group.G1',
['auth.p2', u'auth.group.G1', u'auth.group.G2'])
+Trying to do so does not fire an event.
+
+ >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old
+ True
+
They need not be hierarchical:
>>> ga = zope.app.authentication.groupfolder.GroupInformation("Group A")
@@ -296,6 +346,58 @@
>>> prin.groups
[]
+Member-aware groups
+-------------------
+The groupfolder includes a subscriber that gives group principals the
+zope.security.interfaces.IGroupAware interface and an implementation thereof.
+This allows groups to be able to get and set their members.
+
+Given an info object and a group...
+
+ >>> class DemoGroupInformation(object):
+ ... interface.implements(
+ ... zope.app.authentication.groupfolder.IGroupInformation)
+ ... def __init__(self, title, description, principals):
+ ... self.title = title
+ ... self.description = description
+ ... self.principals = principals
+ ...
+ >>> i = DemoGroupInformation(
+ ... 'Managers', 'Taskmasters', ('joe', 'jane'))
+ ...
+ >>> info = zope.app.authentication.groupfolder.GroupInfo(
+ ... 'groups.managers', i)
+ >>> class DummyGroup(object):
+ ... interface.implements(IGroupAwarePrincipal)
+ ... def __init__(self, id, title=u'', description=u''):
+ ... self.id = id
+ ... self.title = title
+ ... self.description = description
+ ... self.groups = []
+ ...
+ >>> principal = DummyGroup('foo')
+ >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal)
+ False
+
+...when you call the subscriber, it adds the two pseudo-methods to the
+principal and makes the principal provide the IMemberAwareGroup interface.
+
+ >>> zope.app.authentication.groupfolder.setMemberSubscriber(
+ ... interfaces.FoundPrincipalCreated(
+ ... 'dummy auth (ignored)', principal, info))
+ >>> principal.getMembers()
+ ('joe', 'jane')
+ >>> principal.setMembers(('joe', 'jane', 'jaimie'))
+ >>> principal.getMembers()
+ ('joe', 'jane', 'jaimie')
+ >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal)
+ True
+
+The two methods work with the value on the IGroupInformation object.
+
+ >>> i.principals == principal.getMembers()
+ True
+
Limitation
==========
@@ -313,7 +415,7 @@
o It is impossible to assign users from lower authentication
utilities because they can't be seen when managing the group,
- from the site cntaining the group.
+ from the site containing the group.
A better design might be to store user-role assignments independent of
the group definitions and to look for assignments during (url)
Modified: Zope3/trunk/src/zope/app/authentication/groupfolder.zcml
===================================================================
--- Zope3/trunk/src/zope/app/authentication/groupfolder.zcml 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/groupfolder.zcml 2006-01-20 04:19:08 UTC (rev 41374)
@@ -17,14 +17,14 @@
/>
</content>
- <localUtility class=".groupfolder.GroupFolder">
+ <content class=".groupfolder.GroupFolder">
<implements
interface=".groupfolder.IGroupFolder" />
<require
permission="zope.ManageServices"
interface="zope.app.container.interfaces.IContainer
zope.app.container.interfaces.INameChooser" />
- </localUtility>
+ </content>
<adapter
provides="zope.app.container.interfaces.INameChooser"
@@ -42,6 +42,8 @@
handler=".groupfolder.setGroupsForPrincipal"
/>
+ <subscriber handler=".groupfolder.setMemberSubscriber" />
+
<include package=".browser" file="groupfolder.zcml" />
<!-- Registering documentation with API doc -->
Modified: Zope3/trunk/src/zope/app/authentication/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/interfaces.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/interfaces.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -19,6 +19,7 @@
import zope.interface
import zope.schema
+import zope.security.interfaces
from zope.app.i18n import ZopeMessageFactory as _
from zope.app.security.interfaces import ILogout
from zope.app.container.constraints import contains, containers
@@ -44,16 +45,38 @@
credentialsPlugins = zope.schema.List(
title=_('Credentials Plugins'),
+ description=_("""Used for extracting credentials.
+ Names may be of ids of non-utility ICredentialsPlugins contained in
+ the IPluggableAuthentication, or names of registered
+ ICredentialsPlugins utilities. Contained non-utility ids mask
+ utility names."""),
value_type=zope.schema.Choice(vocabulary='CredentialsPlugins'),
default=[],
)
authenticatorPlugins = zope.schema.List(
title=_('Authenticator Plugins'),
+ description=_("""Used for converting credentials to principals.
+ Names may be of ids of non-utility IAuthenticatorPlugins contained in
+ the IPluggableAuthentication, or names of registered
+ IAuthenticatorPlugins utilities. Contained non-utility ids mask
+ utility names."""),
value_type=zope.schema.Choice(vocabulary='AuthenticatorPlugins'),
default=[],
)
+ def getCredentialsPlugins():
+ """Return iterable of (plugin name, actual credentials plugin) pairs.
+ Looks up names in credentialsPlugins as contained ids of non-utility
+ ICredentialsPlugins first, then as registered ICredentialsPlugin
+ utilities. Names that do not resolve are ignored."""
+
+ def getAuthenticatorPlugins():
+ """Return iterable of (plugin name, actual authenticator plugin) pairs.
+ Looks up names in authenticatorPlugins as contained ids of non-utility
+ IAuthenticatorPlugins first, then as registered IAuthenticatorPlugin
+ utilities. Names that do not resolve are ignored."""
+
prefix = zope.schema.TextLine(
title=_('Prefix'),
default=u'',
@@ -62,7 +85,7 @@
)
def logout(request):
- """Performs a logout by delegating to its authentictor plugins."""
+ """Performs a logout by delegating to its authenticator plugins."""
class ICredentialsPlugin(IPlugin):
@@ -154,7 +177,22 @@
IPluggableAuthentication.getPrincipal.
""")
+class IPrincipal(zope.security.interfaces.IGroupClosureAwarePrincipal):
+ groups = zope.schema.List(
+ title=_("Groups"),
+ description=_(
+ """ids of groups to which the principal directly belongs.
+
+ Plugins may append to this list. Mutating the list only affects
+ the life of the principal object, and does not persist (so
+ persistently adding groups to a principal should be done by working
+ with a plugin that mutates this list every time the principal is
+ created, like the group folder in this package.)
+ """),
+ value_type=zope.schema.TextLine(),
+ required=False)
+
class IPrincipalFactory(zope.interface.Interface):
"""A principal factory."""
@@ -281,3 +319,31 @@
def __repr__(self):
return "<GroupAdded %r>" % self.group.id
+
+class IPrincipalsAddedToGroup(zope.interface.Interface):
+ group_id = zope.interface.Attribute(
+ 'the id of the group to which the principal was added')
+ principal_ids = zope.interface.Attribute(
+ 'an iterable of one or more ids of principals added')
+
+class IPrincipalsRemovedFromGroup(zope.interface.Interface):
+ group_id = zope.interface.Attribute(
+ 'the id of the group from which the principal was removed')
+ principal_ids = zope.interface.Attribute(
+ 'an iterable of one or more ids of principals removed')
+
+class AbstractMembersChanged(object):
+
+ def __init__(self, principal_ids, group_id):
+ self.principal_ids = principal_ids
+ self.group_id = group_id
+
+ def __repr__(self):
+ return "<%s %r %r>" % (
+ self.__class__.__name__, sorted(self.principal_ids), self.group_id)
+
+class PrincipalsAddedToGroup(AbstractMembersChanged):
+ zope.interface.implements(IPrincipalsAddedToGroup)
+
+class PrincipalsRemovedFromGroup(AbstractMembersChanged):
+ zope.interface.implements(IPrincipalsRemovedFromGroup)
Modified: Zope3/trunk/src/zope/app/authentication/principalfolder.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/principalfolder.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/principalfolder.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -23,7 +23,6 @@
from zope.event import notify
from zope.schema import Text, TextLine, Password, Choice
from zope.publisher.interfaces import IRequest
-from zope.security.interfaces import IGroupAwarePrincipal
from zope.app import zapi
from zope.app.container.interfaces import DuplicateIDError
@@ -31,6 +30,7 @@
from zope.app.container.constraints import contains, containers
from zope.app.container.btree import BTreeContainer
from zope.app.i18n import ZopeMessageFactory as _
+from zope.app.security.interfaces import IAuthentication
from zope.app.authentication import interfaces
@@ -305,7 +305,6 @@
n += 1
yield self.prefix + value.__name__
-
class Principal(object):
"""A group-aware implementation of zope.security.interfaces.IPrincipal.
@@ -329,8 +328,77 @@
>>> 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']
"""
- interface.implements(IGroupAwarePrincipal)
+ interface.implements(interfaces.IPrincipal)
def __init__(self, id, title=u'', description=u''):
self.id = id
@@ -341,6 +409,23 @@
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.
Modified: Zope3/trunk/src/zope/app/authentication/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/tests.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/tests.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -102,6 +102,7 @@
doctest.DocTestSuite('zope.app.authentication.generic'),
doctest.DocTestSuite('zope.app.authentication.httpplugins'),
doctest.DocTestSuite('zope.app.authentication.ftpplugins'),
+ doctest.DocTestSuite('zope.app.authentication.groupfolder'),
doctest.DocFileSuite('principalfolder.txt',
setUp=placelesssetup.setUp,
tearDown=placelesssetup.tearDown),
@@ -125,6 +126,10 @@
setUp=placelesssetup.setUp,
tearDown=placelesssetup.tearDown,
),
+ doctest.DocFileSuite('vocabulary.txt',
+ setUp=placelesssetup.setUp,
+ tearDown=placelesssetup.tearDown,
+ ),
unittest.makeSuite(NonHTTPSessionTestCase),
))
Added: Zope3/trunk/src/zope/app/authentication/vocabulary.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/vocabulary.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/vocabulary.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Plugin Vocabulary.
+
+This vocabulary provides terms for authentication utility plugins.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import interface, component, i18n
+from zope.interface.interfaces import IInterface
+from zope.schema import vocabulary
+
+import zope.app.dublincore.interfaces
+from zope.app.component.interfaces import ILocalUtility
+from zope.app.i18n import ZopeMessageFactory as _
+
+from zope.app.authentication import interfaces
+
+UTILITY_TITLE = _(
+ 'zope.app.authentication.vocabulary-utility-plugin-title',
+ '${name} (a utility)')
+CONTAINED_TITLE = _(
+ 'zope.app.authentication.vocabulary-contained-plugin-title',
+ '${name} (in contents)')
+MISSING_TITLE = _(
+ 'zope.app.authentication.vocabulary-missing-plugin-title',
+ '${name} (not found; deselecting will remove)')
+
+def _pluginVocabulary(context, interface, attr_name):
+ """Vocabulary that provides names of plugins of a specified interface.
+
+ Given an interface, the options should include the unique names of all of
+ the plugins that provide the specified interface for the current context--
+ which is expected to be a pluggable authentication utility, hereafter
+ referred to as a PAU).
+
+ These plugins may be objects contained within the PAU that do not provide
+ zope.app.component.interfaces.ILocalUtility ("contained plugins"), or may
+ be utilities registered for the specified interface, found in the context
+ of the PAU ("utility plugins"). Contained plugins mask utility plugins of
+ the same name.
+
+ The vocabulary also includes the current values of the PAU even if they do
+ not correspond to a contained or utility plugin.
+ """
+ terms = {}
+ isPAU = interfaces.IPluggableAuthentication.providedBy(context)
+ if isPAU:
+ for k, v in context.items():
+ if interface.providedBy(v) and not ILocalUtility.providedBy(v):
+ dc = zope.app.dublincore.interfaces.IDCDescriptiveProperties(
+ v, None)
+ if dc is not None and dc.title:
+ title = dc.title
+ else:
+ title = k
+ terms[k] = vocabulary.SimpleTerm(
+ k, k.encode('base64').strip(), i18n.Message(
+ CONTAINED_TITLE, mapping={'name': title}))
+ utils = component.getUtilitiesFor(interface, context)
+ for nm, util in utils:
+ if nm not in terms:
+ terms[nm] = vocabulary.SimpleTerm(
+ nm, nm.encode('base64').strip(), i18n.Message(
+ UTILITY_TITLE, mapping={'name': nm}))
+ if isPAU:
+ for nm in set(getattr(context, attr_name)):
+ if nm not in terms:
+ terms[nm] = vocabulary.SimpleTerm(
+ nm, nm.encode('base64').strip(), i18n.Message(
+ MISSING_TITLE, mapping={'name': nm}))
+ return vocabulary.SimpleVocabulary(
+ [term for nm, term in sorted(terms.items())])
+
+def authenticatorPlugins(context):
+ return _pluginVocabulary(
+ context, interfaces.IAuthenticatorPlugin, 'authenticatorPlugins')
+
+def credentialsPlugins(context):
+ return _pluginVocabulary(
+ context, interfaces.ICredentialsPlugin, 'credentialsPlugins')
Added: Zope3/trunk/src/zope/app/authentication/vocabulary.txt
===================================================================
--- Zope3/trunk/src/zope/app/authentication/vocabulary.txt 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/authentication/vocabulary.txt 2006-01-20 04:19:08 UTC (rev 41374)
@@ -0,0 +1,206 @@
+The vocabulary module provides vocabularies for the authenticatorPlugins and
+the credentialsPlugins.
+
+The options should include the unique names of all of the plugins that provide
+the appropriate interface (interfaces.ICredentialsPlugin or
+interfaces.IAuthentiatorPlugin, respectively) for the current context-- which
+is expected to be a pluggable authentication utility, hereafter referred to as
+a PAU.
+
+These names may be for objects contained within the PAU that do not provide
+zope.app.component.interfaces.ILocalUtility ("contained plugins"), or may
+be utilities registered for the specified interface, found in the context
+of the PAU ("utility plugins"). Contained plugins mask utility plugins of
+the same name. They also may be names currently selected in the PAU that do
+not actually have a corresponding plugin at this time.
+
+Here is a short example of how the vocabulary should work. Let's say we're
+working with authentication plugins. We'll create some faux
+authentication plugins, and register some of them as utilities and put
+others in a faux PAU. Two of the contained plugins will provide
+zope.app.component.interfaces.ILocalUtility, and so should not be included in
+the vocabulary.
+
+ >>> from zope.app.authentication import interfaces
+ >>> from zope import interface, component
+ >>> from zope.app.component.interfaces import ILocalUtility
+ >>> class DemoPlugin(object):
+ ... interface.implements(interfaces.IAuthenticatorPlugin)
+ ... def __init__(self, name):
+ ... self.name = name
+ ...
+ >>> utility_plugins = dict(
+ ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4))
+ >>> contained_plugins = dict(
+ ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5))
+ >>> interface.directlyProvides(contained_plugins[2], ILocalUtility)
+ >>> interface.directlyProvides(contained_plugins[3], ILocalUtility)
+ >>> sorted(utility_plugins.keys())
+ [0, 1, 2, 3]
+ >>> for p in utility_plugins.values():
+ ... component.provideUtility(p, name=p.name)
+ ...
+ >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1
+ [1, 2, 3, 4]
+ >>> class DemoAuth(dict):
+ ... interface.implements(interfaces.IPluggableAuthentication)
+ ... def __init__(self, *args, **kwargs):
+ ... super(DemoAuth, self).__init__(*args, **kwargs)
+ ... self.authenticatorPlugins = (u'Plugin 3', u'Plugin X')
+ ... self.credentialsPlugins = (u'Plugin 4', u'Plugin X')
+ ...
+ >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
+
+ >>> @component.adapter(interface.Interface)
+ ... @interface.implementer(component.ISiteManager)
+ ... def getSiteManager(context):
+ ... return component.getGlobalSiteManager()
+ ...
+ >>> component.provideAdapter(getSiteManager)
+
+We are now ready to create a vocabulary that we can use. The context is
+our faux authentication utility, `auth`.
+
+ >>> from zope.app.authentication import vocabulary
+ >>> vocab = vocabulary.authenticatorPlugins(auth)
+
+Iterating over the vocabulary results in all of the terms, in a relatively
+arbitrary order of their names. (This vocabulary should typically use a
+widget that sorts values on the basis of localized collation order of the
+term titles.)
+
+ >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
+ [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4',
+ u'Plugin X']
+
+Similarly, we can use `in` to test for the presence of values in the
+vocabulary.
+
+ >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
+ [False, True, True, True, True, True, False]
+ >>> 'Plugin X' in vocab
+ True
+
+The length reports the expected value.
+
+ >>> len(vocab)
+ 6
+
+One can get a term for a given value using `getTerm()`; its token, in
+turn, should also return the same effective term from `getTermByToken`.
+
+ >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
+ ... 'Plugin X']
+ >>> for val in values:
+ ... term = vocab.getTerm(val)
+ ... assert term.value == val
+ ... term2 = vocab.getTermByToken(term.token)
+ ... assert term2.token == term.token
+ ... assert term2.value == val
+ ...
+
+The terms have titles, which are message ids that show the plugin title or id
+and whether the plugin is a utility or just contained in the auth utility.
+We'll give one of the plugins a dublin core title just to show the
+functionality.
+
+ >>> import zope.app.dublincore.interfaces
+ >>> class ISpecial(interface.Interface):
+ ... pass
+ ...
+ >>> interface.directlyProvides(contained_plugins[1], ISpecial)
+ >>> class DemoDCAdapter(object):
+ ... interface.implements(
+ ... zope.app.dublincore.interfaces.IDCDescriptiveProperties)
+ ... component.adapts(ISpecial)
+ ... def __init__(self, context):
+ ... pass
+ ... title = u'Special Title'
+ ...
+ >>> component.provideAdapter(DemoDCAdapter)
+
+We need to regenerate the vocabulary, since it calculates all of its data at
+once.
+
+ >>> vocab = vocabulary.authenticatorPlugins(auth)
+
+Now we'll check the titles. We'll have to translate them to see what we
+expect.
+
+ >>> from zope import i18n
+ >>> import pprint
+ >>> pprint.pprint([i18n.translate(term.title) for term in vocab])
+ [u'Plugin 0 (a utility)',
+ u'Special Title (in contents)',
+ u'Plugin 2 (a utility)',
+ u'Plugin 3 (a utility)',
+ u'Plugin 4 (in contents)',
+ u'Plugin X (not found; deselecting will remove)']
+
+credentialsPlugins
+-----
+
+For completeness, we'll do the same review of the credentialsPlugins.
+
+ >>> class DemoPlugin(object):
+ ... interface.implements(interfaces.ICredentialsPlugin)
+ ... def __init__(self, name):
+ ... self.name = name
+ ...
+ >>> utility_plugins = dict(
+ ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4))
+ >>> contained_plugins = dict(
+ ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5))
+ >>> interface.directlyProvides(contained_plugins[2], ILocalUtility)
+ >>> interface.directlyProvides(contained_plugins[3], ILocalUtility)
+ >>> for p in utility_plugins.values():
+ ... component.provideUtility(p, name=p.name)
+ ...
+ >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
+ >>> vocab = vocabulary.credentialsPlugins(auth)
+
+Iterating over the vocabulary results in all of the terms, in a relatively
+arbitrary order of their names. (This vocabulary should typically use a
+widget that sorts values on the basis of localized collation order of the term
+titles.) Similarly, we can use `in` to test for the presence of values in the
+vocabulary. The length reports the expected value.
+
+ >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
+ [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4',
+ u'Plugin X']
+ >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
+ [False, True, True, True, True, True, False]
+ >>> 'Plugin X' in vocab
+ True
+ >>> len(vocab)
+ 6
+
+One can get a term for a given value using `getTerm()`; its token, in
+turn, should also return the same effective term from `getTermByToken`.
+
+ >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
+ ... 'Plugin X']
+ >>> for val in values:
+ ... term = vocab.getTerm(val)
+ ... assert term.value == val
+ ... term2 = vocab.getTermByToken(term.token)
+ ... assert term2.token == term.token
+ ... assert term2.value == val
+ ...
+
+The terms have titles, which are message ids that show the plugin title or id
+and whether the plugin is a utility or just contained in the auth utility.
+We'll give one of the plugins a dublin core title just to show the
+functionality. We need to regenerate the vocabulary, since it calculates all
+of its data at once. Then we'll check the titles. We'll have to translate
+them to see what we expect.
+
+ >>> interface.directlyProvides(contained_plugins[1], ISpecial)
+ >>> vocab = vocabulary.credentialsPlugins(auth)
+ >>> pprint.pprint([i18n.translate(term.title) for term in vocab])
+ [u'Plugin 0 (a utility)',
+ u'Special Title (in contents)',
+ u'Plugin 2 (a utility)',
+ u'Plugin 3 (a utility)',
+ u'Plugin 4 (in contents)',
+ u'Plugin X (not found; deselecting will remove)']
Property changes on: Zope3/trunk/src/zope/app/authentication/vocabulary.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/zopeappgenerations/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/zopeappgenerations/__init__.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/zopeappgenerations/__init__.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -24,7 +24,7 @@
ZopeAppSchemaManager = SchemaManager(
minimum_generation=0,
- generation=2,
+ generation=3,
package_name=key)
Added: Zope3/trunk/src/zope/app/zopeappgenerations/evolve3.py
===================================================================
--- Zope3/trunk/src/zope/app/zopeappgenerations/evolve3.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/app/zopeappgenerations/evolve3.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -0,0 +1,95 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Evolve existing PAU group folders.
+
+They should be used as contained plugins rather than registered plugins.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import component
+
+from zope.app.component.interfaces import ISite
+from zope.app.zopeappgenerations import getRootFolder
+
+from zope.app.generations.utility import findObjectsProviding
+
+from zope.app.component import registration
+import zope.app.authentication.interfaces
+from zope.app.authentication import groupfolder
+from zope.app.copypastemove.interfaces import IObjectMover
+
+generation = 3
+
+def evolve(context):
+ """Evolve existing PAUs and group folders.
+
+ - Group folders should no longer be registered.
+
+ - PAUs that use group folders should use their contents name, not their
+ (formerly) registered name.
+
+ Group folders used by multiple PAUs were not supported, and are not
+ supported with this evolution.
+ """
+ root = getRootFolder(context)
+
+ for site in findObjectsProviding(root, ISite):
+ sm = site.getSiteManager()
+ for pau in findObjectsProviding(
+ sm, zope.app.authentication.interfaces.IPluggableAuthentication):
+ for nm, util in component.getUtilitiesFor(
+ zope.app.authentication.interfaces.IAuthenticatorPlugin,
+ context=pau):
+ if groupfolder.IGroupFolder.providedBy(util):
+ if util.__parent__ is not pau:
+ raise RuntimeError(
+ "I don't know how to migrate your database: "
+ "each group folder should only be within the "
+ "Pluggable Authentication utility that uses it")
+ # we need to remove this registration
+ regs = registration.Registered(util).registrations()
+ if len(regs) != 1:
+ raise RuntimeError(
+ "I don't know how to migrate your database: "
+ "you should only have registered your group "
+ "folder as an IAuthenticatorPlugin, but it looks "
+ "like it's registered for something additional "
+ "that I don't expect")
+ r = regs[0]
+ r.getRegistry().unregister(r)
+ if r.name in pau.authenticatorPlugins:
+ if util.__name__ != r.name: # else no-op
+ plugins = list(pau.authenticatorPlugins)
+ if util.__name__ in pau.authenticatorPlugins:
+ # argh! another active plugin's name is
+ # the same as this group folder's
+ # __name__. That means we need to choose
+ # a new name that is also not in
+ # authenticatorPlugins and not in
+ # pau.keys()...
+ ct = 0
+ nm = '%s_%d' % (util.__name__, ct)
+ while (nm in pau.authenticatorPlugins and
+ nm in pau):
+ ct += 1
+ nm = '%s_%d' % (util.__name__, ct)
+ IObjectMover(util).moveTo(pau, nm)
+ plugins[plugins.index(r.name)] = util.__name__
+ pau.authenticatorPlugins = tuple(plugins)
+ for k, r in pau.registrationManager.items():
+ if groupfolder.IGroupFolder.providedBy(r.component):
+ del pau.registrationManager[k]
+
Modified: Zope3/trunk/src/zope/security/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/security/interfaces.py 2006-01-20 04:14:38 UTC (rev 41373)
+++ Zope3/trunk/src/zope/security/interfaces.py 2006-01-20 04:19:08 UTC (rev 41374)
@@ -277,16 +277,30 @@
Extends IPrincipal to contain group information.
"""
- groups = List(
- title=_("Groups"),
- description=_("List of ids of groups the principal belongs to"),
- value_type=TextLine(),
- required=False)
+ groups = Attribute(
+ 'An iterable of groups to which the principal directly belongs')
+class IGroupClosureAwarePrincipal(IGroupAwarePrincipal):
+
+ allGroups = Attribute(
+ "An iterable of the full closure of the principal's groups.")
+
class IGroup(IPrincipal):
"""Group of principals
"""
-
+
+class IMemberGetterGroup(IGroup):
+ """a group that can get its members"""
+
+ def getMembers():
+ """return an iterable of the members of the group"""
+
+class IMemberAwareGroup(IMemberGetterGroup):
+ """a group that can both set and get its members."""
+
+ def setMembers(value):
+ """set members of group to the principal ids in the iterable value"""
+
class IPermission(Interface):
"""A permission object."""
More information about the Zope3-Checkins
mailing list