[Zope3-checkins] SVN: Zope3/trunk/ Refactored the group-folder implentation:

Jim Fulton jim at zope.com
Mon Nov 1 14:03:56 EST 2004


Log message for revision 28311:
  Refactored the group-folder implentation:
  
  - Moved into pas
  
  - Changed the strategy to be consistent with principals. We no longer
    store groups persistently, but create them when needed. This was
    necessary to make them work properly with the other principal apis.
  
  - Don't store group ids in the group information.
  
  - Added group-folder prefix, to make sure group ids don't overlap
    with other ids.
  
  - Added a documentation file and a functional test/
  

Changed:
  D   Zope3/trunk/package-includes/groupscontainer-configure.zcml
  D   Zope3/trunk/src/zope/app/groupfolder/
  U   Zope3/trunk/src/zope/app/pas/browser/configure.zcml
  U   Zope3/trunk/src/zope/app/pas/browser/ftests.py
  A   Zope3/trunk/src/zope/app/pas/browser/groupfolder.txt
  A   Zope3/trunk/src/zope/app/pas/browser/groupfolder.zcml
  U   Zope3/trunk/src/zope/app/pas/configure.zcml
  A   Zope3/trunk/src/zope/app/pas/groupfolder.py
  A   Zope3/trunk/src/zope/app/pas/groupfolder.txt
  A   Zope3/trunk/src/zope/app/pas/groupfolder.zcml
  U   Zope3/trunk/src/zope/app/pas/tests.py

-=-
Deleted: Zope3/trunk/package-includes/groupscontainer-configure.zcml
===================================================================
--- Zope3/trunk/package-includes/groupscontainer-configure.zcml	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/package-includes/groupscontainer-configure.zcml	2004-11-01 19:03:56 UTC (rev 28311)
@@ -1 +0,0 @@
-<include package="zope.app.groupfolder"/>

Modified: Zope3/trunk/src/zope/app/pas/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/configure.zcml	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/browser/configure.zcml	2004-11-01 19:03:56 UTC (rev 28311)
@@ -115,6 +115,8 @@
       description="PAS Credential Extraction and Challenge Plugin"
       />
 
+<include file="groupfolder.zcml" />
+
 <!-- Challengers -->
   
   <addMenuItem

Modified: Zope3/trunk/src/zope/app/pas/browser/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/ftests.py	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/browser/ftests.py	2004-11-01 19:03:56 UTC (rev 28311)
@@ -21,6 +21,7 @@
     from zope.app.tests import functional
     return unittest.TestSuite((
         functional.FunctionalDocFileSuite('principalfolder.txt'),
+        functional.FunctionalDocFileSuite('groupfolder.txt'),
         ))
 
 if __name__ == '__main__':

Added: Zope3/trunk/src/zope/app/pas/browser/groupfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/groupfolder.txt	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/browser/groupfolder.txt	2004-11-01 19:03:56 UTC (rev 28311)
@@ -0,0 +1,634 @@
+Using Group Folders
+===================
+
+Group folders are used to define groups.  Before you can define
+groups, you have to create a group folder and configure it in a
+pluggable authentication service (PAS). The group folder has to be
+registered with a PAS before defining any groups.  This is because the
+groups folder needs to use the PAS to find all of the groups
+containing a given group so that it can check for group cycles. Not
+all of a group's groups need to be defined in it's group folder. Other
+groups folders or group-defining plugins could define groups for a
+group.
+
+Let's walk through an example.
+
+First, we'll create a principal folder:
+
+  (The first 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
+  ...
+
+Next we'l add some users:
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 784
+  ... Content-Type: multipart/form-data; boundary=---------------------------62010169718836874861388307181
+  ... 
+  ... -----------------------------62010169718836874861388307181
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... bob
+  ... -----------------------------62010169718836874861388307181
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------62010169718836874861388307181
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Bob
+  ... -----------------------------62010169718836874861388307181
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------62010169718836874861388307181
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------62010169718836874861388307181
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------62010169718836874861388307181--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 779
+  ... Content-Type: multipart/form-data; boundary=---------------------------1501629520183211901834390790
+  ... 
+  ... -----------------------------1501629520183211901834390790
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... bill
+  ... -----------------------------1501629520183211901834390790
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------1501629520183211901834390790
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Bill
+  ... -----------------------------1501629520183211901834390790
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------1501629520183211901834390790
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------1501629520183211901834390790
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------1501629520183211901834390790--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 781
+  ... Content-Type: multipart/form-data; boundary=---------------------------3362827831346173768318792608
+  ... 
+  ... -----------------------------3362827831346173768318792608
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... betty
+  ... -----------------------------3362827831346173768318792608
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------3362827831346173768318792608
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Betty
+  ... -----------------------------3362827831346173768318792608
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------3362827831346173768318792608
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------3362827831346173768318792608
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------3362827831346173768318792608--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 781
+  ... Content-Type: multipart/form-data; boundary=---------------------------1771586876978613244952985501
+  ... 
+  ... -----------------------------1771586876978613244952985501
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... sally
+  ... -----------------------------1771586876978613244952985501
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------1771586876978613244952985501
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Sally
+  ... -----------------------------1771586876978613244952985501
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------1771586876978613244952985501
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------1771586876978613244952985501
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------1771586876978613244952985501--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 783
+  ... Content-Type: multipart/form-data; boundary=---------------------------6406512534224572322062554722
+  ... 
+  ... -----------------------------6406512534224572322062554722
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... george
+  ... -----------------------------6406512534224572322062554722
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------6406512534224572322062554722
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... George
+  ... -----------------------------6406512534224572322062554722
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------6406512534224572322062554722
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------6406512534224572322062554722
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------6406512534224572322062554722--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 779
+  ... Content-Type: multipart/form-data; boundary=---------------------------1596878616204415667781266350
+  ... 
+  ... -----------------------------1596878616204415667781266350
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... mike
+  ... -----------------------------1596878616204415667781266350
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------1596878616204415667781266350
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Mike
+  ... -----------------------------1596878616204415667781266350
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------1596878616204415667781266350
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------1596878616204415667781266350
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------1596878616204415667781266350--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/users/+/AddPrincipalInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 793
+  ... Content-Type: multipart/form-data; boundary=---------------------------160587971417390263241080578782
+  ... 
+  ... -----------------------------160587971417390263241080578782
+  ... Content-Disposition: form-data; name="field.login"
+  ... 
+  ... mary
+  ... -----------------------------160587971417390263241080578782
+  ... Content-Disposition: form-data; name="field.password"
+  ... 
+  ... 123
+  ... -----------------------------160587971417390263241080578782
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Mary
+  ... -----------------------------160587971417390263241080578782
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------160587971417390263241080578782
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------160587971417390263241080578782
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------160587971417390263241080578782--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/users/@@contents.html
+  ...
+
+Next, we'll add out groups folder:
+
+  >>> print http(r"""
+  ... POST /++etc++site/AddIPrincipalSearchPluginTool/AddGroupFolder.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 432
+  ... Content-Type: multipart/form-data; boundary=---------------------------18984415031531709165482618952
+  ... 
+  ... -----------------------------18984415031531709165482618952
+  ... Content-Disposition: form-data; name="field.prefix"
+  ... 
+  ... groups.
+  ... -----------------------------18984415031531709165482618952
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------18984415031531709165482618952
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... groups
+  ... -----------------------------18984415031531709165482618952--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: ../@@manageIPrincipalSearchPluginTool.html
+  ...
+
+Now, before we can define any groups, we have to add a PAS:
+
+  >>> 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
+  ... 
+  ... 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 and the groups folder:
+
+
+  >>> print http(r"""
+  ... POST /++etc++site/default/LocalPAS/@@edit.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 2073
+  ... Content-Type: multipart/form-data; boundary=---------------------------18023914511159666166636904990
+  ... 
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.extractors.to"
+  ... 
+  ... HTTP Basic
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.authenticators.to"
+  ... 
+  ... users
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.challengers.to"
+  ... 
+  ... No Challenge if Authenticated
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.challengers.to"
+  ... 
+  ... Zope Realm HTTP Basic
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.factories.to"
+  ... 
+  ... Default
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.searchers.to"
+  ... 
+  ... users
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.searchers.to"
+  ... 
+  ... groups
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Change
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.extractors"
+  ... 
+  ... HTTP Basic
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.authenticators"
+  ... 
+  ... users
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.challengers"
+  ... 
+  ... No Challenge if Authenticated
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.challengers"
+  ... 
+  ... Zope Realm HTTP Basic
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.factories"
+  ... 
+  ... Default
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.searchers"
+  ... 
+  ... users
+  ... -----------------------------18023914511159666166636904990
+  ... Content-Disposition: form-data; name="field.searchers"
+  ... 
+  ... groups
+  ... -----------------------------18023914511159666166636904990--
+  ... """)
+  HTTP/1.1 200 Ok
+  ...
+
+Now, we can define some groups.  Let's start with a group named
+"Admin":
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/groups/+/AddGroupInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 540
+  ... Content-Type: multipart/form-data; boundary=---------------------------5412502961004181070544094984
+  ... 
+  ... -----------------------------5412502961004181070544094984
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Admin
+  ... -----------------------------5412502961004181070544094984
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------5412502961004181070544094984
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------5412502961004181070544094984
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... 
+  ... -----------------------------5412502961004181070544094984--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/groups/@@contents.html
+  ...
+
+That includes Betty, Mary and Mike:
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/groups/1/@@edit.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 1426
+  ... Content-Type: multipart/form-data; boundary=---------------------------67523504021030130962010243745
+  ... Referer: http://localhost:8081/++etc++site/tools/groups/1/@@edit.html
+  ... 
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Admin
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuMw__
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuNw__
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuNg__
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals.displayed"
+  ... 
+  ... y
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search"
+  ... 
+  ... 
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search"
+  ... 
+  ... 
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="field.principals.MA__.query.searchstring"
+  ... 
+  ... 
+  ... -----------------------------67523504021030130962010243745
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Change
+  ... -----------------------------67523504021030130962010243745--
+  ... """)
+  HTTP/1.1 200 Ok
+  ...
+
+and a group "Power Users":
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/groups/+/AddGroupInformation.html%3D HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 556
+  ... Content-Type: multipart/form-data; boundary=---------------------------14430301351028860873795053640
+  ... 
+  ... -----------------------------14430301351028860873795053640
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Power Users
+  ... -----------------------------14430301351028860873795053640
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------14430301351028860873795053640
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Add
+  ... -----------------------------14430301351028860873795053640
+  ... Content-Disposition: form-data; name="add_input_name"
+  ... 
+  ... power
+  ... -----------------------------14430301351028860873795053640--
+  ... """)
+  HTTP/1.1 303 See Other
+  ...
+  Location: http://localhost/++etc++site/tools/groups/@@contents.html
+  ...
+
+with users Betty, Bill, Bob, George, and Mary:
+
+  >>> print http(r"""
+  ... POST /++etc++site/tools/groups/power/@@edit.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Length: 1708
+  ... Content-Type: multipart/form-data; boundary=---------------------------46600477014278930691159535998
+  ... 
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.title"
+  ... 
+  ... Power Users
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.description"
+  ... 
+  ... 
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuMw__
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuMg__
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuMQ__
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuNQ__
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals:list"
+  ... 
+  ... dXNlcnMuNw__
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals.displayed"
+  ... 
+  ... y
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search"
+  ... 
+  ... 
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search"
+  ... 
+  ... 
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="field.principals.MA__.query.searchstring"
+  ... 
+  ... 
+  ... -----------------------------46600477014278930691159535998
+  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
+  ... 
+  ... Change
+  ... -----------------------------46600477014278930691159535998--
+  ... """)
+  HTTP/1.1 200 Ok
+  ...
+
+Now, with these groups set up, we should see these groups on the
+affected principals.  First, we'll make the root folder the
+thread-local site:
+
+  >>> from zope.app.component.hooks import setSite
+  >>> setSite(getRootFolder())
+
+and we'll get the PAS:
+
+  >>> from zope.app import zapi
+  >>> principals = zapi.principals()
+
+Finally we'll get Betty and see that she is in the admin and
+power-user groups:
+
+  >>> betty = principals.getPrincipal(u'users.3')
+  >>> betty.groups.sort()
+  >>> betty.groups
+  [u'groups.1', u'groups.power']
+
+And we'll get Bill, and see that he is only in the power-user group:
+
+  >>> bill = principals.getPrincipal(u'users.2')
+  >>> bill.groups
+  [u'groups.power']

Added: Zope3/trunk/src/zope/app/pas/browser/groupfolder.zcml
===================================================================
--- Zope3/trunk/src/zope/app/pas/browser/groupfolder.zcml	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/browser/groupfolder.zcml	2004-11-01 19:03:56 UTC (rev 28311)
@@ -0,0 +1,62 @@
+<configure 
+    xmlns='http://namespaces.zope.org/browser'
+    i18n_domain="zope"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    >
+  
+<editform
+    schema="..groupfolder.IGroupInformation"
+    label="Change group information"
+    name="edit.html"
+    menu="zmi_views" title="Edit"
+    permission="zope.ManageServices"
+    />
+  
+<addform
+    schema="..groupfolder.IGroupInformation"
+    content_factory="..groupfolder.GroupInformation"
+    label="Add group information"
+    name="AddGroupInformation.html"
+    permission="zope.ManageServices"
+    fields="title description"
+    />
+    
+<addMenuItem
+    title="Group"
+    description="A principals group"
+    class="..groupfolder.GroupInformation"
+    permission="zope.ManageServices"
+    view="AddGroupInformation.html"
+    />
+  
+<tool
+    interface="..groupfolder.IGroupFolder"
+    title="Groups Folder"
+    description="Groups Folder"
+    />
+  
+<addform
+    schema="..groupfolder.IGroupFolder"
+    content_factory="..groupfolder.GroupFolder"
+    arguments="prefix"
+    label="Add group folder"
+    name="AddGroupFolder.html"
+    permission="zope.ManageServices"
+    />
+
+<addMenuItem
+    title="Group Folder"
+    description="A Group folder"
+    class="..groupfolder.GroupFolder"
+    permission="zope.ManageServices"
+    view="AddGroupFolder.html"
+    />
+      
+<containerViews
+     for="..groupfolder.IGroupFolder"
+     contents="zope.ManageServices"
+     index="zope.ManageServices"
+     add="zope.ManageServices"
+     />
+      
+</configure> 


Property changes on: Zope3/trunk/src/zope/app/pas/browser/groupfolder.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/app/pas/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/pas/configure.zcml	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/configure.zcml	2004-11-01 19:03:56 UTC (rev 28311)
@@ -73,6 +73,7 @@
   <include file="challengeplugins.zcml" />
   <include file="principalplugins.zcml" />
   <include file="authenticationplugins.zcml" />
+  <include file="groupfolder.zcml" />
 
   <include package=".browser" />
   

Copied: Zope3/trunk/src/zope/app/pas/groupfolder.py (from rev 28240, Zope3/trunk/src/zope/app/groupfolder/groupfolder.py)
===================================================================
--- Zope3/trunk/src/zope/app/groupfolder/groupfolder.py	2004-10-22 19:14:37 UTC (rev 28240)
+++ Zope3/trunk/src/zope/app/pas/groupfolder.py	2004-11-01 19:03:56 UTC (rev 28311)
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Zope Groups Folder implementation
+
+$Id: groupfolder.py 27237 2004-10-12 09:33:00 mriya3 $
+
+"""
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from BTrees.OOBTree import OOBTree
+from persistent import Persistent
+
+import zope.interface
+import zope.schema
+
+from zope.app import zapi
+from zope.app.container.btree import BTreeContainer
+import zope.app.container.constraints
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.i18n import ZopeMessageIDFactory as _
+from zope.app.pas.interfaces import IAuthenticatedPrincipalCreated
+from zope.app.pas.interfaces import IQuerySchemaSearch, IPrincipalSearchPlugin
+import zope.app.security.vocabulary
+        
+class IGroupInformation(zope.interface.Interface):
+
+    title = zope.schema.TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the permission."),
+        required=True)
+
+    description = zope.schema.Text(
+        title=_("Description"),
+        description=_("Provides a description for the permission."),
+        required=False)
+
+    principals = zope.schema.List(
+        title=_("Principals"),
+        value_type=zope.schema.Choice(
+            source=zope.app.security.vocabulary.PrincipalSource()),
+        description=_(
+        "List of principal ids of principals which belong to the group"),
+        required=False)
+        
+class IGroupFolder(IContainer, IQuerySchemaSearch):
+
+    zope.app.container.constraints.contains(IGroupInformation)
+
+    prefix = zope.schema.TextLine(
+        title=u"Group ID prefix",
+        description=u"Prefix added to is of groups in this folder",
+        readonly=True,
+        )
+       
+    def getGroupsForPrincipal(principalid):
+        """Get groups the given principal belongs to"""
+        
+    def getPrincipalsForGroup(groupid):
+        """Get principals which belong to the group"""
+        
+class IGroupContained(IContained):
+
+    zope.app.container.constraints.containers(IGroupFolder)
+             
+
+class IGroupSearchCriteria(zope.interface.Interface):
+    search = zope.schema.TextLine(title=u"Group Search String")
+
+
+class GroupFolder(BTreeContainer):
+
+    zope.interface.implements(IGroupFolder)
+    schema = (IGroupSearchCriteria)
+    
+    def __init__(self, prefix=u''):
+        self.prefix=prefix
+        super(BTreeContainer,self).__init__()
+        # __inversemapping is used to map principals to groups
+        self.__inverseMapping = OOBTree()
+
+    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)
+
+    def __delitem__(self, name):
+        value = self[name]
+        group_id = self._groupid(value)
+        for principal_id in value.principals:
+            self._removePrincipalFromGroup(principal_id, 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 _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 getGroupsForPrincipal(self, principalid):
+        """Get groups the given principal belongs to"""
+        return self.__inverseMapping.get(principalid, ())
+        
+
+    def search(self, query, start=None, batch_size=None):
+        """ Search for groups"""
+        search = query.get('search').lower()
+        if search is not None:
+            i = 0
+            n = 0
+            for id, groupinfo in self.items():
+                if (search in groupinfo.title.lower() or
+                    search in groupinfo.description.lower()):
+                    if not ((start is not None and i < start)
+                            or
+                            (batch_size is not None and n > batch_size)):
+                        n += 1
+                        yield self.prefix+id
+                i += 1
+        
+    def principalInfo(self, id):
+        if id.startswith(self.prefix):
+            id = id[len(self.prefix):]
+            info = self.get(id)
+            if info is not None:
+                return {'title': info.title,
+                        'description': info.description,
+                        }
+
+class GroupCycle(Exception):
+    """There is a cyclic relationship among groups
+    """
+
+class InvalidPrincipalIds(Exception):
+    """A user has a group id for a group that can't be found
+    """
+
+class InvalidGroupId(Exception):
+    """A user has a group id for a group that can't be found
+    """
+
+def nocycles(principal_ids, seen=None, getPrincipal=None):
+    if seen is None:
+        seen = {}
+        getPrincipal = zapi.principals().getPrincipal
+
+    for principal_id in principal_ids:
+        if principal_id in seen:
+            raise GroupCycle(principal_id)
+        seen[principal_id] = 1
+
+    for principal_id in principal_ids:
+        principal = getPrincipal(principal_id)
+        if principal is None:
+            raise InvalidGroupId(principal_id)
+
+        nocycles(principal.groups, seen, getPrincipal)
+
+class GroupInformation(Persistent):
+
+    zope.interface.implements(IGroupInformation, IGroupContained)
+    
+    __parent__ = __name__ = None
+
+    _principals = ()
+    
+    def __init__(self, title='', description=''):
+        self.title = title
+        self.description = description
+        
+    def setPrincipals(self, prinlist):
+        parent = self.__parent__
+        if parent is not None:
+            old = set(self._principals)
+            new = set(prinlist)
+            group_id = parent._groupid(self)
+            
+            for principal_id in old - new:
+                try:
+                    parent._removePrincipalFromGroup(principal_id, group_id)
+                except AttributeError:
+                    pass
+
+            for principal_id in new - old:
+                try:
+                    parent._addPrincipalToGroup(principal_id, group_id)
+                except AttributeError:
+                    pass
+
+            nocycles((group_id, ))
+
+        self._principals = tuple(prinlist)
+        
+    principals = property(lambda self: self._principals, setPrincipals)
+
+def setGroupsForPrincipal(event):
+    """Set group information when a principal is created"""
+
+    principal = event.principal
+    try:
+        groups = principal.groups
+    except AttributeError:
+        # If the principal doesn't support groups. then do nothing
+        return
+    
+    groupfolders = zapi.getUtilitiesFor(IPrincipalSearchPlugin)
+    for name, groupfolder in groupfolders:
+        # It's annoying that we have to filter here, but there isn't
+        # a good reason for people to register group folder utilities.
+        if not isinstance(groupfolder, GroupFolder):
+            continue
+        principal.groups.extend(
+            groupfolder.getGroupsForPrincipal(principal.id),
+            )

Added: Zope3/trunk/src/zope/app/pas/groupfolder.txt
===================================================================
--- Zope3/trunk/src/zope/app/pas/groupfolder.txt	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/groupfolder.txt	2004-11-01 19:03:56 UTC (rev 28311)
@@ -0,0 +1,139 @@
+Group Folders
+=============
+
+Group folders provide support for groups information stored in the
+ZODB.
+
+Like other principals, groups are created when they are needed.
+
+Group folders contain group-information objects that contain group
+information.  We create group information using the `GroupInformation` class:
+
+  >>> import zope.app.pas.groupfolder
+  >>> g1 = zope.app.pas.groupfolder.GroupInformation("Group 1")
+
+  >>> groups = zope.app.pas.groupfolder.GroupFolder('group.')
+  >>> groups['g1'] = g1
+
+Groups are defined with respect to an authentication service.  Groups
+must be accessable via an authentication service and can contain
+principals accessable via an authentication service.
+
+To illustrate the group interaction with the authentication service,
+we'll create a sample authentication service:
+
+  >>> import zope.interface
+  >>> from zope.app.security.interfaces import IAuthenticationService
+  >>> from zope.app.pas.groupfolder import setGroupsForPrincipal
+
+  >>> class Principal:
+  ...     def __init__(self, id, title, description):
+  ...         self.id, self.title, self.description = id, title, description
+  ...         self.groups = []
+
+  >>> class PrincipalCreatedEvent:
+  ...     def __init__(self, principal):
+  ...         self.principal = principal
+
+  >>> class Principals:
+  ...
+  ...     zope.interface.implements(IAuthenticationService)
+  ...
+  ...     def __init__(self, groups):
+  ...         self.principals = {
+  ...            'p1': {'title': '', 'description': ''},
+  ...            'p2': {'title': '', 'description': ''},
+  ...            }
+  ...         self.groups = groups
+  ...
+  ...     def getPrincipal(self, id):
+  ...         info = self.principals.get(id)
+  ...         if info is None:
+  ...             info = self.groups.principalInfo(id)
+  ...             if info is None:
+  ...                return None
+  ...         principal = Principal(id, **info)
+  ...         setGroupsForPrincipal(PrincipalCreatedEvent(principal))
+  ...         return principal
+
+This class doesn't really implement the fill `IAuthenticationService`
+interface, but it implements the `getPrincipal` method used by groups.
+It works very much like PAS.  It creates principals on demand.  It
+calls `setGroupsForPrincipal`, which is normally called as an event
+subscriber, when principals are created.  In order for
+`setGroupsForPrincipal` to find out group folder, we have to register
+it as a utility:
+
+  >>> from zope.app.tests import ztapi
+  >>> ztapi.provideUtility(zope.app.pas.groupfolder.IGroupFolder,
+  ...                      groups)
+
+The authentication service has a very simple implementation.  It has a
+`principals` dictionary and a groups folder.
+
+  >>> principals = Principals(groups)
+  >>> ztapi.provideService('Authentication', principals)
+
+Now we can set the principals on the group:
+
+  >>> g1.principals = ['p1', 'p2']
+  >>> g1.principals
+  ('p1', 'p2')
+
+This allows us to look up groups for the principals:
+
+  >>> groups.getGroupsForPrincipal('p1')
+  (u'group.g1',)
+
+Note that the group id is a concatination of the group-folder prefix
+and the name of the group-information object within the folder.
+
+If we delete a group:
+
+  >>> del groups['g1']
+
+then the groups folder loses the group information for that group's
+principals: 
+
+  >>> groups.getGroupsForPrincipal('p1')
+  ()
+
+but the principal information on the group is unchanged:
+
+  >>> g1.principals
+  ('p1', 'p2')
+
+Adding the group sets the folder principal information.  Let's use a
+different group name:
+
+  >>> groups['G1'] = g1
+
+  >>> groups.getGroupsForPrincipal('p1')
+  (u'group.G1',)
+
+Here we see that the new name is reflected in the group information.
+
+Groups can contain groups:
+
+  >>> g2 = zope.app.pas.groupfolder.GroupInformation("Group Two")
+  >>> groups['G2'] = g2
+  >>> g2.principals = ['group.G1']
+
+  >>> groups.getGroupsForPrincipal('group.G1')
+  (u'group.G2',)
+
+But, of course, they cannot contain cycles:
+
+  >>> g1.principals = ('p1', 'p2', 'group.G2')
+  Traceback (most recent call last):
+  ...
+  GroupCycle: group.G1
+
+Group folders provide a very simple search interface.  They perform
+simple string searches on group titles and descriptions.
+
+  >>> list(groups.search({'search': 'grou'}))
+  [u'group.G1', u'group.G2']
+
+  >>> list(groups.search({'search': 'two'}))
+  [u'group.G2']


Property changes on: Zope3/trunk/src/zope/app/pas/groupfolder.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: Zope3/trunk/src/zope/app/pas/groupfolder.zcml (from rev 28240, Zope3/trunk/src/zope/app/groupfolder/configure.zcml)
===================================================================
--- Zope3/trunk/src/zope/app/groupfolder/configure.zcml	2004-10-22 19:14:37 UTC (rev 28240)
+++ Zope3/trunk/src/zope/app/pas/groupfolder.zcml	2004-11-01 19:03:56 UTC (rev 28311)
@@ -0,0 +1,42 @@
+<configure 
+    xmlns='http://namespaces.zope.org/zope'
+    xmlns:browser='http://namespaces.zope.org/browser'
+    i18n_domain="zope"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    >
+      
+<content class=".groupfolder.GroupInformation">
+    <require
+        permission="zope.ManageServices"
+        interface=".groupfolder.IGroupInformation 
+                   .groupfolder.IGroupContained"
+        set_schema=".groupfolder.IGroupInformation"
+        />
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+</content>
+    
+<subscriber
+    factory=".groupfolder.setGroupsForPrincipal"
+    for="zope.app.pas.interfaces.IPASPrincipalCreated"
+    />
+
+<localUtility class=".groupfolder.GroupFolder">
+  <implements
+      interface=".groupfolder.IGroupFolder" />
+  <implements
+      interface="zope.app.annotation.IAttributeAnnotatable" />
+  <require
+      permission="zope.ManageServices"
+      interface="zope.app.container.interfaces.IContainer
+                 zope.app.container.interfaces.INameChooser" />
+</localUtility>
+
+<adapter
+    provides="zope.app.container.interfaces.INameChooser"
+    for=".groupfolder.IGroupFolder"
+    factory=".idpicker.IdPicker"
+    />
+      
+</configure> 

Modified: Zope3/trunk/src/zope/app/pas/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/pas/tests.py	2004-11-01 19:03:54 UTC (rev 28310)
+++ Zope3/trunk/src/zope/app/pas/tests.py	2004-11-01 19:03:56 UTC (rev 28311)
@@ -16,14 +16,19 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
+
 import unittest
+
 from zope.testing import doctest
+from zope.interface import implements
+from zope.publisher.interfaces import IRequest
+from zope.publisher.tests.httprequest import TestRequest
+
+from zope.app import zapi
 from zope.app.tests import placelesssetup, ztapi
 from zope.app.event.tests.placelesssetup import getEvents
 from zope.app.tests.setup import placefulSetUp, placefulTearDown
-from zope.interface import implements
-from zope.app.zapi import getUtility
-
+from zope.app.security.interfaces import IAuthenticationService
 from zope.app.session.interfaces import \
         IClientId, IClientIdManager, ISession, ISessionDataContainer, \
         ISessionPkgData, ISessionData
@@ -32,8 +37,6 @@
         PersistentSessionDataContainer, RAMSessionDataContainer
 from zope.app.session.http import CookieClientIdManager
 
-from zope.publisher.interfaces import IRequest
-from zope.publisher.tests.httprequest import TestRequest
 
 class TestClientId(object):
     implements(IClientId)
@@ -54,6 +57,13 @@
 def formAuthTearDown(self):
     placefulTearDown()
 
+def groupSetUp(test):
+    placelesssetup.setUp()
+    services = zapi.getGlobalServices()
+    services.defineService(zapi.servicenames.Authentication,
+                           IAuthenticationService)
+
+
 def test_suite():
     return unittest.TestSuite((
         doctest.DocTestSuite('zope.app.pas.generic'),
@@ -70,6 +80,11 @@
                              globs={'provideUtility': ztapi.provideUtility,
                                     'getEvents': getEvents,
                                     }),
+        doctest.DocFileSuite('groupfolder.txt',
+                             setUp=groupSetUp,
+                             tearDown=placelesssetup.tearDown,
+                             ),
+        
         ))
 
 if __name__ == '__main__':



More information about the Zope3-Checkins mailing list