[Zope-CVS] SVN: zc.sharing/trunk/ import code

Jim Fulton jim at zope.com
Wed Mar 1 14:14:49 EST 2006


Log message for revision 65678:
  import code

Changed:
  A   zc.sharing/trunk/
  A   zc.sharing/trunk/src/
  A   zc.sharing/trunk/src/zc/
  A   zc.sharing/trunk/src/zc/sharing/
  A   zc.sharing/trunk/src/zc/sharing/__init__.py
  A   zc.sharing/trunk/src/zc/sharing/browser/
  A   zc.sharing/trunk/src/zc/sharing/browser/__init__.py
  A   zc.sharing/trunk/src/zc/sharing/browser/configure.zcml
  A   zc.sharing/trunk/src/zc/sharing/browser/ftesting.zcml
  A   zc.sharing/trunk/src/zc/sharing/browser/functional.txt
  A   zc.sharing/trunk/src/zc/sharing/browser/group_icon.gif
  A   zc.sharing/trunk/src/zc/sharing/browser/ntests.py
  A   zc.sharing/trunk/src/zc/sharing/browser/sharing.pt
  A   zc.sharing/trunk/src/zc/sharing/browser/sharing.py
  A   zc.sharing/trunk/src/zc/sharing/browser/sharing.txt
  A   zc.sharing/trunk/src/zc/sharing/browser/test_template.pt
  A   zc.sharing/trunk/src/zc/sharing/browser/tests.py
  A   zc.sharing/trunk/src/zc/sharing/browser/user_icon.gif
  A   zc.sharing/trunk/src/zc/sharing/configure.zcml
  A   zc.sharing/trunk/src/zc/sharing/generation/
  A   zc.sharing/trunk/src/zc/sharing/generation/__init__.py
  A   zc.sharing/trunk/src/zc/sharing/generation/install.py
  A   zc.sharing/trunk/src/zc/sharing/i18n.py
  A   zc.sharing/trunk/src/zc/sharing/index.py
  A   zc.sharing/trunk/src/zc/sharing/index.txt
  A   zc.sharing/trunk/src/zc/sharing/interfaces.py
  A   zc.sharing/trunk/src/zc/sharing/meta.zcml
  A   zc.sharing/trunk/src/zc/sharing/policy.py
  A   zc.sharing/trunk/src/zc/sharing/policy.txt
  A   zc.sharing/trunk/src/zc/sharing/sharing.py
  A   zc.sharing/trunk/src/zc/sharing/sharing.txt
  A   zc.sharing/trunk/src/zc/sharing/tests.py
  A   zc.sharing/trunk/src/zc/sharing/utils.py
  A   zc.sharing/trunk/src/zc/sharing/zcml.py
  A   zc.sharing/trunk/src/zc/sharing/zcml.txt

-=-
Added: zc.sharing/trunk/src/zc/sharing/__init__.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/__init__.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/__init__.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1 @@
+#


Property changes on: zc.sharing/trunk/src/zc/sharing/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/__init__.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/__init__.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/__init__.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1 @@
+#


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/configure.zcml
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/configure.zcml	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/configure.zcml	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,29 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns="http://namespaces.zope.org/browser"
+    i18n_domain="zc.sharing">
+
+
+<page
+    for="..interfaces.ISharable" 
+    name="sharing.html"
+    menu="zmi_views" 
+    title="Sharing"
+    template="sharing.pt"
+    class=".sharing.SharingTab"
+    permission="zope.Security"
+    />
+
+<resource
+    name="user_icon.gif"
+    image="user_icon.gif"
+    permission="zope.Public"
+    />
+
+<resource
+    name="group_icon.gif"
+    image="group_icon.gif"
+    permission="zope.Public"
+    />
+
+</configure>


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/ftesting.zcml
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/ftesting.zcml	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/ftesting.zcml	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,118 @@
+<configure 
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:zc="http://namespaces.zope.com/zc"
+    i18n_domain="zope"
+    package="zc.sharing"
+    >
+
+  <!-- This file is the equivalent of site.zcml and it is -->
+  <!-- used for functional testing setup -->
+
+  <include package="zope.app" />
+  <include package="zope.app.authentication" />
+
+  <!-- Principals -->
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User" />
+
+  <authenticatedGroup
+    id="zope.Authenticated"
+    title="Everybody" 
+    />
+
+  <!-- Principal that tests generally run as -->
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw" />
+
+  <!-- Bootstrap principal used to make local grant to the principal above -->
+  <principal
+      id="zope.globalmgr"
+      title="Manager"
+      login="globalmgr"
+      password="globalmgrpw" />
+
+  <include package="zc.sharing" file="meta.zcml" />
+  <include package="zc.sharing" />
+
+  <zc:privilege bit="0" title="Read"
+                description="Read or view content"
+                />
+
+    <zc:permissionPrivilege permission="zope.View"
+                            privilege="0"
+                            />
+
+    <zc:permissionPrivilege permission="zope.app.dublincore.view"
+                            privilege="0"
+                            />
+
+  <zc:privilege bit="2" title="Write"
+                description="Modify content"
+                />
+
+    <zc:permissionPrivilege permission="zope.ManageContent"
+                            privilege="2"
+                            />
+    <zc:permissionPrivilege permission="zope.app.dublincore.change"
+                            privilege="2"
+                            />
+
+  <zc:privilege bit="4" title="Share"
+                description="Share content"
+                />
+
+    <zc:permissionPrivilege permission="zope.Security"
+                            privilege="4"
+                            />
+
+  <zc:privileges for="zc.sharing.interfaces.ISharable"
+                 titles="Read Write Share"
+                 />
+
+  <zc:subobjectPrivileges
+     for="zope.app.container.interfaces.IContainer"
+     titles="Read Write Share"
+     />
+
+  <securityPolicy component=".policy.SecurityPolicy" />
+
+
+  <zc:systemAdministrators principals="zope.globalmgr zope.mgr" />
+
+
+  <!-- XXX We need to explain/rationalize this better.            -->
+  <!-- If the root object or other objects on the way to sharable -->
+  <!-- objects are not sharable, then we need to either:          -->
+  <!--                                                            -->
+  <!-- o Make reading them public or                              -->
+  <!--                                                            -->
+  <!-- o Provide public traversal adapters                        -->
+  <content class="zope.app.folder.Folder">
+    <require
+        permission="zope.Public"
+        interface="zope.app.container.interfaces.IReadContainer" 
+        />
+    <implements interface="zc.sharing.interfaces.ISharable" />
+  </content>
+
+  <utility component=".browser.ntests.formatterFactory" />
+  <utility 
+      factory=".browser.ntests.Authentication"
+      provides="zope.app.security.interfaces.IAuthentication" 
+      />
+
+  <browser:page 
+      for="zope.app.folder.interfaces.IFolder"
+      name="test_greet"
+      permission="zope.View"
+      template="browser/test_template.pt"
+      />
+
+
+</configure>


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/functional.txt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/functional.txt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/functional.txt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,175 @@
+Basic Functional Demonstration
+==============================
+
+We can try to log on as jim:
+
+
+We were able to log in as Jim, but we weren't able to access the site,
+because it hasn't been shared to anyone but the management user.
+
+Let's start by sharing it to everybody (authenticated):
+
+  >>> print http(r"""
+  ... POST /@@sharing.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Referer: http://localhost:8081/@@sharing.html
+  ...
+  ... sharing.em9wZS5BdXRoZW50aWNhdGVk.0.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.0=on"""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.2.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.4.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.6.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.8.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.10.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.12.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.14.used="""
+  ... """&effective_principal_type=group"""
+  ... """&setRecursiveSharingInfo=+Share+"""
+  ... """&effective_principal_text=""", handle_errors=False)
+  HTTP/1.1 200 Ok
+  ...
+
+Now, with this, Jim can see /, but not /manage
+
+  >>> print http(r"""
+  ... GET /test_greet HTTP/1.1
+  ... Authorization: Basic jim:eek
+  ... """, handle_errors=False)
+  HTTP/1.1 200 Ok
+  ...
+
+  >>> print http(r"""
+  ... GET /manage HTTP/1.1
+  ... Authorization: Basic jim:eek
+  ... """)
+  HTTP/1.1 401 Unauthorized
+  ...
+
+Now, we'll share all privileges but "Share" with Jim:
+
+  >>> print http(r"""
+  ... POST /@@sharing.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ...
+  ... sharing.Mg%3D%3D.0.used="""
+  ... """&sharing.MQ%3D%3D.0=on"""
+  ... """&sharing.MQ%3D%3D.2.used="""
+  ... """&sharing.MQ%3D%3D.2=on"""
+  ... """&sharing.MQ%3D%3D.4.used="""
+  ... """&sharing.MQ%3D%3D.6.used="""
+  ... """&sharing.MQ%3D%3D.6=on"""
+  ... """&sharing.MQ%3D%3D.8.used="""
+  ... """&sharing.MQ%3D%3D.8=on"""
+  ... """&sharing.MQ%3D%3D.10.used="""
+  ... """&sharing.MQ%3D%3D.10=on"""
+  ... """&sharing.MQ%3D%3D.12.used="""
+  ... """&sharing.MQ%3D%3D.12=on"""
+  ... """&sharing.MQ%3D%3D.14.used="""
+  ... """&sharing.MQ%3D%3D.14=on"""
+  ... """&sharing.Mw%3D%3D.0.used="""
+  ... """&sharing.Mw%3D%3D.2.used="""
+  ... """&sharing.Mw%3D%3D.4.used="""
+  ... """&sharing.Mw%3D%3D.6.used="""
+  ... """&sharing.Mw%3D%3D.8.used="""
+  ... """&sharing.Mw%3D%3D.10.used="""
+  ... """&sharing.Mw%3D%3D.12.used="""
+  ... """&sharing.Mw%3D%3D.14.used="""
+  ... """&effective_principal_type=user"""
+  ... """&effective_principal_text=i"""
+  ... """&setSharingInfo=+Share+""")
+  HTTP/1.1 200 Ok
+  ...
+
+which lets Jim get /manage and /@@contents:
+
+  >>> print http(r"""
+  ... GET /manage HTTP/1.1
+  ... Authorization: Basic jim:eek
+  ... """, handle_errors=False)
+  HTTP/1.1 303 See Other
+  Content-Length: 0
+  Content-Type: text/plain;charset=utf-8
+  Location: @@contents.html
+  <BLANKLINE>
+
+
+  >>> contents = http(r"""
+  ... GET /@@contents.html HTTP/1.1
+  ... Authorization: Basic jim:eek
+  ... Cache-Control: max-age=0
+  ... """, handle_errors=False)
+  >>> print contents
+  HTTP/1.1 200 Ok
+  ...
+
+  >>> 'Sharing' not in str(contents)
+  True
+
+Now if we also share "Sharing" with Jim:
+
+  >>> print http(r"""
+  ... POST /@@sharing.html HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/x-www-form-urlencoded
+  ... Referer: http://localhost:8081/@@sharing.html
+  ...
+  ... sharing.MQ%3D%3D.select.used="""
+  ... """&sharing.MQ%3D%3D.0.used="""
+  ... """&sharing.MQ%3D%3D.0=on"""
+  ... """&sharing.MQ%3D%3D.2.used="""
+  ... """&sharing.MQ%3D%3D.2=on"""
+  ... """&sharing.MQ%3D%3D.4.used="""
+  ... """&sharing.MQ%3D%3D.4=on"""
+  ... """&sharing.MQ%3D%3D.6.used="""
+  ... """&sharing.MQ%3D%3D.6=on"""
+  ... """&sharing.MQ%3D%3D.8.used="""
+  ... """&sharing.MQ%3D%3D.8=on"""
+  ... """&sharing.MQ%3D%3D.10.used="""
+  ... """&sharing.MQ%3D%3D.10=on"""
+  ... """&sharing.MQ%3D%3D.12.used="""
+  ... """&sharing.MQ%3D%3D.12=on"""
+  ... """&sharing.MQ%3D%3D.14.used="""
+  ... """&sharing.MQ%3D%3D.14=on"""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.select.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.0.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.0=on"""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.2.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.4.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.6.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.8.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.10.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.12.used="""
+  ... """&sharing.em9wZS5BdXRoZW50aWNhdGVk.14.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.select.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.0.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.0=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.2.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.2=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.4.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.4=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.6.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.6=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.8.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.8=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.10.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.10=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.12.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.12=on"""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.14.used="""
+  ... """&sharing.em9wZS5tYW5hZ2Vy.14=on"""
+  ... """&setSharingInfo=+Apply+""")
+  HTTP/1.1 200 Ok
+  ...
+
+Then if we visit contents, the sharing tab will be included:
+
+  >>> print http(r"""
+  ... GET /@@contents.html HTTP/1.1
+  ... Authorization: Basic jim:eek
+  ... Cache-Control: max-age=0
+  ... """)
+  HTTP/1.1 200 Ok
+  ...Sharing...
+


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/functional.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/group_icon.gif
===================================================================
(Binary files differ)


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/group_icon.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/ntests.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/ntests.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/ntests.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+import os
+import unittest
+from zope import component, interface
+import zope.security.interfaces
+import zope.app.security.interfaces
+from zope.app.testing import functional
+
+import zc.security.interfaces
+import zc.testlayer.ftesting
+import zc.table.table
+import zc.table.interfaces
+
+class Principal:
+    interface.implements(zope.security.interfaces.IPrincipal)
+
+    def __init__(self, id, title):
+        self.id = id
+        self.title = title
+        self.groups = 'zope.Authenticated',
+        
+
+class Authentication:
+    interface.implements(
+        zope.app.security.interfaces.IAuthentication,
+        zc.security.interfaces.ISimpleUserSearch,
+        zc.security.interfaces.ISimpleGroupSearch,
+        )
+
+    def __init__(self):
+        self.byId = dict(
+            [(p.id, p) for p in [
+                Principal('1', 'jim'),
+                Principal('2', 'bob'),
+                Principal('3', 'sally'),
+                Principal('zope.manager', 'manager'),
+                Principal('zope.Authenticated', 'Everybody'),
+                ]
+             ])
+        
+        self.byCred = {
+            'jim:eek': self.byId['1'],
+            }
+
+    def searchUsers(self, filter, start, size):
+        return '1', '2', '3'
+
+    def searchGroups(self, filter, start, size):
+        return 'zope.manager', 'zope.Authenticated'
+
+    def authenticate(self, request):
+        if request._auth:
+            credentials = request._auth.split()[-1]
+            return self.byCred.get(credentials.decode('base64'))
+
+    def getPrincipal(self, id):
+        return self.byId.get(id)
+
+def formatterFactory(*args, **kw):
+    return zc.table.table.FormFullFormatter(*args, **kw)
+interface.directlyProvides(formatterFactory,
+                           zc.table.interfaces.IFormatterFactory)
+
+SharingLayer = zc.testlayer.ftesting.FTestingLayer(
+    os.path.join(os.path.split(__file__)[0], 'ftesting.zcml'),
+    __name__, 'SharingLayer')
+
+def test_suite():
+    suite = functional.FunctionalDocFileSuite('functional.txt')
+    suite.layer = SharingLayer
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/ntests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/sharing.pt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/sharing.pt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/sharing.pt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,97 @@
+<html metal:use-macro="context/@@standard_macros/view"
+      i18n:domain="zc.intranet">
+<head>
+</head>
+<body>
+<div metal:fill-slot="body">
+
+  <form name="sharingform" id="sharingform" method="post"
+      tal:attributes="action request/URL">
+
+    <div id="viewspace">
+      <h1 i18n:translate="">Sharing</h1>
+
+      <div class="message"
+           i18n:translate=""
+           tal:condition="view/message" 
+          tal:content="view/message"> 
+      </div>
+      <div style="width: 100%"> <!-- this is a workaround for an IE bug -->
+       <table class="listingdescription" style="width:100%">
+        <col width="1%">
+        <col class="principal">
+        <col span="3"
+             tal:attributes="span view/nPrivileges"
+             tal:condition="view/nPrivileges"
+             class="privileges"
+             >
+        <col span="3"
+             tal:attributes="span view/nSubobjectPrivileges"
+             tal:condition="view/nSubobjectPrivileges"
+             class="subobjectPrivileges"
+             >
+        <thead tal:content="structure view/formatter/renderHeaderRow">
+        </thead>
+        <tbody tal:content="structure view/formatter/renderRows">
+        </tbody>
+       </table>
+       <div tal:content="structure view/formatter/renderExtra" />
+      </div> <!-- IE bug workaround -->
+    </div> <!-- id="viewspace" -->
+     
+          
+
+    <div id="actionsView">
+      <div class="action-buttons">
+        <input value="Apply" name="setSharingInfo" type="submit"
+          class="submit" i18n:attributes="value" />
+        <input value="Apply to this and all subobjects"
+          name="setRecursiveSharingInfo" type="submit" class="submit"
+          tal:condition="view/subobjects" i18n:attributes="value" />
+        <span class="createSelect">
+          <input type="hidden" name="effective_principal_text" value=""
+                 tal:attributes="value view/effective_principal_text" >
+          <input type="hidden" name="effective_principal_type" value=""
+                 tal:attributes="value view/effective_principal_type" >
+          <input type="text" name="principal_text"
+            tal:attributes="value view/principal_text|nothing" />
+          <select name="principal_type">
+            <option value="user" i18n:translate=""
+              tal:attributes="
+              selected python:view.principal_type!='group' and 'selected' or nothing"
+              >user</option>
+            <option value="group" i18n:translate=""
+              tal:attributes="
+              selected python:view.principal_type=='group' and 'selected' or nothing"
+              >group</option>
+          </select>
+          <input type="submit" value="Search" name="principal_search"
+    		i18n:attributes="value"/>
+        </span>
+        <span class="createSelect" tal:condition="view/macros">
+          <label for="macros">Share with:</label>
+          <select name="macro" id="macro">
+             <option
+                     i18n:translate>Select macro...</option>
+             <option tal:repeat="macro view/macros"
+                     tal:content="macro"
+                     i18n:translate=""
+                     >Everyone readable (Public)</option>
+          </select>
+          <input type="submit" name="apply_sharing_macro"
+                 value="Apply Macro" i18n:attributes="value"/>
+        </span>
+
+      </div> <!-- class="action-buttons" -->
+    </div> <!-- id="actionsview" -->
+
+</form>
+
+<script language="Javascript1.1">
+    // If the skin provides a trackChanges function, call it.
+    var trackChanges;
+    if (trackChanges) trackChanges(document.getElementById('sharingform'));
+</script>
+</div>
+</body>
+</html>


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/sharing.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/sharing.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/sharing.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/sharing.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,293 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""Sharing view
+
+
+$Id$
+"""
+import datetime
+
+from zc.sharing import interfaces
+from zc.table import table, column
+import zc.table.interfaces
+from zope import schema, component, interface
+from zope.app import zapi
+from zope.app.location.interfaces import ISublocations
+from zope.interface import Interface
+from zope.interface.common.idatetime import ITZInfo
+from zope.security.interfaces import IGroup
+import zope.app.security.interfaces
+
+from zc.sharing.i18n import _
+from zc.security.interfaces import ISimpleGroupSearch
+from zc.security.interfaces import ISimpleUserSearch
+from zc.sharing import policy
+from zc.sharing.sharing import sharingMask, getPrivilege
+
+class IPrivilegeColumn(interface.Interface):
+    """Marker interface for internal use."""
+
+class PrincipalColumn(column.SortingColumn):    
+    # we don't want a sorting header, just the convenience of sorting,
+    # so remove the declaration of sortable headers (XXX is this really
+    # what we want)
+    interface.implementsOnly(zc.table.interfaces.IColumn)
+
+    def renderCell(self, item, formatter):
+        principal_id, setting = item
+        principals = zapi.principals()
+        principal = principals.getPrincipal(principal_id)
+        if IGroup.providedBy(principal):
+            icon = 'group_icon.gif'
+        else:
+            icon = 'user_icon.gif'
+        resource = component.getAdapter(formatter.request, Interface,
+                                        icon)
+        return '<img src="%s"> %s' % (resource(), principal.title)
+
+    def getSortKey(self, item, formatter):
+        principal_id, setting = item
+        principals = zapi.principals()
+        principal = principals.getPrincipal(principal_id)
+        return principal.title.lower()
+
+
+def _getgetbit(sharing, bit):
+    v = 2**bit
+    def getbit(data):
+        return bool(sharing.getBinaryPrivileges(data[0]) & v)
+    return getbit
+
+def _getsetbit(sharing, bit):
+    mask = 1 << bit
+    def setbit(data, v):
+        v = bool(v) << bit
+        current = sharing.getBinaryPrivileges(data[0])
+        result = ((current | mask) ^ mask) | v
+        sharing.setBinaryPrivileges(data[0], result)
+    return setbit
+
+def _privilegeColumn(priv, sharing):
+    col = column.FieldEditColumn(
+        priv['title'], "sharing",
+        schema.Bool(__name__=str(priv['id'])),
+        lambda data: data[0],
+        getter = _getgetbit(sharing, priv['id']),
+        setter = _getsetbit(sharing, priv['id']),
+        )
+    interface.alsoProvides(col, IPrivilegeColumn)
+    return col
+
+
+
+class SharingTab:
+    # This view has some weird implementation details due to the
+    # dynamic computation of the items passed to the table formatter.
+    #
+    # The table formatter is created twice; the first time to read
+    # input from the table using the initial set of items, and the
+    # second time with the final set of items; only the latter is used
+    # for rendering.
+    #
+    # Future changes to the table formatter API may make it possible
+    # for this view to be less weird.
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.sharing = sharing = interfaces.IBaseSharing(self.context)
+
+        privids = interfaces.ISharingPrivileges(self.context).privileges
+        privs = [getPrivilege(id) for id in privids]
+        self.nPrivileges = len(privs)
+
+        columns = [_privilegeColumn(priv, sharing) for priv in privs]
+
+        sprivids = interfaces.ISubobjectSharingPrivileges(self.context, None)
+        if sprivids is not None:
+            sprivids = [id for id in sprivids.subobjectPrivileges
+                        if id not in privids]
+            if sprivids:
+                sprivs = [getPrivilege(id) for id in sprivids]
+                columns.extend([_privilegeColumn(priv, sharing)
+                                for priv in sprivs])
+            self.nSubobjectPrivileges = len(sprivids)
+        else:
+            self.nSubobjectPrivileges = 0
+
+        self.columns = [
+            column.SubmitColumn(
+                "",
+                prefix="remove",
+                idgetter=lambda data: str(data[0]),
+                action=lambda d: self.sharing.setBinaryPrivileges(
+                    d[0], 0),
+                labelgetter=lambda data, formatter: _('Remove'),
+                condition=lambda d: d[0] in self.sharing.getPrincipals()
+                ),
+            PrincipalColumn(_("Name"))] + columns
+
+        self.factory = component.getUtility(
+            zc.table.interfaces.IFormatterFactory)
+
+        self.processInput()
+
+        self.formatter = self.factory(
+            context, request, self.settings, columns=self.columns, 
+            sort_on=[('Name', False)])
+
+    def processInput(self):
+        request = self.request
+        sharing = self.sharing
+
+        # XXX completely untested, afaik; we should also set up an i18n domain
+        # and provide a facility on the view for 'translating' the names for
+        # labels.
+        macro_name = request.form.get('apply_sharing_macro')
+        if macro_name:
+            macro = component.getAdapter(
+                self.context, interfaces.ISharingMacro,
+                macro_name)
+            macro.share(sharing)
+
+        updated = False
+        form = request.form
+        settings = []
+        effective_principal_text = form.get('effective_principal_text', '')
+        effective_principal_type = form.get('effective_principal_type', '')
+        principal_text = form.get('principal_text', '')
+        principal_type = form.get('principal_type', '')
+        if 'principal_search' in form:
+            effective_principal_text = principal_text
+            effective_principal_type = principal_type
+        self.effective_principal_text = effective_principal_text
+        self.effective_principal_type = effective_principal_type
+        self.principal_text = principal_text
+        self.principal_type = principal_type
+
+        settingsFactory = lambda: []
+
+        if effective_principal_type=='group':
+            searcher = ISimpleGroupSearch(zapi.principals(), None)
+            # TODO the absence of a searcher should be logged as an error
+            if searcher is not None:
+                res = [[pid, 0]
+                       for pid in searcher.searchGroups(
+                        effective_principal_text, 0, 999999999)]
+                settingsFactory = lambda: res
+        elif effective_principal_type=='user' and effective_principal_text:
+            searcher = ISimpleUserSearch(zapi.principals(), None)
+            # TODO the absence of a searcher should be logged as an error
+            if searcher is not None:
+                res = [[pid, 0]
+                       for pid in searcher.searchUsers(
+                        effective_principal_text, 0, 999999999)]
+                settingsFactory = lambda: res
+        else:
+            if effective_principal_type=='user':
+                self.message = _('You must supply search text to find a user')
+            def settingsFactory():
+                return [[pid, sharing.getBinaryPrivileges(pid)]
+                        for pid in sharing.getPrincipals()]
+
+        settings = settingsFactory()
+        input = self.columns[0].input(settings, request)
+        if input:
+            self.columns[0].update(settings, input) # inefficient :-(
+            settings = settingsFactory()
+
+        if 'setSharingInfo' in request or 'setRecursiveSharingInfo' in request:
+            # apply button
+            for column in self.columns:
+                if not IPrivilegeColumn.providedBy(column):
+                    continue
+                input = column.input(settings, request)
+                if input:
+                    updated = column.update(settings, input) or updated
+
+            # Reset settings, since we always show existing settings
+            # after an update
+            settings = [[pid, sharing.getBinaryPrivileges(pid)]
+                        for pid in sharing.getPrincipals()]
+            self.effective_principal_text = ''
+            self.effective_principal_type = ''
+
+            if 'setRecursiveSharingInfo' in request:
+                applyToSubobjects(settings, self.context, {})
+                updated = True # we'll guess :-/
+
+        self.settings = settings
+        self.updated = updated
+
+    def subobjects(self):
+        subs = ISublocations(self.context, None)
+        if subs is None:
+            return False
+        subs = iter(subs.sublocations())
+        try:
+            subs.next()
+        except StopIteration:
+            return False
+        return True
+
+    def macros(self):
+        macros = [
+            (macro.order, name, macro)
+            for (name, macro)
+            in component.getAdapters((self.context,), interfaces.ISharingMacro)
+            ]
+        macros.sort()
+        return [name for (order, name, macro) in macros]
+
+    @property
+    def message(self):
+        if self.updated:
+            formatter = self.request.locale.dates.getFormatter('dateTime', 
+                                                               'medium')
+            status = _("Updated on ${date_time}", 
+                       mapping={'date_time': formatter.format(
+                        datetime.datetime.now(ITZInfo(self.request, None)))})
+            
+            return status
+        else:
+            return ''
+
+    def newTableFormatter(self, settings, columns=None):
+        if columns is None:
+            columns = self.columns
+        return self.factory(
+            self.context, self.request, settings, columns=columns,
+            sort_on=[('Name', False)])
+
+def applyToSubobjects(settings, ob, seen):
+    obid = id(ob)
+    if obid in seen:
+        return
+    seen[obid] = ob
+
+    sharing = interfaces.IBaseSharing(ob, None)
+    if sharing is not None:
+        mask = sharingMask(ob)
+        for principal in sharing.getPrincipals():
+            sharing.setBinaryPrivileges(principal, 0)
+        for principal, setting in settings:
+            sharing.setBinaryPrivileges(principal, setting & mask)
+
+    subs = ISublocations(ob, None)
+    if subs is None:
+        return
+
+    for sub in subs.sublocations():
+        applyToSubobjects(settings, sub, seen)


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/sharing.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/sharing.txt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/sharing.txt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/sharing.txt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,1239 @@
+Sharing Tab Support
+===================
+
+The sharing tab class provides a form for displaying and manipulating
+sharing settings.  To demonstrate this, we'll set up a fake
+authentication utility:
+
+    >>> class Principal:
+    ...     def __init__(self, id, title):
+    ...         self.id, self.title = id, title
+
+    >>> import zope.security.interfaces
+    >>> from zope import interface
+    >>> class Group(Principal):
+    ...     interface.implements(zope.security.interfaces.IGroup)
+
+    >>> import zope.app.security.interfaces
+    >>> from zc.sharing import interfaces
+    >>> from zc.security.interfaces import ISimpleUserSearch
+    >>> from zc.security.interfaces import ISimpleGroupSearch
+    >>> class Principals:
+    ...     interface.implements(zope.app.security.interfaces.IAuthentication,
+    ...                          ISimpleGroupSearch, ISimpleUserSearch,
+    ...                          )
+    ...     def __init__(self):
+    ...         self.users = {
+    ...             'p1': Principal("Grace", "Grace Slick"),
+    ...             'p2': Principal("Alice", "Alice Cooper"),
+    ...             'p3': Principal('baba', 'Baba'),
+    ...             'p4': Principal('baback', 'Baback'),
+    ...             'p5': Principal('barney', 'Barney'),
+    ...             'p6': Principal('bash', 'Bash'),
+    ...             'p7': Principal('bat', 'Bat'),
+    ...             'p8': Principal('bathsheba', 'Bathsheba'),
+    ...             'p9': Principal('basil', 'Basil'),
+    ...             }
+    ...         self.groups = {
+    ...             'rockers': Group("Rockers", "Rock Performers"),
+    ...             'g2': Group('zane', 'zane'),
+    ...             'g3': Group('zulu', 'zulu'),
+    ...             'g4': Group('zandra', 'zandra'),
+    ...             'g5': Group('zorina', 'zorina'),
+    ...             'g6': Group('zubaida', 'zubaida'),
+    ...             'g7': Group('zan', 'zan'),
+    ...             }
+    ...
+    ...     def getPrincipal(self, pid):
+    ...         return self.users.get(pid, self.groups.get(pid))
+    ...
+    ...     def searchUsers(self, filter, start, size):
+    ...         return [i for (i, p) in self.users.items()
+    ...                 if filter in p.title][start:start+size]
+    ...
+    ...     def searchGroups(self, filter, start, size):
+    ...         return [i for (i, p) in self.groups.items()
+    ...                 if filter in p.title][start:start+size]
+
+    >>> from zope import component
+    >>> component.provideUtility(Principals(),
+    ...                          zope.app.security.interfaces.IAuthentication)
+
+We'll also create a sample content object that can be adapted to ISharing:
+
+    >>> from zc.sharing import interfaces
+    >>> class SharingSample:
+    ...     """Sample content class
+    ...
+    ...     Normally, we adapt content objects to ISharing. To keep this
+    ...     example simple, we'll implement ISharing directly.
+    ...     """
+    ...     interface.implements(interfaces.IBaseSharing)
+    ...
+    ...     def __init__(self, **data):
+    ...         self.data = data
+    ...
+    ...     def getPrincipals(self):
+    ...         return self.data.keys()
+    ...     def getBinaryPrivileges(self, principal_id):
+    ...         return self.data.get(principal_id, 0)
+    ...     def setBinaryPrivileges(self, principal_id, privileges):
+    ...         if privileges:
+    ...             self.data[principal_id] = privileges
+    ...         else:
+    ...             del self.data[principal_id]
+
+    >>> sharing = SharingSample(p1=7, rockers=4)
+
+We need to define some privileges:
+
+    >>> import zc.sharing.sharing
+    >>> zc.sharing.sharing.definePrivilege(0, "Share")
+    >>> zc.sharing.sharing.definePrivilege(1, "Work")
+    >>> zc.sharing.sharing.definePrivilege(2, "Play")
+
+And we need to define the privileges used by content
+
+    >>> from zc.sharing import policy
+    >>> policy.sharingPrivileges(SharingSample, ["Play", "Work", "Share"])
+
+Now we can create a sharing tab:
+
+    >>> from zc.sharing.browser.sharing import SharingTab
+    >>> from zope.publisher.browser import TestRequest
+    >>> request = TestRequest()
+    >>> tabs = SharingTab(sharing, request)
+
+The component generates components of the settings form, most notably,
+the table headers:
+
+    >>> def output_row(row):
+    ...     for cell in row:
+    ...         print '    cell:'
+    ...         print '      '+cell
+
+    >>> output_row(tabs.formatter.getHeaders())
+        cell:
+    <BLANKLINE>
+        cell:
+          Name
+        cell:
+          Play
+        cell:
+          Work
+        cell:
+          Share
+
+and the rows:
+
+    >>> def output(rows):
+    ...     for row in rows:
+    ...         print '  row:'
+    ...         output_row(row)
+
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cm9ja2Vycw==" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+
+We can update the settings. The request has to have data for the
+settings and the 'setSharingInfo' key needs to be in the request.  Let's
+add the write privilege to the rockers group. First, we'll include the
+data in the request:
+
+    >>> request.form["sharing.cm9ja2Vycw==.1.used"] = ""
+    >>> request.form["sharing.cm9ja2Vycw==.1"] = "on"
+    >>> request.form["sharing.cm9ja2Vycw==.0.used"] = ""
+    >>> request.form["sharing.cm9ja2Vycw==.0"] = "on"
+    >>> tabs = SharingTab(sharing, request)
+
+With this, the form is updated:
+
+    >>> output(tabs.formatter.getRows())
+    ... # doctest: +ELLIPSIS
+      row:
+    ...
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+
+But the data are unaffected:
+
+    >>> sharing.getBinaryPrivileges('rockers')
+    4
+
+Now, if we include the 'setSharingInfo' key, we'll get the same
+output, but we'll also have the data updated:
+
+    >>> request.form['setSharingInfo'] = ""
+    >>> tabs = SharingTab(sharing, request)
+
+    >>> output(tabs.formatter.getRows())
+    ... # doctest: +ELLIPSIS
+      row:
+    ...
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+    <BLANKLINE>
+
+
+    >>> sharing.getBinaryPrivileges('rockers')
+    7
+
+
+Sharing to groups
+-----------------
+
+Normally, we show the principals for which we have settings.  We cant
+change settings for a principal if we don't already have settings for
+them.  We can add settings for new principals by searching for them.  For
+groups, you need to select 'group' from the search dropdown, and optionally
+include a search string, and then click 'Search'.  This means in the request
+that 'principal_type' must be 'group' and the 'principal_search' search button
+is in the request:
+
+    >>> request = TestRequest()
+    >>> request.form['principal_type'] = 'group'
+    >>> request.form['principal_search'] = ''
+    >>> tabs = SharingTab(sharing, request)
+
+The form stills allow you to remove principals if they have settings, so the
+headings remain the same:
+
+    >>> output_row(tabs.formatter.getHeaders())
+        cell:
+    <BLANKLINE>
+        cell:
+          Name
+        cell:
+          Play
+        cell:
+          Work
+        cell:
+          Share
+
+And the rows still have a selection column.  Note that now only groups are
+listed--both 'Rock Performers', which currently has settings, and the other
+groups.
+
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cm9ja2Vycw==" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zan
+        cell:
+          <input class="hiddenType" id="sharing.Zzc=.2.used"
+                 name="sharing.Zzc=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.Zzc=.2"
+                 name="sharing.Zzc=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.Zzc=.1.used"
+                 name="sharing.Zzc=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.Zzc=.1"
+                 name="sharing.Zzc=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.Zzc=.0.used"
+                 name="sharing.Zzc=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.Zzc=.0"
+                 name="sharing.Zzc=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zandra
+        cell:
+          <input class="hiddenType" id="sharing.ZzQ=.2.used"
+                 name="sharing.ZzQ=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzQ=.2"
+                 name="sharing.ZzQ=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzQ=.1.used"
+                 name="sharing.ZzQ=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzQ=.1"
+                 name="sharing.ZzQ=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzQ=.0.used"
+                 name="sharing.ZzQ=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzQ=.0"
+                 name="sharing.ZzQ=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zane
+        cell:
+          <input class="hiddenType" id="sharing.ZzI=.2.used"
+                 name="sharing.ZzI=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzI=.2"
+                 name="sharing.ZzI=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzI=.1.used"
+                 name="sharing.ZzI=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzI=.1"
+                 name="sharing.ZzI=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzI=.0.used"
+                 name="sharing.ZzI=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzI=.0"
+                 name="sharing.ZzI=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zubaida
+        cell:
+          <input class="hiddenType" id="sharing.ZzY=.2.used"
+                 name="sharing.ZzY=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzY=.2"
+                 name="sharing.ZzY=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzY=.1.used"
+                 name="sharing.ZzY=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzY=.1"
+                 name="sharing.ZzY=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzY=.0.used"
+                 name="sharing.ZzY=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzY=.0"
+                 name="sharing.ZzY=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zulu
+        cell:
+          <input class="hiddenType" id="sharing.ZzM=.2.used"
+                 name="sharing.ZzM=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzM=.2"
+                 name="sharing.ZzM=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzM=.1.used"
+                 name="sharing.ZzM=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzM=.1"
+                 name="sharing.ZzM=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzM=.0.used"
+                 name="sharing.ZzM=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzM=.0"
+                 name="sharing.ZzM=.0" type="checkbox" value="on"  />
+    <BLANKLINE>
+
+We can filter the groups presented by actually providing search text:
+
+    >>> request.form['principal_text'] = 'zor'
+    >>> tabs = SharingTab(sharing, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+
+The view provides two attributes, `effective_principal_text` and
+`effective_principal_type`, that should be used to persist the search text and
+type in the request.  `principal_text` and `principal_type` are used to persist
+the values in the fields themselves, and don't affect the effective values
+unless the `principal_search` submit button is in the request.
+
+    >>> tabs.effective_principal_type
+    'group'
+    >>> tabs.effective_principal_text
+    'zor'
+    >>> tabs.principal_type
+    'group'
+    >>> tabs.principal_text
+    'zor'
+
+We can supply data for a group.  Note again that `principal_text` and
+`principal_type` are ignored unless `principal_search` is in the request, which
+allows the form to keep state with what the user changes in the search fields
+while not getting the data confused with the effective search:
+
+    >>> request = TestRequest()
+    >>> request.form['principal_type'] = 'user'
+    >>> request.form['principal_text'] = 'foo'
+    >>> request.form['effective_principal_type'] = 'group'
+    >>> request.form['effective_principal_text'] = 'zor'
+    >>> request.form["sharing.ZzU=.2.used"] = ""
+    >>> request.form["sharing.ZzU=.2"] = "on"
+    >>> request.form['setSharingInfo'] = ""
+    >>> tabs = SharingTab(sharing, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cm9ja2Vycw==" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.ZzU=" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+
+The settings, rather than groups, are displayed again, so the
+effective search is cleared (but the field values remain):
+
+    >>> tabs.effective_principal_text
+    ''
+    >>> tabs.effective_principal_type
+    ''
+    >>> tabs.principal_text
+    'foo'
+    >>> tabs.principal_type
+    'user'
+
+
+Sharing to users
+----------------
+
+Just as we can share to groups, we can also share to users.  The only 
+difference in behavior from the group story is that search text must be
+provided.
+
+    >>> request = TestRequest()
+    >>> request.form['principal_type'] = 'user'
+    >>> request.form['principal_text'] = 'c'
+    >>> request.form['principal_search'] = ''
+    >>> tabs = SharingTab(sharing, request)
+
+We only got results with a "c" in the user name:
+
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/user_icon.gif"> Alice Cooper
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.2.used"
+                 name="sharing.cDI=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.2"
+                 name="sharing.cDI=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.1.used"
+                 name="sharing.cDI=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.1"
+                 name="sharing.cDI=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.0.used"
+                 name="sharing.cDI=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.0"
+                 name="sharing.cDI=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+    <BLANKLINE>
+        cell:
+          <img src="http://mysite/user_icon.gif"> Baback
+        cell:
+          <input class="hiddenType" id="sharing.cDQ=.2.used"
+                 name="sharing.cDQ=.2.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDQ=.2"
+                 name="sharing.cDQ=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDQ=.1.used"
+                 name="sharing.cDQ=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDQ=.1"
+                 name="sharing.cDQ=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDQ=.0.used"
+                 name="sharing.cDQ=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDQ=.0"
+                 name="sharing.cDQ=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+
+The view provides two attributes, `effective_principal_text` and
+`effective_principal_type`, that should be used to persist the search text and
+type in the request.  `principal_text` and `principal_type` are used to persist
+the values in the fields themselves, and don't affect the effective values
+unless the `principal_search` submit button is in the request.
+
+    >>> tabs.effective_principal_type
+    'user'
+    >>> tabs.effective_principal_text
+    'c'
+    >>> tabs.principal_type
+    'user'
+    >>> tabs.principal_text
+    'c'
+
+We can supply data for a user (Alice Cooper, while leaving Grace Slick's
+permissions in place):
+
+    >>> request = TestRequest()
+    >>> request.form['effective_principal_type'] = 'user'
+    >>> request.form['effective_principal_text'] = 'c'
+    >>> request.form['principal_type'] = 'user'
+    >>> request.form['principal_text'] = 'c'
+    >>> request.form['sharing.cDI=.2.used'] = ""
+    >>> request.form['sharing.cDI=.2'] = "on"
+    >>> request.form['sharing.cDE=.1.used'] = ""
+    >>> request.form['sharing.cDE=.1'] = "on"
+    >>> request.form['sharing.cDE=.2.used'] = ""
+    >>> request.form['sharing.cDE=.2'] = "on"
+    >>> request.form['sharing.cDE=.3.used'] = ""
+    >>> request.form['sharing.cDE=.3'] = "on"
+    >>> request.form['setSharingInfo'] = ""
+    >>> tabs = SharingTab(sharing, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDI=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Alice Cooper
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.2.used"
+                 name="sharing.cDI=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDI=.2"
+                 name="sharing.cDI=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.1.used"
+                 name="sharing.cDI=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.1"
+                 name="sharing.cDI=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.0.used"
+                 name="sharing.cDI=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.0"
+                 name="sharing.cDI=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cm9ja2Vycw==" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.ZzU=" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+
+The settings, rather than groups, are displayed again, so the
+effective search is cleared (but the field values remain):
+
+    >>> tabs.effective_principal_text
+    ''
+    >>> tabs.effective_principal_type
+    ''
+    >>> tabs.principal_text
+    'c'
+    >>> tabs.principal_type
+    'user'
+
+Removing settings for principals
+--------------------------------
+
+We can remove all settings for a given principal by clicking the `Remove`
+button next to the principal. Here we'll remove the settings for the rockers
+group:
+
+    >>> request = TestRequest()
+    >>> request.form["remove.cm9ja2Vycw=="] = 'Remove'
+    >>> tabs = SharingTab(sharing, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDI=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Alice Cooper
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.2.used"
+                 name="sharing.cDI=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDI=.2"
+                 name="sharing.cDI=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.1.used"
+                 name="sharing.cDI=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.1"
+                 name="sharing.cDI=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDI=.0.used"
+                 name="sharing.cDI=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDI=.0"
+                 name="sharing.cDI=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.ZzU=" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+
+Subobjects
+----------
+
+If an object has subobjects (sublocations). then an option is provided
+to share with subobjects.  Our existing sample object doesn't have
+subobjects, which we can determine by calling the subobjects method on
+the view:
+
+    >>> tabs.subobjects()
+    False
+
+Let's create a new sample object that supports sublocations.
+Normally, objects are adapted to ISublocations, however, to keep the
+example simple, we'll just extend our sample class to support
+sublocations:
+
+    >>> from zope.app.location.interfaces import ISublocations
+    >>> class SharingSampleWithSublocations(SharingSample):
+    ...     interface.implements(ISublocations)
+    ...     subs = ()
+    ...     def sublocations(self):
+    ...         return self.subs
+
+
+For our new objects, we'll define some different privileges:
+
+    >>> zc.sharing.sharing.definePrivilege(3, "List")
+    >>> zc.sharing.sharing.definePrivilege(4, "Modify")
+    >>> policy.sharingPrivileges(SharingSampleWithSublocations,
+    ...                          ["List", "Modify", "Share"])
+
+Because these are containers, we also need to specify the privileges
+that subobjects can have:
+
+    >>> policy.subobjectSharingPrivileges(SharingSampleWithSublocations,
+    ...                                   ["Work", "Play", "Share"])
+
+This is necessary so that we can use the sharing tab to specify
+settings that we will share with subobjects, as well as the settings
+that we apply to the container.
+
+Now, we can create a container:
+
+    >>> container = SharingSampleWithSublocations(p1=31)
+    >>> request = TestRequest()
+    >>> tabs = SharingTab(container, request)
+    >>> tabs.subobjects()
+    False
+
+    >>> container2 = SharingSampleWithSublocations(p2=7)
+    >>> container3 = SharingSampleWithSublocations(p3=7)
+    >>> container.subs = container2, container3
+    >>> container3.subs = (sharing, )
+
+    >>> tabs.subobjects()
+    True
+
+If we select the 'setRecursiveSharingInfo' button, then, settings are
+applied to the current object and the ones below it.  Note that this example
+cheats: we should really duplicate the information from the form, and are
+using our knowledge of the implementation to bypass the requirement.
+Converting this to testbrowser will be a better solution in the future.
+
+    >>> request.form['setRecursiveSharingInfo'] = ''
+    >>> tabs = SharingTab(container, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.3.used"
+                 name="sharing.cDE=.3.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.3"
+                 name="sharing.cDE=.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.4.used"
+                 name="sharing.cDE=.4.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.4"
+                 name="sharing.cDE=.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+
+    Now, if we look at the subobjects, we'll see that their settings were
+    changed to match the container:
+
+    >>> [(p, container2.getBinaryPrivileges(p)) for p in container2.getPrincipals()]
+    [('p1', 31)]
+
+    >>> [(p, container3.getBinaryPrivileges(p)) for p in container3.getPrincipals()]
+    [('p1', 31)]
+
+    >>> [(p, sharing.getBinaryPrivileges(p)) for p in sharing.getPrincipals()]
+    [('p1', 7)]
+
+Note that the simple (non-container) sharing object only has bits set
+for the privileges defined for it.
+
+If we make changes when supplying the recursive option, then the
+settings are duplicated after applying the changes.  Note that this example
+cheats: we should really duplicate the information from the form, and are
+using our knowledge of the implementation to bypass the requirement.
+Converting this to testbrowser will be a better solution in the future.
+
+    >>> request = TestRequest()
+    >>> request.form['setRecursiveSharingInfo'] = ''
+    >>> request.form["sharing.cDE=.0.used"] = ''
+    >>> tabs = SharingTab(container, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.3.used"
+                 name="sharing.cDE=.3.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.3"
+                 name="sharing.cDE=.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.4.used"
+                 name="sharing.cDE=.4.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.4"
+                 name="sharing.cDE=.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+
+    >>> [(p, container2.getBinaryPrivileges(p)) for p in container2.getPrincipals()]
+    [('p1', 30)]
+
+    >>> [(p, container3.getBinaryPrivileges(p)) for p in container3.getPrincipals()]
+    [('p1', 30)]
+
+    >>> [(p, sharing.getBinaryPrivileges(p)) for p in sharing.getPrincipals()]
+    [('p1', 6)]
+
+    >>> request = TestRequest()
+    >>> request.form['setRecursiveSharingInfo'] = ''
+    >>> request.form['effective_principal_type'] = 'group'
+    >>> request.form["sharing.ZzU=.2.used"] = ""
+    >>> request.form["sharing.ZzU=.2"] = "on"
+    >>> tabs = SharingTab(container, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.3.used"
+                 name="sharing.cDE=.3.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.3"
+                 name="sharing.cDE=.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.4.used"
+                 name="sharing.cDE=.4.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.4"
+                 name="sharing.cDE=.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.ZzU=" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.3.used"
+                 name="sharing.ZzU=.3.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.3"
+                 name="sharing.ZzU=.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.4.used"
+                 name="sharing.ZzU=.4.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.4"
+                 name="sharing.ZzU=.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />
+
+
+    >>> def sorted(l):
+    ...     tmp = list(l)
+    ...     tmp.sort()
+    ...     return tmp
+
+    >>> for ob in container2, container3, sharing:
+    ...     print sorted([(p, container2.getBinaryPrivileges(p))
+    ...                  for p in container2.getPrincipals()])
+    [('g5', 4), ('p1', 30)]
+    [('g5', 4), ('p1', 30)]
+    [('g5', 4), ('p1', 30)]
+
+Typically, columns for privileges applicable to the container are
+displayed differently than the columns applicable to just subobjects.
+To aid with the display of the columns, the view has variables giving
+the number of columns of column privileges:
+
+    >>> tabs.nPrivileges
+    3
+
+and the number of columns of privileges for subobjects:
+
+    >>> tabs.nSubobjectPrivileges
+    2
+
+Sharing "macros"
+================
+
+Sharing macros are simply named adapters from content to
+`ISharingMacro`.
+
+    >>> class RockersPlayable:
+    ...     component.adapts(interface.Interface)
+    ...     interface.implements(interfaces.ISharingMacro)
+    ...
+    ...     order = 1
+    ...
+    ...     def __init__(self, ignored):
+    ...         pass # we don't need the content for this example
+    ...
+    ...     def share(self, sharing):
+    ...         sharing.setBinaryPrivileges('rockers', 4)
+
+    >>> component.provideAdapter(RockersPlayable, name='Rockers Playable')
+
+    >>> class AliceAll(RockersPlayable):
+    ...
+    ...     order = 9
+    ...
+    ...     def share(self, sharing):
+    ...         sharing.setBinaryPrivileges('p2', 7)
+
+    >>> component.provideAdapter(AliceAll, name='Alice All')
+
+
+The sharing tab provides a macros method for getting the macro names:
+
+    >>> list(tabs.macros())
+    [u'Rockers Playable', u'Alice All']
+
+If a request is provided with a 'apply_sharing_macro' value,
+then the macro is executed and the output reflects the changes.
+
+    >>> request = TestRequest()
+    >>> request.form['apply_sharing_macro'] = u'Rockers Playable'
+    >>> tabs = SharingTab(container, request)
+    >>> output(tabs.formatter.getRows())
+      row:
+        cell:
+          <input type='submit' name="remove.cDE=" value="Remove" />
+        cell:
+          <img src="http://mysite/user_icon.gif"> Grace Slick
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.3.used"
+                 name="sharing.cDE=.3.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.3"
+                 name="sharing.cDE=.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.4.used"
+                 name="sharing.cDE=.4.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.4"
+                 name="sharing.cDE=.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.0.used"
+                 name="sharing.cDE=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cDE=.0"
+                 name="sharing.cDE=.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.1.used"
+                 name="sharing.cDE=.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.1"
+                 name="sharing.cDE=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cDE=.2.used"
+                 name="sharing.cDE=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.cDE=.2"
+                 name="sharing.cDE=.2" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.cm9ja2Vycw==" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> Rock Performers
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.3.used"
+                 name="sharing.cm9ja2Vycw==.3.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cm9ja2Vycw==.3"
+                 name="sharing.cm9ja2Vycw==.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.4.used"
+                 name="sharing.cm9ja2Vycw==.4.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cm9ja2Vycw==.4"
+                 name="sharing.cm9ja2Vycw==.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.0.used"
+                 name="sharing.cm9ja2Vycw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cm9ja2Vycw==.0"
+                 name="sharing.cm9ja2Vycw==.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.1.used"
+                 name="sharing.cm9ja2Vycw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.cm9ja2Vycw==.1"
+                 name="sharing.cm9ja2Vycw==.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.cm9ja2Vycw==.2.used"
+                 name="sharing.cm9ja2Vycw==.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked"
+                 id="sharing.cm9ja2Vycw==.2"
+                 name="sharing.cm9ja2Vycw==.2" type="checkbox" value="on"  />
+      row:
+        cell:
+          <input type='submit' name="remove.ZzU=" value="Remove" />
+        cell:
+          <img src="http://mysite/group_icon.gif"> zorina
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.3.used"
+                 name="sharing.ZzU=.3.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.3"
+                 name="sharing.ZzU=.3" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.4.used"
+                 name="sharing.ZzU=.4.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.4"
+                 name="sharing.ZzU=.4" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.0.used"
+                 name="sharing.ZzU=.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.0"
+                 name="sharing.ZzU=.0" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.1.used"
+                 name="sharing.ZzU=.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="sharing.ZzU=.1"
+                 name="sharing.ZzU=.1" type="checkbox" value="on"  />
+        cell:
+          <input class="hiddenType" id="sharing.ZzU=.2.used"
+                 name="sharing.ZzU=.2.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="sharing.ZzU=.2"
+                 name="sharing.ZzU=.2" type="checkbox" value="on"  />


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/sharing.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/test_template.pt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/test_template.pt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/test_template.pt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,2 @@
+This is a test template used in the sharing functional tests.
+


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/test_template.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/tests.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/browser/tests.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/browser/tests.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+import unittest
+from zope.app.testing import placelesssetup
+from zope import component, interface
+import zope.publisher.interfaces.browser
+import zope.schema.interfaces
+import zope.app.form.browser
+from zc.sharing import policy
+import zc.sharing.sharing
+import zc.table.interfaces
+import zc.table.table
+
+class ICon:
+    
+    def __init__(self, name):
+        self.name = name
+
+    def __call__(self, request=None):
+        if request is None:
+            return 'http://mysite/%s' % self.name
+            
+        return self
+
+def sharingSetUp(test):
+    placelesssetup.setUp(test)
+    component.provideAdapter(
+        zope.app.form.browser.CheckBoxWidget,
+        (zope.schema.interfaces.IBool,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ),
+        zope.app.form.interfaces.IInputWidget)
+    component.provideAdapter(
+        ICon('user_icon.gif'),
+        [zope.publisher.interfaces.browser.IBrowserRequest],
+        interface.Interface, 'user_icon.gif')
+    component.provideAdapter(
+        ICon('group_icon.gif'),
+        [zope.publisher.interfaces.browser.IBrowserRequest],
+        interface.Interface, 'group_icon.gif')
+    interface.directlyProvides(zc.table.table.FormFullFormatter,
+                               zc.table.interfaces.IFormatterFactory)
+    component.provideUtility(zc.table.table.FormFullFormatter,
+                             zc.table.interfaces.IFormatterFactory)
+
+def sharingTearDown(test):
+    placelesssetup.tearDown()
+    zc.sharing.sharing.clearPrivileges()
+
+def test_suite():
+    from zope.testing import doctest
+    return unittest.TestSuite((
+        doctest.DocFileSuite(
+            'sharing.txt',
+            setUp=sharingSetUp, tearDown=sharingTearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE,
+            ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/browser/user_icon.gif
===================================================================
(Binary files differ)


Property changes on: zc.sharing/trunk/src/zc/sharing/browser/user_icon.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/configure.zcml
===================================================================
--- zc.sharing/trunk/src/zc/sharing/configure.zcml	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/configure.zcml	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,36 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:zc="http://namespaces.zope.com/zc"
+    i18n_domain="zc.sharing">
+
+<content class=".index.Index">
+    <require permission="zope.ManageServices"
+             interface="zope.index.interfaces.IStatistics"
+             />
+</content>
+
+<adapter factory=".sharing.BaseSharing"
+         trusted="1" permission="zope.Security" />
+
+<adapter factory=".sharing.Sharing"
+         trusted="1" permission="zope.Security" />
+
+<adapter factory=".sharing.InitialSharing" />
+
+<subscriber for=".interfaces.ISharable
+                 zope.app.container.interfaces.IObjectAddedEvent"
+            handler=".sharing.initialSharing"
+            />
+
+<securityPolicy component=".policy.SecurityPolicy" />
+
+
+<include package=".browser" />
+
+<utility
+    name="zc.sharing.generation" 
+    provides="zope.app.generations.interfaces.ISchemaManager"
+    component=".generation.manager"
+    />  
+
+</configure>


Property changes on: zc.sharing/trunk/src/zc/sharing/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/generation/__init__.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/generation/__init__.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/generation/__init__.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import zope.app.generations
+
+minimum_generation = 0
+generation = 0
+
+manager = zope.app.generations.generations.SchemaManager(
+    minimum_generation, generation, 'zc.sharing.generation')


Property changes on: zc.sharing/trunk/src/zc/sharing/generation/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/generation/install.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/generation/install.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/generation/install.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,22 @@
+import zc.sharing.interfaces
+import ZODB.FileStorage.FileStorage
+
+def evolve(context):
+    # specifically for FileStorage, since all legacy instances are FileStorage
+    storage = context.connection.db()._storage
+    if not isinstance(storage, ZODB.FileStorage.FileStorage):
+        return
+    key = None
+    next_oid = 0
+    while next_oid is not None:
+        oid, tid, data, next_oid = storage.record_iternext(key)
+        obj = context.connection.get(oid)
+        sharing = zc.sharing.interfaces.IBaseSharing(obj, None)
+        if sharing is not None:
+            try:
+                data = sharing.annotations.pop('instranet.sharing.sharing')
+            except KeyError:
+                pass
+            else:
+                sharing.annotations['zc.sharing.sharing'] = data
+        key = next_oid


Property changes on: zc.sharing/trunk/src/zc/sharing/generation/install.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/i18n.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/i18n.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/i18n.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""I18N support for sharing.
+
+This defines a `MessageFactory` for the I18N domain for the sharing
+package.  This is normally used with this import::
+
+  from i18n import MessageFactory as _
+
+The factory is then used normally.  Two examples::
+
+  text = _('some internationalized text')
+  text = _('helpful-descriptive-message-id', 'default text')
+"""
+__docformat__ = "reStructuredText"
+
+
+from zope import i18nmessageid
+
+MessageFactory = _ = i18nmessageid.MessageFactory("zc.sharing")


Property changes on: zc.sharing/trunk/src/zc/sharing/i18n.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/index.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/index.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/index.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,111 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import BTrees.IFBTree
+import BTrees.OOBTree
+import BTrees.IOBTree
+import persistent
+
+from zope import component, interface
+import zope.security.interfaces
+import zope.security.proxy
+import zope.index.interfaces
+
+import zope.app.catalog.interfaces
+from zope.app import zapi
+
+from zc.sharing import interfaces, policy
+
+class Index(persistent.Persistent):
+
+    interface.implements(zope.app.catalog.interfaces.ICatalogIndex,
+                         zope.index.interfaces.IStatistics,
+                         )
+        
+    def __init__(self, privilege_id):
+        self.privilege_id = privilege_id
+        self.privileges = 1 << privilege_id
+        self.clear()
+    
+    def index_doc(self, doc_id, value):
+        self.unindex_doc(doc_id)
+
+        unproxied = zope.security.proxy.removeSecurityProxy(value)
+        sharing = interfaces.IBaseSharing(unproxied, None)
+        if sharing is None:
+            return
+
+        document_principals = self.document_principals
+        principal_documents = self.principal_documents
+
+        for principal_id in sharing.getPrincipals():
+            if self.privileges & sharing.getBinaryPrivileges(principal_id):
+                docs = principal_documents.get(principal_id)
+                if docs is None:
+                    docs = BTrees.IFBTree.IFTreeSet()
+                    principal_documents[principal_id] = docs
+                docs.insert(doc_id)
+
+                principals = document_principals.get(doc_id)
+                if principals is None:
+                    principals = BTrees.OOBTree.OOTreeSet()
+                    document_principals[doc_id] = principals
+                principals.insert(principal_id)
+
+
+    def unindex_doc(self, doc_id):
+        principals = self.document_principals.get(doc_id)
+        if principals is None:
+            return
+
+        for principal_id in principals:
+            docs = self.principal_documents.get(principal_id)
+            if docs and (doc_id in docs):
+                docs.remove(doc_id)
+
+        del self.document_principals[doc_id]
+
+    def clear(self):
+        self.principal_documents = BTrees.OOBTree.OOBTree()
+        self.document_principals = BTrees.IOBTree.IOBTree()
+
+    def apply(self, principal):
+        principal_documents = self.principal_documents
+        result = principal_documents.get(principal.id)
+
+        groups = {}
+        getPrincipal = zapi.principals().getPrincipal
+        policy._findGroupsFor(principal, getPrincipal, groups)
+
+        for gid in groups:
+            result = BTrees.IFBTree.union(result, principal_documents.get(gid))
+
+        if result is None:
+            result = BTrees.IFBTree.IFSet()
+
+        return result
+
+    # XXX need Length objects for scalability
+        
+    def documentCount(self):
+        return len(self.document_principals)        
+
+    def wordCount(self):
+        return len(self.principal_documents)
+


Property changes on: zc.sharing/trunk/src/zc/sharing/index.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/index.txt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/index.txt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/index.txt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,114 @@
+Sharing Index
+=============
+
+The sharing index supports "security-filtered search".  A sharing
+index keeps track of the principals that have the read priviledge for
+objects.  The index can then provide a set of all of the objects a
+principal can access, taking the principal's groups into account.
+
+Sharing indexes adapt their inputs to ISharing.  For illustrative
+purposes, we'll provide objects that provide ISharing directly.  We'll
+only bother implementing the methods that the sharing index actually
+uses:
+
+    >>> from zope import interface
+    >>> import zc.sharing.sharing
+    >>> from zc.sharing import interfaces
+    >>> zc.sharing.sharing.definePrivilege(0, "Read")
+    >>> class SampleDoc:
+    ...      interface.implements(interfaces.IBaseSharing)
+    ...
+    ...      def __init__(self, *principal_ids):
+    ...          self.principal_ids = principal_ids
+    ...
+    ...      def getPrincipals(self):
+    ...          return self.principal_ids
+    ...
+    ...      def getBinaryPrivileges(self, principal_id):
+    ...          if principal_id in self.principal_ids:
+    ...              return 2
+    ...          return 0
+
+
+Now we can try indexing some documents:
+
+    >>> import zc.sharing.index
+    >>> index = zc.sharing.index.Index(1)
+
+    >>> index.index_doc(1, SampleDoc('bob', 'Everyone'))
+    >>> index.index_doc(2, SampleDoc('bob'))
+    >>> index.index_doc(3, SampleDoc('sally', 'Editors'))
+    >>> index.index_doc(4, SampleDoc('sally', 'Reviewers'))
+    >>> index.index_doc(5, SampleDoc('sally'))
+    >>> index.index_doc(6, SampleDoc('sally', 'Everyone'))
+    >>> index.index_doc(7, SampleDoc('Workers'))
+    
+Note that, when we created the index, we passed the privilege id of
+the privilege to be used for the index.
+
+Now, we can search it to find the documents accessable to a principal.
+First we'll define a principal class.  Principals have a groups
+attribute that has the groups that the principal is contained in
+directly:
+
+    >>> class Principal:
+    ...     def __init__(self, id, *groups):
+    ...         self.id, self.groups = id, (groups + ('Everyone', ))
+
+The sharing index determines all of the groups a principal is in
+by loading information about their groups from an authentication
+utility.  We'll provide one that knows about our groups:
+
+    >>> from zope import component
+    >>> from zope.app.security.interfaces import IAuthentication
+    >>> class Authentication:
+    ...     interface.implements(IAuthentication)
+    ...     def getPrincipal(self, id):
+    ...         if id in ('Editors', 'Reviewers'):
+    ...             return Principal(id, 'Workers')
+    ...         else:
+    ...             return Principal(id) 
+
+    >>> component.provideUtility(Authentication())
+
+    >>> r = index.apply(Principal('sally', 'Editors', 'Reviewers'))
+    >>> r.__class__.__name__
+    'IFSet'
+
+    >>> list(r)
+    [1, 3, 4, 5, 6, 7]
+
+    >>> list(index.apply(Principal('fred')))
+    [1, 6]
+
+If we modify a document, so that different principals have access:
+
+    >>> index.index_doc(4, SampleDoc('fred', 'Reviewers'))
+
+Then the results change accordingly:
+
+    >>> list(index.apply(Principal('sally', 'Editors')))
+    [1, 3, 5, 6, 7]
+
+    >>> list(index.apply(Principal('fred')))
+    [1, 4, 6]
+
+And if we remove documents:
+
+    >>> index.unindex_doc(1)
+    >>> index.unindex_doc(4)
+
+    >>> list(index.apply(Principal('sally', 'Editors')))
+    [3, 5, 6, 7]
+
+    >>> list(index.apply(Principal('fred')))
+    [6]
+
+Of course, if we clear the index:
+
+    >>> index.clear()
+    >>> list(index.apply(Principal('sally', 'Editors')))
+    []
+
+    >>> list(index.apply(Principal('fred')))
+    []


Property changes on: zc.sharing/trunk/src/zc/sharing/index.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/interfaces.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/interfaces.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/interfaces.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,195 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""Z4I Security Policy (Sharing) APIs.
+
+$Id$
+"""
+
+from zope import interface, schema
+import zope.app.annotation.interfaces
+import zope.app.event.interfaces
+import zope.app.event.objectevent
+
+class ISharable(zope.app.annotation.interfaces.IAttributeAnnotatable):
+    """Sharable content
+
+    Sharable can be adapted to ISharing.
+    """
+
+class IBaseSharing(ISharable):
+
+    def getPrincipals():
+        """Return the principal ids for the principals that have privileges
+
+        The return value is an iterable.
+        """
+
+    def getBinaryPrivileges(principal_id):
+        """Get the principal's privileges
+
+        An integer privileges value is returned.
+        """
+
+    def setBinaryPrivileges(principal_id, privileges):
+        """Set the principal's privileges to the privileges passed
+
+        The privileges argument is an integer.
+        """
+
+    def sharedTo(id, principal_ids):
+        """Test whether the collection of principals have a privilege.
+        
+        privileges are identified with a bit position
+
+        Return a boolean value indicating whether the privilege has been
+        shared to any of the principals given by the principal_ids.
+
+        The principal_ids argument is an iterable of principal ids.
+        """
+
+class ISharing(IBaseSharing):
+
+    def removeBinaryPrivileges(principal_id, mask):
+        "Remove the privileges in the bit mask for principal_id"
+
+    def addBinaryPrivileges(principal_id, mask):
+        "Add the privileges in the bit mask for principal_id"
+    
+    def getIdPrivilege(principal_id, id):
+        """Test whether the privilege is shared to a principal
+
+        Return a boolean value indicating whether the privilege has been
+        shared to the principal_id.
+        """
+
+    def setIdPrivilege(principal_id, id, value):
+        """set the privilege for the principal.
+        
+        Leaves all other privileges alone.
+        """
+
+    def getIdPrivileges(principal_id):
+        """Return a sequence of the bit positions for the given principal
+        """
+
+    def setIdPrivileges(principal_id, ids):
+        "Set the principals privileges to those specified by the bit positions"
+
+    def addIdPrivileges(principal_id, ids):
+        """Add privileges, specified by bit positions, for principal.  
+        
+        If principal already has privilege, it is silently ignored"""
+
+    def removeIdPrivileges(principal_id, ids):
+        """Remove privileges, specified by bit positions, for principal.  
+        
+        If principal already does not have privilege, it is silently ignored
+        """
+
+    def getPrivilege(principal_id, title):
+        """Test whether the privilege is shared to a principal
+
+        Return a boolean value indicating whether the privilege has been
+        shared to the principal_id.
+        """
+
+    def setPrivilege(principal_id, title, value):
+        """set the privilege for the principal.
+        
+        Leaves all other privileges alone.
+        """
+
+    def getPrivileges(principal_id):
+        "Return a sequence of the sharing titles for the principal_id"
+
+    def setPrivileges(principal_id, titles):
+        "Set the principals privileges to those specified by the titles"
+
+    def addPrivileges(principal_id, titles):
+        """Add privileges, specified by titles, for principal.
+        
+        If principal already has privilege, it is silently ignored"""
+
+    def removePrivileges(principal_id, titles):
+        """Remove privileges, specified by titles, for principal.
+        
+        If principal already does not have privilege, it is silently ignored
+        """
+
+class ISharingPrivileges(interface.Interface):
+
+    privileges = schema.Tuple(
+        title=u"Ids of privileges used by a content type",
+        value_type=schema.Int(),
+        )
+
+class ISubobjectSharingPrivileges(interface.Interface):
+
+    subobjectPrivileges = schema.Tuple(
+        title=u"Ids of privileges used by subobjects of a content type",
+        value_type=schema.Int(),
+        )
+
+class IInitialSharing(interface.Interface):
+    """Adapter to set sharing for newly added objects
+    """
+
+    def share():
+        """Set the initial sharing for a sharable object
+        """
+
+class ISharingMacro(interface.Interface):
+    """Sharing macros provide pluggable rules for creating sharing settings.
+
+    They are registered as named adapters for a context and a request.
+    """
+
+    def share(sharing):
+        """Modify the settings for a sharing object.
+        
+        Return a boolean True if the macro changed sharing, False otherwise.
+        """
+
+    order = schema.Int(title=u"Order in which item should be displayed")
+
+    title = schema.TextLine(title=u'title', description=
+        u'''The display title of the sharing macro.
+        Will typically be a zope.i18n.Message.  If None, the registered
+        adapter name will be used.''', required=False)
+
+class ISharingEvent(zope.app.event.interfaces.IObjectModifiedEvent):
+    "An event fired by the sharing package"
+
+class ISharingChanged(ISharingEvent):
+    """Sharing settings were changed for an object
+    """
+
+    principal_id = schema.TextLine(
+        title=u"The id of the principal who's sharing has changed",
+        )
+
+    old = schema.Int(title=u"Old settings")
+
+    new = schema.Int(title=u"Old settings")
+
+class SharingChanged(zope.app.event.objectevent.ObjectModifiedEvent):
+
+    interface.implements(ISharingChanged)
+
+    def __init__(self, object, principal_id, old, new):
+        zope.app.event.objectevent.ObjectModifiedEvent.__init__(self, object)
+        self.principal_id = principal_id
+        self.old = old
+        self.new = new


Property changes on: zc.sharing/trunk/src/zc/sharing/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/meta.zcml
===================================================================
--- zc.sharing/trunk/src/zc/sharing/meta.zcml	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/meta.zcml	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,29 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:meta="http://namespaces.zope.org/meta">
+
+  <meta:directive namespace="http://namespaces.zope.com/zc"
+                  name="privilege"
+                  schema=".zcml.IdefinePrivilege"
+                  handler=".zcml.definePrivilege" />
+
+  <meta:directive namespace="http://namespaces.zope.com/zc"
+                  name="permissionPrivilege"
+                  schema=".zcml.IpermissionPrivilege"
+                  handler=".zcml.permissionPrivilege" />
+
+  <meta:directive namespace="http://namespaces.zope.com/zc"
+                  name="systemAdministrators"
+                  schema=".zcml.IsystemAdministrators"
+                  handler=".zcml.systemAdministrators" />
+
+  <meta:directive namespace="http://namespaces.zope.com/zc"
+                  name="privileges"
+                  schema=".zcml.Iprivileges"
+                  handler=".zcml.privileges" />
+
+  <meta:directive namespace="http://namespaces.zope.com/zc"
+                  name="subobjectPrivileges"
+                  schema=".zcml.Iprivileges"
+                  handler=".zcml.subobjectPrivileges" />
+
+</configure>


Property changes on: zc.sharing/trunk/src/zc/sharing/meta.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/policy.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/policy.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/policy.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,210 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""Zope4Intranets security policy
+
+$Id$
+"""
+
+from zope import interface, component
+
+from zope.app import zapi
+from zope.app.security.interfaces import PrincipalLookupError
+
+from zope.security.checker import CheckerPublic
+from zope.security.management import system_user
+from zope.security.simplepolicies import ParanoidSecurityPolicy
+from zope.security.interfaces import ISecurityPolicy
+from zope.security.proxy import removeSecurityProxy
+from zope.app.security.interfaces import PrincipalLookupError
+
+from zc.sharing import interfaces, sharing
+
+admin_group = 'zc.intranet.policy.admingroup'
+
+class CacheEntry:
+    pass
+
+class SecurityPolicy(ParanoidSecurityPolicy):
+    interface.classProvides(ISecurityPolicy)
+
+    def __init__(self, *args, **kw):
+        ParanoidSecurityPolicy.__init__(self, *args, **kw)
+        self._cache = {}
+
+    def invalidateCache(self):
+        self._cache = {}
+
+    def cache(self, parent):
+        cache = self._cache.get(id(parent))
+        if cache:
+            cache = cache[0]
+        else:
+            cache = CacheEntry()
+            self._cache[id(parent)] = cache, parent
+        return cache
+
+    def cachedDecision(self, parent, principal, groups, privilege):
+        # Return the decision for a principal and permission
+
+        cache = self.cache(parent)
+        try:
+            cache_decision = cache.decision
+        except AttributeError:
+            cache_decision = cache.decision = {}
+
+        cache_decision_prin = cache_decision.get(principal)
+        if not cache_decision_prin:
+            cache_decision_prin = cache_decision[principal] = {}
+
+        try:
+            return cache_decision_prin[privilege]
+        except KeyError:
+            pass
+
+        sharing = interfaces.IBaseSharing(parent, None)
+        if sharing is not None:
+            decision = sharing.sharedTo(privilege, groups)
+        elif parent is None:
+            decision = False
+        else:
+            parent = removeSecurityProxy(getattr(parent, '__parent__', None))
+            decision = self.cachedDecision(
+                parent, principal, groups, privilege)
+
+
+        cache_decision_prin[privilege] = decision
+        return decision
+
+
+    def checkPermission(self, permission, object):
+        if permission is CheckerPublic:
+            return True
+
+        object = removeSecurityProxy(object)
+        seen = {}
+        for participation in self.participations:
+            principal = participation.principal
+            if principal is system_user:
+                continue # always allow system_user
+
+            if principal.id in seen or principal.id in systemAdministrators:
+                continue
+
+
+            groups = self._groupsFor(principal)
+
+            privilege = _permissionPrivileges.get(permission, -1)
+            if privilege < 0:
+                # No privilege
+                return False
+
+            if admin_group not in groups:
+                # admins have all privileges:
+
+                if not self.cachedDecision(
+                    object, principal.id, groups, privilege,
+                    ):
+                    return False
+
+            seen[principal.id] = 1
+
+        return True
+
+    def _groupsFor(self, principal):
+        groups = self._cache.get(principal.id)
+        if groups is None:
+            groups = getattr(principal, 'groups', ())
+            if groups:
+                groups = {}
+                getPrincipal = zapi.principals().getPrincipal
+                _findGroupsFor(principal, getPrincipal, groups)
+            else:
+                groups = {}
+
+            groups[principal.id] = 1
+
+            self._cache[principal.id] = groups
+
+        return groups
+
+def _findGroupsFor(principal, getPrincipal, seen):
+    for group_id in getattr(principal, 'groups', ()):
+        if group_id in seen:
+            # Dang, we have a cycle.  We don't want to
+            # raise an exception here (or do we), so we'll skip it
+            continue
+        seen[group_id] = 1
+
+        try:
+            group = getPrincipal(group_id)
+        except PrincipalLookupError:
+            # It's bad if we have an undefined principal,
+            # but we don't want to fail here.  But we won't
+            # honor any grants for the group. We'll just skip it.
+            continue
+
+        _findGroupsFor(group, getPrincipal, seen)
+
+_permissionPrivileges = {}
+permissionPrivilege = _permissionPrivileges.__setitem__
+getPermissionPrivilege = _permissionPrivileges.__getitem__
+removePermissionPrivilege = _permissionPrivileges.__delitem__
+
+def fixedAdapter(adapter):
+
+    def adapt(ob):
+        return adapter
+
+    return adapt
+
+class SharingPrivileges:
+    interface.implements(interfaces.ISharingPrivileges)
+
+    def __init__(self, privileges):
+        self.privileges = tuple(privileges)
+
+def sharingPrivileges(for_, titles):
+    defined = dict([(p['title'], p) for p in sharing.getPrivileges()])
+
+    ids = []
+    for title in titles:
+        try:
+            ids.append(defined[title]['id'])
+        except KeyError:
+            raise ValueError("Undefined privilege", title)
+
+    component.provideAdapter(fixedAdapter(SharingPrivileges(ids)),
+                             (for_, ), interfaces.ISharingPrivileges)
+
+class SubobjectSharingPrivileges:
+    interface.implements(interfaces.ISharingPrivileges)
+
+    def __init__(self, privileges):
+        self.subobjectPrivileges = tuple(privileges)
+
+def subobjectSharingPrivileges(for_, titles):
+    defined = dict([(p['title'], p) for p in sharing.getPrivileges()])
+
+    ids = []
+    for title in titles:
+        try:
+            ids.append(defined[title]['id'])
+        except KeyError:
+            raise ValueError("Undefined privilege", title)
+
+    component.provideAdapter(fixedAdapter(SubobjectSharingPrivileges(ids)),
+                             (for_, ), interfaces.ISubobjectSharingPrivileges)
+
+systemAdministrators = ()


Property changes on: zc.sharing/trunk/src/zc/sharing/policy.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/policy.txt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/policy.txt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/policy.txt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,270 @@
+Zope for Intranets Security Policy
+==================================
+
+This package implements a security policy based on privileges.  A
+privilege is a very abstract permission.  The security policy is
+responsible for deciding whether an interaction has a permission on an
+object.  This security policy does this using privilege-grant
+information.  Users with the sharing privilege can grant privileges
+to users or groups.
+
+Privileges
+-----------
+
+First we must define our privileges, as discussed in sharing.txt, and then
+map our permissions onto them.
+
+  >>> import zc.sharing.sharing
+  >>> zc.sharing.sharing.definePrivilege(0, "Read", "Read content")
+  >>> zc.sharing.sharing.definePrivilege(2, "Write", "Write content")
+  >>> zc.sharing.sharing.definePrivilege(
+  ...   4, "Share", "Share content (grant privileges)")
+  >>> from zc.sharing import policy
+  >>> policy.permissionPrivilege('R1', 0)
+  >>> policy.permissionPrivilege('R2', 0)
+  >>> policy.permissionPrivilege('W1', 2)
+  >>> policy.permissionPrivilege('W2', 2)
+  >>> policy.permissionPrivilege('W3', 2)
+  >>> policy.permissionPrivilege('S1', 4)
+
+Principals and Interactions
+---------------------------
+
+We use objects to represent principals.  These objects implement an
+interface named `IPrincipal`, but the security policy only uses the `id`
+and `groups` attributes:
+
+  >>> class Principal:
+  ...     def __init__(self, id):
+  ...         self.id = id
+  ...         self.groups = []
+
+  >>> principal = Principal('bob')
+
+Privileges and permissions are also represented by objects, however, for
+the purposes of the security policy, only string `ids` are used.
+
+The security policy provides a factory for creating interactions:
+
+  >>> interaction = policy.SecurityPolicy()
+
+An interaction represents a specific interaction between some
+principals (normally users) and the system.  Normally, we are only
+concerned with the interaction of one principal with the system, although
+we can have interactions of multiple principals.  Multiple-principal
+interactions normally occur when untrusted users store code on a
+system for later execution.  When untrusted code is executing, the
+authors of the code participate in the interaction.  An
+interaction has a permission on an object only if all of the
+principals participating in the interaction have access to the object.
+
+The `checkPermission` method on interactions is used to test whether
+an interaction has a permission for an object.  An interaction without
+participants always has every permission:
+
+  >>> import zope.interface
+  >>> class Ob:
+  ...     pass
+
+  >>> ob = Ob()
+
+  >>> interaction.checkPermission('W1', ob)
+  True
+
+In this example, 'W1' is a permission id.
+
+Normally, interactions have participants:
+
+  >>> class Participation:
+  ...     interaction = None
+  >>> participation = Participation()
+  >>> participation.principal = principal
+  >>> interaction.add(participation)
+
+If we have participants, then we don't have a permission unless there
+are grants:
+
+    >>> interaction.checkPermission('W1', ob)
+    False
+
+Note, however, that we always have the CheckerPublic permission:
+
+    >>> from zope.security.checker import CheckerPublic
+    >>> interaction.checkPermission(CheckerPublic, ob)
+    True
+
+Grants
+------
+
+We make grants on objects by adapting them to ISharing.  Here's a very
+simple adapter that implements ISharing:
+
+    >>> from zc.sharing import interfaces
+    >>> from zope import component
+    >>> class Sharing:
+    ...
+    ...     component.adapts(Ob)
+    ...     zope.interface.implements(interfaces.ISharing)
+    ...
+    ...     def __init__(self, context):
+    ...         self.context = context
+    ...
+    ...     def getPrincipals(self):
+    ...         return getattr(self.context, 'privileges', {}).keys()
+    ...
+    ...     def getPrivileges(self, principal_id):
+    ...         privileges = getattr(self.context, 'privileges', {})
+    ...         return privileges.get(principal_id, 0)
+    ...
+    ...     def setPrivileges(self, principal_id, privileges):
+    ...         current = getattr(self.context, 'privileges', None)
+    ...         if current is None:
+    ...             self.context.privileges = current = {}
+    ...         if privileges:
+    ...             current[principal_id] = privileges
+    ...         else:
+    ...             if principal_id in current:
+    ...                 del current[principal_id]
+    ...         interaction.invalidateCache()
+    ...
+    ...     def sharedTo(self, privilege, principal_ids):
+    ...         privileges = getattr(self.context, 'privileges', {})
+    ...         for principal_id in principal_ids:
+    ...             if 2**privilege & privileges.get(principal_id, 0):
+    ...                 return True
+    ...         return False
+
+    >>> component.provideAdapter(Sharing)
+
+(Note that in setPrivileges, we invalidated the interaction cache.)
+
+Now we can grant privileges to our object. Let's grant the write
+privilege to bob:
+
+    >>> sharing = interfaces.ISharing(ob)
+    >>> sharing.setPrivileges('bob', 2**2)
+
+Now, we have the permission:
+
+    >>> interaction.checkPermission('W1', ob)
+    True
+
+because the write privilege has the permission and our principal,
+'bob', has the privilege.  Of course, we still don't have the write
+privilege:
+
+    >>> interaction.checkPermission('R1', ob)
+    False
+
+
+Non-Sharing objects
+-------------------
+
+If an object doesn't support sharing, then access isn't granted:
+
+    >>> class Other:
+    ...     pass
+
+    >>> other = Other()
+    >>> interaction.checkPermission('W1', other)
+    False
+
+However, if the object has a parent that supports sharing, then access
+depends on the parent's sharing:
+
+    >>> other = Other()
+    >>> other.__parent__ = ob
+    >>> interaction.checkPermission('W1', other)
+    True
+
+This applies to ancestors too:
+
+    >>> other = Other()
+    >>> other.__parent__ = Other()
+    >>> interaction.checkPermission('W1', other)
+    False
+
+    >>> other = Other()
+    >>> other.__parent__ = Other()
+    >>> other.__parent__.__parent__ = ob
+    >>> interaction.checkPermission('W1', other)
+    True
+
+Groups
+------
+
+Principals may have groups.  Groups are also principals (and, thus,
+may have groups).
+
+If a principal has groups, the groups are available as group ids in
+the principal's `groups` attribute.  The interaction has to convert
+these group ids to group objects, so that it can tell whether the
+groups have groups.  It does this by calling the `getPrincipal` method
+on the principal authentication service, which is responsible for,
+among other things, converting a principal id to a principal.
+For our examples here, we'll create and register a stub principal
+authentication service:
+
+    >>> from zope.app.security.interfaces import IAuthentication
+    >>> class FauxPrincipals(dict):
+    ...     zope.interface.implements(IAuthentication)
+    ...     def getPrincipal(self, id):
+    ...         return self[id]
+
+    >>> auth = FauxPrincipals()
+
+    >>> from zope.app.tests import ztapi
+    >>> ztapi.provideUtility(IAuthentication, auth)
+    >>> from zope.app import zapi
+
+Let's define a group and assign it the read privilege:
+
+    >>> auth['g1'] = Principal('g1')
+    >>> sharing.setPrivileges('g1', 2**0)
+
+Let's put the principal in our group.  We do that by adding the group id
+to the new principal's groups:
+
+    >>> principal.groups.append('g1')
+
+And now we have the read permission:
+
+    >>> interaction.checkPermission('R1', ob)
+    True
+
+Of course, this works with the non-sharable object that has this
+object as an ancestor:
+
+    >>> interaction.checkPermission('R1', other)
+    True
+
+Administrative groups
+---------------------
+
+There is a special administrative groups, defined by the
+policy, that has all privileges:
+
+    >>> auth[policy.admin_group] = Principal(policy.admin_group)
+
+Our principal doesn't have the share privilege:
+
+    >>> interaction.checkPermission('S1', ob)
+    False
+
+But if we put them in the admin group, they do
+
+    >>> principal.groups.append(policy.admin_group)
+    >>> interaction.invalidateCache()
+    >>> interaction.checkPermission('S1', ob)
+    True
+
+There is a collection of system administrators that have all
+permissions, including those that aren't associated with privileges:
+
+    >>> interaction.checkPermission('P1', ob)
+    False
+
+    >>> policy.systemAdministrators = ('bob', )
+
+    >>> interaction.checkPermission('P1', ob)
+    True


Property changes on: zc.sharing/trunk/src/zc/sharing/policy.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/sharing.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/sharing.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/sharing.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,280 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""Sharing adapter
+
+$Id$
+"""
+
+import persistent
+import persistent.dict
+
+from zope import component, interface, event
+
+from zope.security.management import queryInteraction
+from zope.publisher.interfaces import IRequest
+
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.container.interfaces import IObjectAddedEvent
+
+from zc.sharing import interfaces
+from zc.sharing.i18n import _
+
+key = 'zc.sharing.sharing'
+
+class SharingData(persistent.dict.PersistentDict):
+    """Sharing Data
+    """
+
+class BaseSharing(object):
+
+    component.adapts(interfaces.ISharable)
+    interface.implements(interfaces.IBaseSharing)
+
+    def __init__(self, context):
+        self.context = context
+        self.annotations = IAnnotations(self.context)
+
+    def getPrincipals(self):
+        privileges = self.annotations.get(key)
+        if privileges:
+            return privileges.keys()
+        return ()
+
+    def getBinaryPrivileges(self, principal_id):
+        privileges = self.annotations.get(key)
+        if privileges:
+            return privileges.get(principal_id, 0)
+        return 0
+
+    def setBinaryPrivileges(self, principal_id, privileges):
+        saved = self.annotations.get(key)
+        if saved is None:
+            self.annotations[key] = saved = SharingData()
+
+        old = saved.get(principal_id, 0)
+        if privileges:
+            saved[principal_id] = privileges
+        else:
+            if principal_id in saved:
+                del saved[principal_id]
+
+        interaction = queryInteraction()
+        if interaction is not None:
+            try:
+                invalidateCache = interaction.invalidateCache
+            except AttributeError:
+                pass
+            else:
+                invalidateCache()
+
+        if old != privileges:
+            event.notify(
+                interfaces.SharingChanged(
+                    self.context, principal_id, old, privileges,
+                    )
+                )
+
+    def sharedTo(self, id, principal_ids):
+        privileges = self.annotations.get(key)
+        if privileges:
+            bit = 2**id
+            for principal_id in principal_ids:
+                if bit & privileges.get(principal_id, 0):
+                    return True
+        return False
+
+class Sharing(object):
+
+    component.adapts(interfaces.ISharable)
+    interface.implements(interfaces.ISharing)
+
+    def __init__(self, context):
+        self.context = context
+        self.base = interfaces.IBaseSharing(context)
+
+    # look transparently through to base for IBaseSharing methods
+    def __getattr__(self, name):
+        if name in (
+            'getPrincipals', 'getBinaryPrivileges', 'setBinaryPrivileges',
+            'sharedTo'):
+            return getattr(self.base, name)
+
+    # additional ISharing methods
+    def removeBinaryPrivileges(self, principal_id, mask):
+        privs = self.base.getBinaryPrivileges(principal_id)
+        self.base.setBinaryPrivileges(principal_id, (privs | mask) ^ mask)
+
+    def addBinaryPrivileges(self, principal_id, mask):
+        privs = self.base.getBinaryPrivileges(principal_id)
+        self.base.setBinaryPrivileges(principal_id, privs | mask)
+
+    def getIdPrivilege(self, principal_id, id):
+        return self.base.sharedTo(id, (principal_id,))
+    
+    def setIdPrivilege(self, principal_id, id, value):
+        if value:
+            return self.addIdPrivileges(principal_id, (id,))
+        else:
+            return self.removeIdPrivileges(principal_id, (id,))
+
+    def getIdPrivileges(self, principal_id):
+        return idsFromSetting(self.base.getBinaryPrivileges(principal_id))
+
+    def setIdPrivileges(self, principal_id, ids):
+        return self.base.setBinaryPrivileges(
+            principal_id, settingFromIds(ids))
+
+    def addIdPrivileges(self, principal_id, ids):
+        return self.addBinaryPrivileges(
+            principal_id, settingFromIds(ids))
+
+    def removeIdPrivileges(self, principal_id, ids):
+        return self.removeBinaryPrivileges(
+            principal_id, settingFromIds(ids))
+
+    def getPrivilege(self, principal_id, title):
+        return self.getIdPrivilege(
+            principal_id, getIdByTitle(title))
+
+    def setPrivilege(self, principal_id, title, value):
+        return self.setIdPrivilege(
+            principal_id, getIdByTitle(title), value)
+
+    def getPrivileges(self, principal_id):
+        return [getPrivilege(bit)['title'] for bit
+                in self.getIdPrivileges(principal_id)]
+
+    def setPrivileges(self, principal_id, titles):
+        return self.setIdPrivileges(
+            principal_id, (getIdByTitle(title) for title in titles))
+
+    def addPrivileges(self, principal_id, titles):
+        return self.addIdPrivileges(
+            principal_id, (getIdByTitle(title) for title in titles))
+
+    def removePrivileges(self, principal_id, titles):
+        return self.removeIdPrivileges(
+            principal_id, (getIdByTitle(title) for title in titles))
+
+octToBit = {
+    '0': (),
+    '1': (0,),
+    '2': (1,),
+    '3': (0, 1),
+    '4': (2,),
+    '5': (0, 2),
+    '6': (1, 2),
+    '7': (0, 1, 2)
+    }
+def idsFromSetting(i):
+    "given an integer, return a sequence of the bits that are on"
+    res = []
+    for pos, c in enumerate(oct(i)[-1:0:-1]):
+        place = pos*3
+        res.extend(place+bit for bit in octToBit[c])
+    return res
+
+def settingFromIds(bits):
+    "Given any number of bit positions, generate a corresponding integer"
+    val = 0
+    for bit in bits:
+        val |= 1<<bit
+    return val
+
+def settingFromTitles(titles):
+    return settingFromIds(getIdByTitle(t) for t in titles)
+
+def titlesFromSetting(setting):
+    return (getPrivilege(i)['title'] for i in idsFromSetting(setting))
+
+def sharingMask(ob):
+    mask = 0
+    for bit in interfaces.ISharingPrivileges(ob).privileges:
+        mask |= 1 << bit
+    privs = interfaces.ISubobjectSharingPrivileges(ob, None)
+    if privs is not None:
+        for bit in privs.subobjectPrivileges:
+            mask |= 1 << bit
+    
+    return mask
+
+
+_privileges_by_bit = {}
+_privileges_by_title = {}
+
+def definePrivilege(id, title, description='', info=None):
+    if title in _privileges_by_title:
+        raise ValueError("Duplicate title") # TODO should be catchable in zcml
+    if id in _privileges_by_bit:
+        raise ValueError("Duplicate id") # is caught in zcml
+    _privileges_by_bit[id] = {
+        'id': id,
+        'title': title,
+        'description': description,
+        'info': info,
+        }
+    _privileges_by_title[title] = id
+def removePrivilege(id):
+    data = _privileges_by_bit.pop(id)
+    del _privileges_by_title[data['title']]
+def clearPrivileges():
+    _privileges_by_bit.clear()
+    _privileges_by_title.clear()
+getPrivileges = _privileges_by_bit.values
+getPrivilege = _privileges_by_bit.get
+getIdByTitle = _privileges_by_title.__getitem__
+
+class InitialSharing(object):
+
+    component.adapts(interfaces.ISharable, IObjectAddedEvent)
+    interface.implements(interfaces.IInitialSharing)
+
+    def __init__(self, *contexts):
+        self.ob, self.event = contexts
+
+    def sharingMask(self):
+        return sharingMask(self.ob)
+
+    def share(self):
+        ob = self.ob
+        event = self.event
+
+        sharing = interfaces.IBaseSharing(ob)
+        if tuple(sharing.getPrincipals()):
+            return # already shared
+        
+
+        mask = self.sharingMask()
+
+        # Get the parent settings:
+        parent = event.newParent
+        psharing = interfaces.IBaseSharing(parent, None)
+        if psharing is not None:
+            for p in psharing.getPrincipals():
+                sharing.setBinaryPrivileges(
+                    p, psharing.getBinaryPrivileges(p) & mask)
+
+        # Share everything with current user, if any
+        interactions = queryInteraction()
+        if interactions is not None:
+            for participation in interactions.participations:
+                if IRequest.providedBy(participation):
+                    sharing.setBinaryPrivileges(
+                        participation.principal.id, mask)
+
+def initialSharing(ob, event):
+    adapter = component.getMultiAdapter((ob, event),
+                                        interfaces.IInitialSharing)
+    adapter.share()


Property changes on: zc.sharing/trunk/src/zc/sharing/sharing.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/sharing.txt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/sharing.txt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/sharing.txt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,509 @@
+Sharing Adapters
+================
+
+BaseSharing Adapter
+-------------------
+
+The BaseSharing adapter implements the `IBaseSharing` interface for
+`ISharable` objects.
+
+IBaseSharing is an interface that provides the minimum tools needed to work
+with sharing.  The ISharing interface, an extension of IBaseSharing discussed
+below, offers more convenient access to the sharing capabilities; ISharing's
+getPrivilege, setPrivilege, getPrivileges, setPrivileges, addPrivileges, and
+removePrivileges, combined with IBaseSharing's getPrincipals, are intended to
+be the most common ways for non-security-policy code to use the interface.
+The sharedTo method is important for policy code.  The other methods expose 
+the core sharing design as used for storage and security checking.
+
+We begin by discussing the IBaseSharing interface, because it is the best way
+to teach sharing thoroughly.  If you are interested more in a quick start, skim
+to find the discussion of the more convenient methods below.
+
+The IBaseSharing interface uses bits to store privileges.  These bits are 
+exposed all together in a combined integer by the getBinaryPrivileges and
+setBinaryPrivileges methods, and by individual bit position in sharedTo (as 
+well as in many of the methods in ISharing, discussed below).  The bit 
+position is called 'id' in the code.  For instance, bit position (or 'id') 
+0, 1, 2, 3, and 4 are exposed in get- and setBinaryPrivileges as bits 1,
+2, 4, 8, and 16. Below, when we set privileges to 21, that indicates privilege
+ids 0, 2, and 4 (1+4+16=21).
+
+By convention, product-level bit positions are even, and customization-level
+ids are odd.
+
+Setting privileges fires interfaces.SharingChanged events if the sharing
+changed.
+
+    >>> import zope.interface
+    >>> from zope.interface.verify import verifyObject
+    >>> from zc.sharing import interfaces
+
+    >>> class MyContent:
+    ...     zope.interface.implements(interfaces.ISharable)
+
+    >>> import zc.sharing.sharing
+    >>> content = MyContent()
+    >>> sharing = zc.sharing.sharing.BaseSharing(content)
+    >>> verifyObject(interfaces.IBaseSharing, sharing)
+    True
+
+    >>> tuple(sharing.getPrincipals())
+    ()
+    >>> sharing.getBinaryPrivileges('bob')
+    0
+    >>> sharing.sharedTo(0, 'bob')
+    False
+
+    >>> sharing.setBinaryPrivileges('bob', 21)
+    >>> ev = events[-1]
+    >>> verifyObject(interfaces.ISharingChanged, ev)
+    True
+    >>> ev.object is content
+    True
+    >>> ev.principal_id
+    'bob'
+    >>> ev.old
+    0
+    >>> ev.new
+    21
+
+    >>> sharing.setBinaryPrivileges('mary', 1)
+    >>> ev = events[-1]
+    >>> verifyObject(interfaces.ISharingChanged, ev)
+    True
+    >>> ev.object is content
+    True
+    >>> ev.principal_id
+    'mary'
+    >>> ev.old
+    0
+    >>> ev.new
+    1
+
+    >>> sorted(sharing.getPrincipals())
+    ['bob', 'mary']
+
+    >>> sharing.getBinaryPrivileges('bob')
+    21
+
+    >>> sharing.sharedTo(0, ['bob'])
+    True
+
+(This example is misleading: sharedTo generally takes a principal and all of
+his or her groups, so this would be an example of user who is simultaneously
+both 'bob' and 'mary'.  The policy module handles multiple users in an
+interaction.)
+
+    >>> sharing.sharedTo(4, ['bob', 'mary'])
+    True
+
+    >>> sharing.sharedTo(1, ['bob', 'mary'])
+    False
+
+
+The sharing data are stored persistently:
+
+    >>> import ZODB.tests.util
+    >>> db = ZODB.tests.util.DB()
+    >>> conn = db.open()
+    >>> root = conn.root()
+    >>> root['spam'] = content
+    >>> ZODB.tests.util.commit()
+    >>> conn.cacheMinimize()
+
+    >>> sorted(sharing.getPrincipals())
+    ['bob', 'mary']
+
+    >>> sharing.getBinaryPrivileges('bob')
+    21
+
+    >>> sharing.sharedTo(0, ['bob'])
+    True
+
+    >>> sharing.sharedTo(1, ['bob'])
+    False
+
+    >>> sharing.setBinaryPrivileges('bob', 18)
+    >>> ev = events[-1]
+    >>> verifyObject(interfaces.ISharingChanged, ev)
+    True
+    >>> ev.object is content
+    True
+    >>> ev.principal_id
+    'bob'
+    >>> ev.old
+    21
+    >>> ev.new
+    18
+
+    >>> ZODB.tests.util.commit()
+    >>> conn.cacheMinimize()
+
+    >>> sharing.sharedTo(0, ['bob'])
+    False
+
+    >>> sharing.sharedTo(1, ['bob'])
+    True
+
+    >>> sharing.setBinaryPrivileges('sally', 4)
+    >>> sharing.setBinaryPrivileges('bob', 0)
+    >>> ZODB.tests.util.commit()
+    >>> conn.cacheMinimize()
+    >>> sharing.sharedTo(0, ['bob'])
+    False
+    >>> sharing.sharedTo(1, ['bob'])
+    False
+
+    >>> sorted(sharing.getPrincipals())
+    ['mary', 'sally']
+
+Initial sharing
+---------------
+
+An adapter is used to set the initial sharing for objects.
+The default adapter:
+
+- Copies sharing settings from the object's container
+
+- Gives the current user all privileges
+
+The adapter provides IInitialSharing and is called when an object is
+added (not moved) to a container.  The default adapter is provided by
+the InitialSharing class:
+
+    >>> from zc.sharing.sharing import InitialSharing
+
+Let's look at an example. The InitialSharing adapter will adapt the
+object it adapts to IBaseSharing.  Here's an example object that is
+adaptable to IBaseSharing because it already provides it:
+
+    >>> from zope import interface
+    >>> from zc.sharing.interfaces import IBaseSharing
+    >>> class MyOb:
+    ...
+    ...    interface.implements(IBaseSharing)
+    ...
+    ...    def __init__(self):
+    ...        self.privileges = {}
+    ...
+    ...    def getPrincipals(self):
+    ...        return self.privileges.keys()
+    ...
+    ...    def getBinaryPrivileges(self, principal):
+    ...        return self.privileges.get(principal, 0)
+    ...
+    ...    def setBinaryPrivileges(self, principal, privileges):
+    ...        if privileges:
+    ...            self.privileges[principal] = privileges
+    ...        else:
+    ...            del self.privileges[principal]
+
+    >>> class Container(MyOb):
+    ...     pass
+
+Now we'll create a container, and give it some privileges:
+
+    >>> container = Container()
+    >>> container.setBinaryPrivileges('p1', 31)
+
+Now, if we create a subobject, privileges will be copied from the
+container to the subobject, however, only those privileges that
+pertain to the subobject are copied.  This is determined by the
+per-type privilege definitions.  Let's define some privileges and set
+which ones apply to our type:
+
+    >>> zc.sharing.sharing.definePrivilege(0, "Share")
+    >>> zc.sharing.sharing.definePrivilege(1, "Work")
+    >>> zc.sharing.sharing.definePrivilege(2, "Play")
+    >>> zc.sharing.sharing.definePrivilege(3, "Read")
+    >>> zc.sharing.sharing.definePrivilege(4, "Write")
+
+We'll define the read, write, and share privileges on the container:
+
+    >>> from zc.sharing import policy
+    >>> policy.sharingPrivileges(Container, ["Read", "Write", "Share"])
+
+And we'll set the work, play, and share privileges on the subobject:
+
+    >>> policy.sharingPrivileges(MyOb, ["Play", "Work", "Share"])
+
+Because the container will contain MyOb instances, we'll define play,
+work, and share as subobject privileges:
+
+    >>> policy.subobjectSharingPrivileges(Container, ["Play", "Work", "Share"])
+
+Now, we'll add an object to the container:
+
+    >>> container.x = MyOb()
+    >>> container.x.__parent__ = MyOb()
+
+The new object has no privileges:
+
+    >>> container.x.getPrincipals()
+    []
+
+We generate an add event:
+
+    >>> from zope.app.container.contained import ObjectAddedEvent
+    >>> event = ObjectAddedEvent(container.x, container, 'x')
+
+Now, we call our adapter:
+
+    >>> adapter = InitialSharing(container.x, event)
+    >>> adapter.share()
+
+And we see that the privileges have been set:
+
+    >>> [(p, container.x.getBinaryPrivileges(p))
+    ...  for p in container.x.getPrincipals()]
+    [('p1', 7)]
+
+Note that only the privileges defined for MyOb instances were set.
+
+Privileges are only copied if they were not previously set.  Let's
+change the settings on out container:
+
+    >>> container.setBinaryPrivileges('p1', 28)
+    >>> container.setBinaryPrivileges('p2', 31)
+
+Now if we call the share method, there is no effect:
+
+    >>> adapter.share()
+    >>> [(p, container.x.getBinaryPrivileges(p))
+    ...  for p in container.x.getPrincipals()]
+    [('p1', 7)]
+
+Unless we remove the settings in our subobject:
+
+    >>> container.x.setBinaryPrivileges('p1', 0)
+    >>> list(container.x.getPrincipals())
+    []
+
+    >>> adapter.share()
+    >>> privs = [(p, container.x.getBinaryPrivileges(p))
+    ...          for p in container.x.getPrincipals()]
+    >>> privs.sort()
+    >>> privs
+    [('p1', 4), ('p2', 7)]
+
+If there is an interaction, and if any of the participations are
+requests (iow, if any of the participants are participating by way of
+a UI), then these participats will be given fill access:
+
+    >>> class Principal:
+    ...     def __init__(self, id):
+    ...         self.id = id
+
+    >>> from zope.publisher.interfaces import IRequest
+    >>> class Request:
+    ...     interface.implements(IRequest)
+    ...     def __init__(self, principal):
+    ...         self.principal = principal
+    ...         self.interaction = None
+
+    >>> from zope.security.management import newInteraction
+    >>> newInteraction(Request(Principal('p3')))
+
+    >>> container.x.setBinaryPrivileges('p1', 0)
+    >>> container.x.setBinaryPrivileges('p2', 0)
+    >>> list(container.x.getPrincipals())
+    []
+
+    >>> adapter.share()
+    >>> privs = [(p, container.x.getBinaryPrivileges(p))
+    ...          for p in container.x.getPrincipals()]
+    >>> privs.sort()
+    >>> privs
+    [('p1', 4), ('p2', 7), ('p3', 7)]
+
+There's a subscriber for object-added events on sharables that
+looks up and uses the adapter.
+
+    >>> from zope import component
+    >>> component.provideAdapter(InitialSharing)
+
+    >>> for p in container.x.getPrincipals():
+    ...     container.x.setBinaryPrivileges(p, 0)
+    >>> list(container.x.getPrincipals())
+    []
+
+    >>> from zc.sharing.sharing import initialSharing
+    >>> initialSharing(container.x, event)
+
+    >>> privs = [(p, container.x.getBinaryPrivileges(p))
+    ...          for p in container.x.getPrincipals()]
+    >>> privs.sort()
+    >>> privs
+    [('p1', 4), ('p2', 7), ('p3', 7)]
+
+To implement a different initial sharing policy, simply register a
+different adapter.
+
+
+Sharing Adapter
+---------------
+
+The IBaseSharing interface is the core sharing functionality needed.  Relying
+on it, however, requires knowledge of the binary interface and sometimes some
+binary arithmetic that is not at the tip of a Python programmer's mind.  The
+ISharing interface extends IBaseSharing and provides a number of conveniences.
+
+The most convenient methods are those that use the titles of the privileges.
+Above, we have already defined five privileges: "Share", at bit position 0;
+"Work", at 1; "Play", at 2; "Read", at 3; and "Write", at 4.
+
+    >>> content = MyContent()
+    >>> basesharing = zc.sharing.sharing.BaseSharing(content)
+    >>> sharing = zc.sharing.sharing.Sharing(basesharing)
+    >>> verifyObject(interfaces.ISharing, sharing)
+    True
+    >>> sharing.getPrivileges('bob')
+    []
+    >>> sharing.setPrivilege('bob', 'Read', True)
+    >>> sharing.getPrivileges('bob')
+    ['Read']
+    >>> sharing.getPrivilege('bob', 'Read')
+    True
+    >>> sharing.getPrivilege('bob', 'Write')
+    False
+    >>> sharing.addPrivileges('bob', ('Write', 'Work'))
+    >>> sharing.getPrivileges('bob') # in bit position order
+    ['Work', 'Read', 'Write']
+    >>> sharing.removePrivileges('bob', ('Share', 'Write'))
+    >>> sharing.getPrivileges('bob')
+    ['Work', 'Read']
+    >>> sharing.setPrivileges('bob', ('Play',))
+    >>> sharing.getPrivileges('bob')
+    ['Play']
+    >>> sharing.setPrivileges('bob', ())
+    >>> sharing.getPrivileges('bob')
+    []
+
+The bit positions are used with the sharedTo method, the method used by the
+security policy.  They can also be used to modify privileges.  They are called
+'id'.
+
+    >>> sharing.getIdPrivileges('bob')
+    []
+    >>> sharing.setIdPrivilege('bob', 3, True)
+    >>> sharing.getIdPrivileges('bob')
+    [3]
+    >>> sharing.getIdPrivilege('bob', 3)
+    True
+    >>> sharing.getIdPrivilege('bob', 4)
+    False
+    >>> sharing.addIdPrivileges('bob', (4, 1))
+    >>> sharing.getIdPrivileges('bob') # in bit position order
+    [1, 3, 4]
+    >>> sharing.removeIdPrivileges('bob', (0, 4))
+    >>> sharing.getIdPrivileges('bob')
+    [1, 3]
+    >>> sharing.setIdPrivileges('bob', (2,))
+    >>> sharing.getIdPrivileges('bob')
+    [2]
+    >>> sharing.setIdPrivileges('bob', ())
+    >>> sharing.getIdPrivileges('bob')
+    []
+
+ISharing also provides two additional methods to work with the full binary set.
+
+    >>> sharing.getBinaryPrivileges('bob')
+    0
+    >>> sharing.setBinaryPrivileges('bob', 8)
+    >>> sharing.addBinaryPrivileges('bob', 18)
+    >>> sharing.getBinaryPrivileges('bob')
+    26
+    >>> sharing.removeBinaryPrivileges('bob', 17)
+    >>> sharing.getBinaryPrivileges('bob')
+    10
+
+Module Functions
+================
+
+Privileges
+----------
+
+Privileges are simplified permissions.  Developers define
+permissions. Site managers define privileges and collect numerous
+permissions into each privilege.
+
+Before a privilege can be used, it must be defined.
+
+  >>> zc.sharing.sharing.clearPrivileges() # clean from previous examples
+  >>> zc.sharing.sharing.getPrivileges()
+  []
+  >>> zc.sharing.sharing.definePrivilege(0, "Read", "Read content")
+  >>> zc.sharing.sharing.definePrivilege(2, "Write", "Write content")
+  >>> zc.sharing.sharing.definePrivilege(
+  ...     4, "Share", "Share content (grant privileges)")
+  >>> import pprint
+  >>> pprint.pprint(zc.sharing.sharing.getPrivileges())
+  [{'info': None, 'description': 'Read content', 'id': 0, 'title': 'Read'},
+   {'info': None, 'description': 'Write content', 'id': 2, 'title': 'Write'},
+   {'description': 'Share content (grant privileges)',
+    'id': 4,
+    'info': None,
+    'title': 'Share'}]
+
+The `definePrivilege` method takes a privilege ID, a title, and a
+description [#i18n]_. The privilege ID should be a small positive
+integer, as it is actually a bit identifier [#bit]_. User privileges
+are stored as bits in a sharing value.
+
+`clearPrivileges` has already been demonstrated: it removes all privilege 
+definitions.  `getPrivileges` has also already been introduced by example: it
+returns a sequence of information dicts, one for each privilege.  The `id`
+is the bit position, and the `title` is the value used for all of the most
+convenient ISharing methods, as illustrated above.
+
+`getPrivilege` returns the information for a given bit positition (`id`):
+
+  >>> pprint.pprint(zc.sharing.sharing.getPrivilege(2))
+  {'info': None, 'description': 'Write content', 'id': 2, 'title': 'Write'}
+
+`getIdByTitle` returns the bit position of a privilege by the title.
+
+  >>> [zc.sharing.sharing.getIdByTitle(t) for t
+  ...  in ('Read', 'Write', 'Share')]
+  [0, 2, 4]
+
+`removePrivilege` removes the registration of a single privilege, using its id.
+
+  >>> zc.sharing.sharing.removePrivilege(2)
+
+  >>> pprint.pprint(zc.sharing.sharing.getPrivileges())
+  [{'info': None, 'description': 'Read content', 'id': 0, 'title': 'Read'},
+   {'description': 'Share content (grant privileges)',
+    'id': 4,
+    'info': None,
+    'title': 'Share'}]
+
+
+Other Functions
+---------------
+
+The idsFromSetting and settingFromIds are utilities to convert values from
+sequences of "on" bits--privilege ids--to an integer, and back.
+
+    >>> zc.sharing.sharing.settingFromIds((0, 2, 4, 8)) # 1 + 4 + 16 + 256
+    277
+    >>> zc.sharing.sharing.idsFromSetting(277)
+    [0, 2, 4, 8]
+
+settingFromTitles converts titles to an int, and titlesFromSetting does the
+reverse.
+
+    >>> zc.sharing.sharing.settingFromTitles(('Read', 'Share'))
+    17
+    >>> tuple(zc.sharing.sharing.titlesFromSetting(17))
+    ('Read', 'Share')
+
+
+.. [#i18n] The title and descriptions should be message ids, as they
+           may need to be translated.
+
+.. [#bit] Zope Corporation reserves even bits for it's own use.  Odd
+          bits are used for customer-specific privileges.  (VAR's
+          might then decide to subdivide the odd bits.) XXX Is this still 
+          the plan?


Property changes on: zc.sharing/trunk/src/zc/sharing/sharing.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/tests.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/tests.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/tests.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,131 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+import unittest
+import zope.event
+from zope.app.tests import placelesssetup
+from zope.configuration import xmlconfig
+from zc.sharing import policy
+import zc.sharing
+import zc.sharing.sharing
+import zope.app.security
+from zope.app.tests import ztapi
+import zope.app.annotation.interfaces
+import zope.app.annotation.attribute
+from zope.testing import module
+import zope.security.management
+
+def zcml(s):
+    context = xmlconfig.file('meta.zcml', package=zope.app.security)
+    context = xmlconfig.file('meta.zcml', context=context,
+                             package=zc.sharing)
+    xmlconfig.string(s, context)
+
+def zcmlSetUp(test):
+    placelesssetup.setUp()
+    module.setUp(test, 'zc.sharing.zcml_text')
+    test.globs['__old'] = policy.systemAdministrators
+
+def zcmlTearDown(test):
+    placelesssetup.tearDown()
+    zc.sharing.sharing.clearPrivileges()
+    module.tearDown(test, 'zc.sharing.zcml_text')
+    policy.systemAdministrators = test.globs['__old']
+
+def setUpSharing(test):
+    placelesssetup.setUp()
+    module.setUp(test, 'zc.sharing.SHARING')
+    ztapi.provideAdapter(
+        [zope.app.annotation.interfaces.IAttributeAnnotatable],
+        zope.app.annotation.interfaces.IAnnotations,
+        zope.app.annotation.attribute.AttributeAnnotations,
+        )
+    events = test.globs['events'] = []
+    zope.event.subscribers.append(events.append)
+    zope.security.management.endInteraction()
+
+def tearDownSharing(test):
+    placelesssetup.tearDown()
+    module.tearDown(test, 'zc.sharing.SHARING')
+    removed = zope.event.subscribers.pop()
+    assert test.globs['events'] is removed.__self__
+    del test.globs['events'] # just to be sure
+    zc.sharing.sharing.clearPrivileges()
+
+def tearDownIndex(test):
+    placelesssetup.tearDown()
+    zc.sharing.sharing.clearPrivileges()
+
+def make_sure_sharing_uses_instance():
+    """
+    >>> import zope.interface
+    >>> from zc.sharing import interfaces
+
+    >>> class MyContent:
+    ...     zope.interface.implements(interfaces.ISharable)
+
+    >>> import zc.sharing.sharing
+    >>> content = MyContent()
+    >>> basesharing = zc.sharing.sharing.BaseSharing(content)
+    >>> sharing = zc.sharing.sharing.Sharing(basesharing)
+    
+    >>> sharing.setBinaryPrivileges('bob', 21)
+
+    >>> from zope.app.annotation.interfaces import IAnnotations
+    >>> annotations = IAnnotations(content)
+    >>> annotations[zc.sharing.sharing.key].__class__
+    <class 'zc.sharing.sharing.SharingData'>
+    
+    """
+
+def setUpPolicy(test):
+    placelesssetup.setUp()
+    test.globs['__old'] = policy.systemAdministrators
+
+def tearDownPolicy(test):
+    placelesssetup.tearDown()
+    policy.systemAdministrators = test.globs['__old']
+    zc.sharing.sharing.clearPrivileges()
+
+def test_suite():
+    from zope.testing import doctest
+    return unittest.TestSuite((
+        doctest.DocFileSuite(
+            'policy.txt',
+            setUp=setUpPolicy, tearDown=tearDownPolicy),
+        doctest.DocFileSuite(
+            'zcml.txt', globs={'zcml': zcml},
+            setUp=zcmlSetUp, tearDown=zcmlTearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE,
+            ),
+        doctest.DocFileSuite(
+            'sharing.txt',
+            setUp=setUpSharing, tearDown=tearDownSharing,
+            ),
+        doctest.DocTestSuite(
+            setUp=setUpSharing, tearDown=tearDownSharing,
+            ),
+        doctest.DocFileSuite(
+            'index.txt',
+            setUp=placelesssetup.setUp, tearDown=tearDownIndex,
+            ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: zc.sharing/trunk/src/zc/sharing/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/utils.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/utils.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/utils.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""Sharing utility functions
+
+$Id$
+"""
+from zope.app.location.interfaces import ISublocations
+
+from zc.sharing.interfaces import ISharing
+from zc.sharing.sharing import sharingMask
+
+def shareAll(ob, principal_id):
+    sharing = ISharing(ob)
+    mask = sharingMask(ob)
+    sharing.setBinaryPrivileges(principal_id, mask)
+
+def applyToSubobjects(settings, ob, seen=None, clobber=False):
+    """Apply the given sharing settings to all subobjects of `ob`
+    
+    clobber - a boolean, if true no sharing bits will be left enabled for an
+        object unless they exist in that objects sharing mask
+    """
+    if seen is None:
+        seen = {}
+
+    obid = id(ob)
+    if obid in seen:
+        return
+    seen[obid] = ob
+
+    sharing = ISharing(ob, None)
+    if sharing is not None:
+        mask = sharingMask(ob)
+        for principal_id, setting in settings:
+            if not clobber:
+                value = sharing.getBinaryPrivileges(principal_id)
+                # unmasked_value represents all of the bits of value that fall
+                # outside of the mask
+                unmasked_value = (value & mask) ^ value
+                setting |= unmasked_value
+
+            sharing.setBinaryPrivileges(principal_id, setting)
+
+    subs = ISublocations(ob, None)
+    if subs is not None:
+        for sub in subs.sublocations():
+            applyToSubobjects(settings, sub, seen, clobber)


Property changes on: zc.sharing/trunk/src/zc/sharing/utils.py
___________________________________________________________________
Name: svn:executable
   + 
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/zcml.py
===================================================================
--- zc.sharing/trunk/src/zc/sharing/zcml.py	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/zcml.py	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,134 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""ZCML directives for defining privileges.
+
+$Id$
+"""
+
+from types import ClassType
+
+from zope import component, interface, schema
+import zope.configuration.fields
+import zope.app.security.fields
+
+from zc.sharing import policy, sharing, interfaces
+
+class IdefinePrivilege(interface.Interface):
+
+    bit = schema.Int(
+        title=u"Privilege bit",
+        description=(u"The privilege ID should be a small positive "
+                     u"integer, as it is actually a bit "
+                     u"identifier. User privileges are stored as bits "
+                     u"in a sharing value."),
+        )
+
+    title = zope.configuration.fields.MessageID(
+        title=u"Title",
+        description=u"A user-friendly name for the privilege",
+        )
+
+    description = zope.configuration.fields.MessageID(
+        title=u"Description",
+        description=(u"A description of the privilege, including "
+                     u"the capabilities the privilege provides"),
+        required=False,
+        )
+
+def definePrivilege(_context, bit, title, description=''):
+    _context.action(
+        discriminator=('zc.intranet:privilege', bit),
+        callable=sharing.definePrivilege,
+        args=(bit, title, description, _context.info),
+        )
+
+class IpermissionPrivilege(interface.Interface):
+
+    permission = zope.app.security.fields.Permission(
+        title=u"Permission",
+        description=u"Permission for which a privilege is being defined",
+        )
+
+    privilege = schema.Int(
+        title=u"Privilege",
+        description=u"The privilege ID that provides the permission",
+        )
+        
+def checkPrivilege(privilege):
+    if sharing.getPrivilege(privilege, -1) < 0:
+        raise ValueError("Undefined privilege", privilege)
+
+def permissionPrivilege(_context, permission, privilege):
+    _context.action(
+        discriminator=None,
+        callable=checkPrivilege,
+        args=(privilege, )
+        )
+    _context.action(
+        discriminator=('zc:permissionPrivilege', permission),
+        callable=policy.permissionPrivilege,
+        args=(permission, privilege),
+        )
+
+class Iprivileges(interface.Interface):
+
+    for_ = zope.configuration.fields.GlobalObject(
+        title=u"Type the privileges are used for"
+        )
+
+    titles = zope.configuration.fields.Tokens(
+        title=u"Privileges",
+        description=u"List of privilege titles",
+        value_type=schema.TextLine(),
+        )
+
+for_types = type(None), type, type(interface.Interface), ClassType
+def privileges(_context, for_, titles):
+
+    if not isinstance(for_, for_types):
+        raise TypeError("Invalid type for for", type(for_))
+    _context.action(
+        discriminator=('zc:privileges', for_),
+        callable=policy.sharingPrivileges,
+        args=(for_, titles),
+        )
+
+def subobjectPrivileges(_context, for_, titles):
+
+    if not isinstance(for_, for_types):
+        raise TypeError("Invalid type for for", type(for_))
+    _context.action(
+        discriminator=('zc:subobjectPrivileges', for_),
+        callable=policy.subobjectSharingPrivileges,
+        args=(for_, titles),
+        )
+
+class IsystemAdministrators(interface.Interface):
+
+    principals = zope.configuration.fields.Tokens(
+        title=u"Principals",
+        description=u"System administrator principal ids",
+        value_type=schema.TextLine(),
+        )
+
+def setSystemAdministrators(principals):
+    policy.systemAdministrators = principals
+    
+def systemAdministrators(_context, principals):
+    _context.action(
+        discriminator='zc:sysAdmins',
+        callable=setSystemAdministrators,
+        args=(tuple(principals), )
+        )


Property changes on: zc.sharing/trunk/src/zc/sharing/zcml.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zc.sharing/trunk/src/zc/sharing/zcml.txt
===================================================================
--- zc.sharing/trunk/src/zc/sharing/zcml.txt	2006-03-01 18:36:57 UTC (rev 65677)
+++ zc.sharing/trunk/src/zc/sharing/zcml.txt	2006-03-01 19:14:48 UTC (rev 65678)
@@ -0,0 +1,243 @@
+Sharing Configuration directives
+================================
+
+This package provides several ZCML configuration directives for setting up the
+sharing security model.
+
+Defining privileges
+-------------------
+
+To define a privilege, use the privilege directive:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...
+    ...      <zc:privilege bit="0" title="Read" />
+    ...      <zc:privilege bit="2" title="Write"
+    ...                     description="Modify content" />
+    ...      <zc:privilege bit="4" title="Share" />
+    ...    </configure>
+    ... """)
+
+Now, having defined these, we can get privilege definitions:
+
+    >>> import zc.sharing.sharing
+    >>> from zope.testing.doctestunit import pprint
+    >>> pprint(zc.sharing.sharing.getPrivilege(0))
+    {'description': '',
+     'id': 0,
+     'info': File "<string>", line 8.5-8.42,
+     'title': u'Read'}
+
+    >>> pprint(zc.sharing.sharing.getPrivilege(2))
+    {'description': u'Modify content',
+     'id': 2,
+     'info': File "<string>", line 9.5-10.51,
+     'title': u'Write'}
+
+    >>> zc.sharing.sharing.getPrivilege(2)['title'].domain
+    'test'
+
+
+It's an error to try to define the same privilege more than once:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...
+    ...      <zc:privilege bit="6" title="Share" />
+    ...      <zc:privilege bit="6" title="Write"
+    ...                     description="Modify content" />
+    ...    </configure>
+    ... """)
+    Traceback (most recent call last):
+      ...
+    ConfigurationConflictError: Conflicting configuration actions
+      For: ('zc.intranet:privilege', 6)
+        File "<string>", line 8.5-8.43
+          Could not read source.
+        File "<string>", line 9.5-10.51
+          Could not read source.
+
+Associating privileges with permissions
+---------------------------------------
+
+Having defined a privilege, we can associate one or more permissions
+with it.  Note that we have to define the permissions before we can associate
+them with privileges:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...
+    ...      <zc:permissionPrivilege permission="foo.p1" privilege="0" />
+    ...    </configure>
+    ... """)
+    Traceback (most recent call last):
+      ...
+    ConfigurationExecutionError: exceptions.ValueError:
+      ('Undefined permission id', 'foo.p1')
+      in:
+      File "<string>", line 8.5-8.65
+      Could not read source.
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...      <permission id="foo.p1" title="Permission 1" />
+    ...      <zc:permissionPrivilege permission="foo.p1" privilege="0" />
+    ...    </configure>
+    ... """)
+
+    >>> from zc.sharing import policy
+    >>> policy.getPermissionPrivilege("foo.p1")
+    0
+
+Of course, if you don't define a privilege before you use it, you'll
+get an error:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...      <permission id="foo.p2" title="Permission 1" />
+    ...      <zc:permissionPrivilege permission="foo.p2" privilege="10" />
+    ...    </configure>
+    ... """)
+    Traceback (most recent call last):
+      ...
+    ConfigurationExecutionError: exceptions.ValueError:
+      ('Undefined privilege', 10)
+      in:
+      File "<string>", line 8.5-8.66
+      Could not read source.
+
+Associating privileges with content
+-----------------------------------
+
+Different content types can have different privileges.  We use the
+privileges directive to define privileges for a type.  We can define
+default privileges:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...      <zc:privileges for="*" titles="Read Write Share" />
+    ...    </configure>
+    ... """)
+
+
+Now, we'll define a new content type:
+
+    >>> from zope import interface
+    >>> class IMyContent(interface.Interface):
+    ...     "sample type"
+    >>> class MyContent:
+    ...     interface.implements(IMyContent)
+
+We can find out what privileges it has by adapting it to
+ISharingPrivileges:
+
+    >>> from zc.sharing import interfaces
+    >>> interfaces.ISharingPrivileges(MyContent()).privileges
+    (0, 2, 4)
+
+We may want out content type to have different privileges.  If so, we
+simply use a specific interface in the configuration directive:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...      <zc:privileges for="zc.sharing.zcml_text.IMyContent"
+    ...                      titles="Read Share" />
+    ...    </configure>
+    ... """)
+
+    >>> interfaces.ISharingPrivileges(MyContent()).privileges
+    (0, 4)
+
+For containers, we can privileges that may apply to subobjects.  On
+container sharing tabs, we need to include privileges that apply to
+subobjects as well as privileges that apply to the container.  This is
+necessary so that we can create settings on a container and duplicate
+them on subobjects, whether explicitly in the sharing tab, or when
+objects are added.  To specify subobject privileges, use the
+subobjectPrivileges directive:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        i18n_domain="test"
+    ...        >
+    ...      <zc:subobjectPrivileges
+    ...                      for="zc.sharing.zcml_text.IMyContent"
+    ...                      titles="Write Read" />
+    ...    </configure>
+    ... """)
+
+We can find out what these settings are by adapting an object
+to ISubobjectSharingPrivileges:
+
+    >>> interfaces.ISubobjectSharingPrivileges(MyContent()).subobjectPrivileges
+    (2, 0)
+
+Defining system adminstrators
+-----------------------------
+
+System adminstrators are defined using the systemAdministrators
+directive:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        >
+    ...      <zc:systemAdministrators principals="sally bob" />
+    ...    </configure>
+    ... """)
+
+    >>> policy.systemAdministrators
+    (u'sally', u'bob')
+
+It is an error to use the directive more than once:
+
+    >>> zcml("""
+    ...    <configure
+    ...        xmlns="http://namespaces.zope.org/zope"
+    ...        xmlns:zc="http://namespaces.zope.com/zc"
+    ...        >
+    ...      <zc:systemAdministrators principals="sally bob" />
+    ...      <zc:systemAdministrators principals="ted mary sam" />
+    ...    </configure>
+    ... """)
+    Traceback (most recent call last):
+      ...
+    ConfigurationConflictError: Conflicting configuration actions
+      For: zc:sysAdmins
+        File "<string>", line 6.5-6.55
+          Could not read source.
+        File "<string>", line 7.5-7.58
+          Could not read source.
+


Property changes on: zc.sharing/trunk/src/zc/sharing/zcml.txt
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Zope-CVS mailing list