
jim at zope
Mar 1, 2006, 11:14 AM
Post #1 of 1
(738 views)
Permalink
|
|
SVN: zc.sharing/trunk/ import code
|
|
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 _______________________________________________ Zope-CVS maillist - Zope-CVS [at] zope http://mail.zope.org/mailman/listinfo/zope-cvs Zope CVS instructions: http://dev.zope.org/CVS
|