From f6bb099b82d2ab1fedbf0a674205a6d5f7ae25a6 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Wed, 8 Feb 2023 18:05:06 +0100 Subject: [PATCH] chore: isort + black --- borg/__init__.py | 2 +- borg/localrole/__init__.py | 2 +- borg/localrole/bbb/interfaces.py | 4 +- borg/localrole/config.py | 2 +- borg/localrole/default_adapter.py | 2 +- borg/localrole/factory_adapter.py | 2 +- borg/localrole/interfaces.py | 13 +- borg/localrole/setuphandlers.py | 2 +- borg/localrole/tests.py | 25 +- borg/localrole/utils.py | 17 +- borg/localrole/workspace.py | 658 +++++++++++++++--------------- setup.py | 94 ++--- 12 files changed, 406 insertions(+), 417 deletions(-) diff --git a/borg/__init__.py b/borg/__init__.py index de40ea7..5284146 100644 --- a/borg/__init__.py +++ b/borg/__init__.py @@ -1 +1 @@ -__import__('pkg_resources').declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) diff --git a/borg/localrole/__init__.py b/borg/localrole/__init__.py index fb97253..66dcaa4 100644 --- a/borg/localrole/__init__.py +++ b/borg/localrole/__init__.py @@ -16,5 +16,5 @@ def initialize(context): workspace.manage_addWorkspaceLocalRoleManagerForm, workspace.manage_addWorkspaceLocalRoleManager, ), - visibility=None + visibility=None, ) diff --git a/borg/localrole/bbb/interfaces.py b/borg/localrole/bbb/interfaces.py index 95bd9b9..86747ce 100644 --- a/borg/localrole/bbb/interfaces.py +++ b/borg/localrole/bbb/interfaces.py @@ -4,6 +4,7 @@ # BBB: These interfaces will be removed in a later version of borg.locarole. # You should use the interfaces in borg.localrole.interfaces instead! + class IWorkspace(Interface): """A workspace in which custom local roles are needed @@ -13,8 +14,7 @@ class IWorkspace(Interface): """ def getLocalRolesForPrincipal(principal): - """Return a sequence of all local roles for a principal. - """ + """Return a sequence of all local roles for a principal.""" def getLocalRoles(): """Return a dictonary mapping principals to their roles within diff --git a/borg/localrole/config.py b/borg/localrole/config.py index 1bb272e..40d52ac 100644 --- a/borg/localrole/config.py +++ b/borg/localrole/config.py @@ -1,3 +1,3 @@ # Configuration constants -LOCALROLE_PLUGIN_NAME = 'borg_localroles' +LOCALROLE_PLUGIN_NAME = "borg_localroles" diff --git a/borg/localrole/default_adapter.py b/borg/localrole/default_adapter.py index 04b9874..9edda13 100644 --- a/borg/localrole/default_adapter.py +++ b/borg/localrole/default_adapter.py @@ -61,7 +61,7 @@ def __init__(self, context): @property def _rolemap(self): - rolemap = getattr(self.context, '__ac_local_roles__', {}) + rolemap = getattr(self.context, "__ac_local_roles__", {}) # None is the default value from AccessControl.Role.RoleMananger if rolemap is None: return {} diff --git a/borg/localrole/factory_adapter.py b/borg/localrole/factory_adapter.py index ddb0608..18e773c 100644 --- a/borg/localrole/factory_adapter.py +++ b/borg/localrole/factory_adapter.py @@ -97,7 +97,7 @@ def __init__(self, obj): self.folder = obj def getRoles(self, principal_id): - uf = aq_inner(getToolByName(self.folder, 'acl_users')) + uf = aq_inner(getToolByName(self.folder, "acl_users")) user = aq_inner(uf.getUserById(principal_id, default=None)) # use the folder we are creating in as role generating context source = aq_parent(aq_parent(self.folder)) diff --git a/borg/localrole/interfaces.py b/borg/localrole/interfaces.py index 42b729f..ca4dfbb 100644 --- a/borg/localrole/interfaces.py +++ b/borg/localrole/interfaces.py @@ -3,13 +3,13 @@ deprecated( - 'Please use borg.localrole.interfaces.ILocalRoleProvider instead', - IWorkspace='borg.localrole.bbb.interfaces:IWorkspace' + "Please use borg.localrole.interfaces.ILocalRoleProvider instead", + IWorkspace="borg.localrole.bbb.interfaces:IWorkspace", ) deprecated( - 'Please use borg.localrole.interfaces.ILocalRoleProvider instead', - IGroupAwareWorkspace='borg.localrole.bbb.interfaces:IGroupAwareWorkspace' + "Please use borg.localrole.interfaces.ILocalRoleProvider instead", + IGroupAwareWorkspace="borg.localrole.bbb.interfaces:IGroupAwareWorkspace", ) @@ -17,12 +17,11 @@ class ILocalRoleProvider(Interface): """An interface which allows querying the local roles on an object""" def getRoles(principal_id): - """Returns an iterable of roles granted to the specified user object - """ + """Returns an iterable of roles granted to the specified user object""" def getAllRoles(): """Returns an iterable consisting of tuples of the form: - (principal_id, sequence_of_roles) + (principal_id, sequence_of_roles) """ diff --git a/borg/localrole/setuphandlers.py b/borg/localrole/setuphandlers.py index bd29f33..f30b14d 100644 --- a/borg/localrole/setuphandlers.py +++ b/borg/localrole/setuphandlers.py @@ -3,7 +3,7 @@ def importVarious(context): - if context.readDataFile('borg.localrole_various.txt') is None: + if context.readDataFile("borg.localrole_various.txt") is None: return portal = context.getSite() diff --git a/borg/localrole/tests.py b/borg/localrole/tests.py index ad91721..1dab7bb 100644 --- a/borg/localrole/tests.py +++ b/borg/localrole/tests.py @@ -14,18 +14,17 @@ @implementer(borg.localrole.interfaces.ILocalRoleProvider) class SimpleLocalRoleProvider: - def __init__(self, context): self.context = context def getRoles(self, user): """Grant everyone the 'Foo' role""" - return ('Foo', ) + return ("Foo",) def getAllRoles(self): """In the real world we would enumerate all users and grant the 'Foo' role to each, but we won't""" - yield ('bogus_user', ('Foo', )) + yield ("bogus_user", ("Foo",)) class DummyUser: @@ -50,10 +49,7 @@ class Py23DocChecker(doctest.OutputChecker): def check_output(self, want, got, optionflags): if six.PY2: got = re.sub(r"set\(\[(.*?)\]\)", "{\\1}", got) - want = re.sub( - 'plone.memoize.volatile.DontCache', - 'DontCache', want - ) + want = re.sub("plone.memoize.volatile.DontCache", "DontCache", want) return doctest.OutputChecker.check_output(self, want, got, optionflags) @@ -61,25 +57,26 @@ def test_suite(): suite = [ layered( doctest.DocFileSuite( - 'README.txt', - package='borg.localrole', - optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE) + "README.txt", + package="borg.localrole", + optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), ), - layer=PLONE_INTEGRATION_TESTING), + layer=PLONE_INTEGRATION_TESTING, + ), layered( doctest.DocTestSuite( borg.localrole.workspace, optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), checker=Py23DocChecker(), ), - layer=zca.UNIT_TESTING + layer=zca.UNIT_TESTING, ), layered( doctest.DocTestSuite( factory_adapter, - optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE) + optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), ), - layer=zca.UNIT_TESTING + layer=zca.UNIT_TESTING, ), doctest.DocTestSuite(default_adapter), ] diff --git a/borg/localrole/utils.py b/borg/localrole/utils.py index ed5bed4..abf4d06 100644 --- a/borg/localrole/utils.py +++ b/borg/localrole/utils.py @@ -1,18 +1,17 @@ from Acquisition import aq_base from borg.localrole.config import LOCALROLE_PLUGIN_NAME from borg.localrole.workspace import manage_addWorkspaceLocalRoleManager +from io import StringIO from Products.CMFCore.utils import getToolByName from Products.PlonePAS.plugins.local_role import LocalRolesManager from Products.PlonePAS.setuphandlers import activatePluginInterfaces -from io import StringIO def setup_localrole_plugin(portal): - """Install and prioritize the local-role PAS plug-in - """ + """Install and prioritize the local-role PAS plug-in""" out = StringIO() - uf = getToolByName(portal, 'acl_users') + uf = getToolByName(portal, "acl_users") existing = uf.objectIds() @@ -28,13 +27,13 @@ def setup_localrole_plugin(portal): def replace_local_role_manager(portal): """Installs the borg local role manager in place of the standard one from PlonePAS""" - uf = getToolByName(portal, 'acl_users', None) + uf = getToolByName(portal, "acl_users", None) # Make sure we have a PAS user folder - if uf is not None and hasattr(aq_base(uf), 'plugins'): + if uf is not None and hasattr(aq_base(uf), "plugins"): # Remove the original plugin if it's there - if 'local_roles' in uf.objectIds(): - orig_lr = getattr(uf, 'local_roles') + if "local_roles" in uf.objectIds(): + orig_lr = getattr(uf, "local_roles") if isinstance(orig_lr, LocalRolesManager): - uf.plugins.removePluginById('local_roles') + uf.plugins.removePluginById("local_roles") # Install the borg.localrole plugin if it's not already there setup_localrole_plugin(portal) diff --git a/borg/localrole/workspace.py b/borg/localrole/workspace.py index 1f8c82d..fb1ad2f 100644 --- a/borg/localrole/workspace.py +++ b/borg/localrole/workspace.py @@ -1,8 +1,9 @@ from AccessControl import ClassSecurityInfo +from AccessControl.class_init import InitializeClass from Acquisition import aq_get from Acquisition import aq_inner from Acquisition import aq_parent -from AccessControl.class_init import InitializeClass + # BBB interfaces, to be removed from borg.localrole.bbb.interfaces import IGroupAwareWorkspace from borg.localrole.bbb.interfaces import IWorkspace @@ -20,16 +21,11 @@ manage_addWorkspaceLocalRoleManagerForm = PageTemplateFile( "zmi/WorkspaceLocalRoleManagerForm.pt", globals(), - __name__="manage_addWorkspaceRoleManagerForm" + __name__="manage_addWorkspaceRoleManagerForm", ) -def manage_addWorkspaceLocalRoleManager( - dispatcher, - id, - title=None, - REQUEST=None -): +def manage_addWorkspaceLocalRoleManager(dispatcher, id, title=None, REQUEST=None): """Add a WorkspaceLocalRoleManager to a Pluggable Authentication Services. """ @@ -38,8 +34,8 @@ def manage_addWorkspaceLocalRoleManager( if REQUEST is not None: REQUEST.RESPONSE.redirect( - '{}/manage_workspace?' - 'manage_tabs_message=WorkspaceLocalRoleManager+added.'.format( + "{}/manage_workspace?" + "manage_tabs_message=WorkspaceLocalRoleManager+added.".format( dispatcher.absolute_url() ) ) @@ -47,94 +43,94 @@ def manage_addWorkspaceLocalRoleManager( # memoize support for `checkLocalRolesAllowed` def clra_cache_key(method, self, user, obj, object_roles): - """ The cache key needs to include all arguments when caching allowed - local roles, but the key function also needs to decide whether - `volatile.cache` can cache or not by checking if it's possible to - get a request instance from the object. - - To test we'll nee an adaptable object, a user and the method which - results' we'd like to cache: - - >>> from zope.interface import implementer, Interface - >>> @implementer(Interface) - ... class DummyObject(object): - ... pass - >>> obj = DummyObject() - - >>> from borg.localrole.tests import DummyUser - >>> john = DummyUser('john') - - >>> rm = WorkspaceLocalRoleManager('rm', 'A Role Manager') - >>> fun = rm.__class__.checkLocalRolesAllowed - - The dummy object doesn't have an acquired request, so no caching - can be done: - - >>> clra_cache_key(fun, 'me', john, obj, ['foo', 'bar']) - Traceback (most recent call last): - ... - plone.memoize.volatile.DontCache - - So let's add one and try again. Before we also need to mark it as - being annotatable, which normally happens elsewhere: - - >>> from ZPublisher.HTTPRequest import HTTPRequest - >>> request = HTTPRequest('', dict(HTTP_HOST='nohost:8080'), {}) - - >>> try: - ... from Zope2.App.zcml import load_config - ... except ImportError: - ... from Products.Five.zcml import load_config - >>> import zope.component - >>> import zope.annotation - >>> load_config('meta.zcml', zope.component) - >>> load_config('configure.zcml', zope.annotation) - >>> from zope.interface import classImplements - >>> from zope.annotation.interfaces import IAttributeAnnotatable - >>> classImplements(HTTPRequest, IAttributeAnnotatable) - - >>> obj.REQUEST = request - >>> clra_cache_key(fun, 'hmm', john, obj, ['foo', 'bar']) - ('john', ..., ('foo', 'bar')) - - If the objects happens to have a `getPhysicalPath` method, that should - be used instead of the hash: - - >>> class DummyObjectWithPath(DummyObject): - ... def getPhysicalPath(self): - ... return '42!' - >>> obj = DummyObjectWithPath() - >>> obj.REQUEST = request - >>> clra_cache_key(fun, 'hmm', john, obj, ['foo', 'bar']) - ('john', '42!', ('foo', 'bar')) - - Now let's check if the results of a call to `checkLocalRolesAllowed` - is indeed cached, i.e. is the request was annotated correctly. First - try to log the method invocation, though. As monkey patching in - something between the original method and the already applied cache - decorator is tricky, we abuse `_get_userfolder`, which is called - first thing in `checkLocalRolesAllowed`: - - >>> original = rm._get_userfolder - >>> def logger(self, *args, **kw): - ... print('checkLocalRolesAllowed called...') - ... return original(self, *args, **kw) - >>> rm._get_userfolder = logger - - >>> print(rm.checkLocalRolesAllowed(john, obj, ['foo', 'bar'])) - checkLocalRolesAllowed called... - None - >>> [i for i in IAnnotations(request)] - ["borg.localrole.workspace.checkLocalRolesAllowed:('john', '42!', ('foo', 'bar'))"] - - Calling the method a second time should directly return the cached - value, i.e. the logger shouldn't print anything: - - >>> print(rm.checkLocalRolesAllowed(john, obj, ['foo', 'bar'])) - None + """The cache key needs to include all arguments when caching allowed + local roles, but the key function also needs to decide whether + `volatile.cache` can cache or not by checking if it's possible to + get a request instance from the object. + + To test we'll nee an adaptable object, a user and the method which + results' we'd like to cache: + + >>> from zope.interface import implementer, Interface + >>> @implementer(Interface) + ... class DummyObject(object): + ... pass + >>> obj = DummyObject() + + >>> from borg.localrole.tests import DummyUser + >>> john = DummyUser('john') + + >>> rm = WorkspaceLocalRoleManager('rm', 'A Role Manager') + >>> fun = rm.__class__.checkLocalRolesAllowed + + The dummy object doesn't have an acquired request, so no caching + can be done: + + >>> clra_cache_key(fun, 'me', john, obj, ['foo', 'bar']) + Traceback (most recent call last): + ... + plone.memoize.volatile.DontCache + + So let's add one and try again. Before we also need to mark it as + being annotatable, which normally happens elsewhere: + + >>> from ZPublisher.HTTPRequest import HTTPRequest + >>> request = HTTPRequest('', dict(HTTP_HOST='nohost:8080'), {}) + + >>> try: + ... from Zope2.App.zcml import load_config + ... except ImportError: + ... from Products.Five.zcml import load_config + >>> import zope.component + >>> import zope.annotation + >>> load_config('meta.zcml', zope.component) + >>> load_config('configure.zcml', zope.annotation) + >>> from zope.interface import classImplements + >>> from zope.annotation.interfaces import IAttributeAnnotatable + >>> classImplements(HTTPRequest, IAttributeAnnotatable) + + >>> obj.REQUEST = request + >>> clra_cache_key(fun, 'hmm', john, obj, ['foo', 'bar']) + ('john', ..., ('foo', 'bar')) + + If the objects happens to have a `getPhysicalPath` method, that should + be used instead of the hash: + + >>> class DummyObjectWithPath(DummyObject): + ... def getPhysicalPath(self): + ... return '42!' + >>> obj = DummyObjectWithPath() + >>> obj.REQUEST = request + >>> clra_cache_key(fun, 'hmm', john, obj, ['foo', 'bar']) + ('john', '42!', ('foo', 'bar')) + + Now let's check if the results of a call to `checkLocalRolesAllowed` + is indeed cached, i.e. is the request was annotated correctly. First + try to log the method invocation, though. As monkey patching in + something between the original method and the already applied cache + decorator is tricky, we abuse `_get_userfolder`, which is called + first thing in `checkLocalRolesAllowed`: + + >>> original = rm._get_userfolder + >>> def logger(self, *args, **kw): + ... print('checkLocalRolesAllowed called...') + ... return original(self, *args, **kw) + >>> rm._get_userfolder = logger + + >>> print(rm.checkLocalRolesAllowed(john, obj, ['foo', 'bar'])) + checkLocalRolesAllowed called... + None + >>> [i for i in IAnnotations(request)] + ["borg.localrole.workspace.checkLocalRolesAllowed:('john', '42!', ('foo', 'bar'))"] + + Calling the method a second time should directly return the cached + value, i.e. the logger shouldn't print anything: + + >>> print(rm.checkLocalRolesAllowed(john, obj, ['foo', 'bar'])) + None """ # noqa: E501 - request = aq_get(obj, 'REQUEST', None) + request = aq_get(obj, "REQUEST", None) if IAnnotations(request, None) is None: raise DontCache try: @@ -145,235 +141,236 @@ def clra_cache_key(method, self, user, obj, object_roles): def store_on_request(method, self, user, obj, object_roles): - """ helper for caching local roles on the request """ - return IAnnotations(aq_get(obj, 'REQUEST')) + """helper for caching local roles on the request""" + return IAnnotations(aq_get(obj, "REQUEST")) class WorkspaceLocalRoleManager(BasePlugin): """This is the actual plug-in. It takes care of looking up - ILocalRolesProvider adapters (when available) and granting local roles - appropriately. - - First we need to make and register an adapter to provide some roles:: - - >>> from zope.interface import implementer, Interface - >>> from zope.component import adapter - >>> from borg.localrole.tests import SimpleLocalRoleProvider - >>> from borg.localrole.tests import DummyUser - >>> from zope.component import provideAdapter - >>> provideAdapter(SimpleLocalRoleProvider, adapts=(Interface,)) - - - We need an object to adapt, we require nothing of this object, - except it must be adaptable (e.g. have an interface):: - - >>> @implementer(Interface) - ... class DummyObject(object): - ... pass - >>> ob = DummyObject() - - And we need some users that we'll check the permissions of:: - - >>> user1 = DummyUser('bogus_user') - >>> user2 = DummyUser('bogus_user2') - - Now we're ready to make one of our RoleManagers and try it out. - First we'll verify that our users have the 'Foo' role, then we'll - make sure they can access objects which require that role, but not - others:: - - >>> rm = WorkspaceLocalRoleManager('rm', 'A Role Manager') - >>> rm.getRolesInContext(user1, ob) - ['Foo'] - >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Foo', 'Baz']) - 1 - >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Baz']) is None - True - >>> rm.getAllLocalRolesInContext(ob) - {'bogus_user': {'Foo'}} - - - Multiple Role Providers - ----------------------- - - It is a bit more interesting when we have more than one adapter - registered. We register it with a name so that it supplements, - rather than conflict with or override the existing adapter:: - - >>> class LessSimpleLocalRoleProvider(SimpleLocalRoleProvider): - ... userid = 'bogus_user2' - ... roles = ('Foo', 'Baz') - ... def getRoles(self, userid): - ... '''Grant bogus_user2 the 'Foo' and 'Baz' roles''' - ... if userid == self.userid: - ... return self.roles - ... return () - ... - ... def getAllRoles(self): - ... yield (self.userid, self.roles) - - >>> provideAdapter(LessSimpleLocalRoleProvider, adapts=(Interface,), - ... name='adapter2') - - This should have no effect on our first user:: - - >>> rm.getRolesInContext(user1, ob) - ['Foo'] - >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Foo', 'Baz']) - 1 - >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Baz']) is None - True - >>> expected = {'bogus_user': {'Foo'}, 'bogus_user2': {'Foo', 'Baz'}} - >>> rm.getAllLocalRolesInContext(ob) == expected - True - - But our second user notices the change, note that even though two - of our local role providers grant the role 'Foo', it is not duplicated:: - - >>> set(rm.getRolesInContext(user2, ob)) == {'Foo', 'Baz'} - True - >>> rm.checkLocalRolesAllowed(user2, ob, ['Bar', 'Foo', 'Baz']) - 1 - >>> rm.checkLocalRolesAllowed(user2, ob, ['Bar', 'Baz']) - 1 - >>> rm.checkLocalRolesAllowed(user2, ob, ['Bar']) is None - True - - - Role Acquisition and Blocking - ----------------------------- - - This plugin will acquire role definitions from parent objects, - unless explicitly blocked. To test this, we need some objects - which support acquisition:: - - >>> from Acquisition import Implicit - >>> class DummyImplicit(DummyObject, Implicit): - ... def stupid_method(self): - ... return 1 - >>> root = DummyImplicit() - >>> next = DummyImplicit().__of__(root) - >>> last = DummyImplicit().__of__(next) - >>> other = DummyImplicit().__of__(root) - - So we now have /root/next/last and /root/other, we'll create and - register special adapters for our next and other objects. - - >>> class ISpecial1(Interface): - ... pass - >>> class ISpecial2(Interface): - ... pass - >>> from zope.interface import directlyProvides - >>> directlyProvides(next, ISpecial1) - >>> directlyProvides(other, ISpecial2) - >>> @adapter(ISpecial1) - ... class Adapter1(LessSimpleLocalRoleProvider): - ... - ... userid = 'bogus_user' - ... roles = ('Bar',) - >>> @adapter(ISpecial2) - ... class Adapter2(LessSimpleLocalRoleProvider): - ... - ... userid = 'bogus_user3' - ... roles = ('Foobar',) - >>> user3 = DummyUser('bogus_user3') - - We'll register these to override the existing unnamed adapter: - - >>> provideAdapter(Adapter1) - >>> provideAdapter(Adapter2) - - Now we can show how acquisition of roles works, first we look at the - 'last' item, which should have roles provided by - SimpleLocalRoleProvider, and LessSimpleLocalRoleProvider, as well - as acquired from Adapter1 on 'next': - - >>> set(rm.getRolesInContext(user1, last)) == {'Foo', 'Bar'} - True - - >>> set(rm.getRolesInContext(user2, last)) == {'Foo', 'Baz'} - True - - If we look at the parent, we get the same results, because the - SimpleLocalRoleProvider adapter also applies to the 'root' - object. However, if we enable local role blocking on 'next' we - won't see the roles from the 'root':: - - >>> set(rm.getRolesInContext(user1, next)) == {'Foo', 'Bar'} - True - >>> next.__ac_local_roles_block__ = True - >>> rm.getRolesInContext(user1, next) - ['Bar'] - - The checkLocalRolesAllowed and getAllLocalRolesInContext methods - take acquisition and blocking into account as well:: - - >>> rm.checkLocalRolesAllowed(user1, last, ['Bar']) - 1 - >>> rm.checkLocalRolesAllowed(user1, next, ['Foo', 'Baz']) is None - True - >>> expected = {'bogus_user': {'Foo', 'Bar'}, 'bogus_user2': {'Foo', 'Baz'}} - >>> rm.getAllLocalRolesInContext(last) == expected - True - - It's important to note, that roles are acquired only by - containment. Additional wrapping cannot change the security on an - object. For example if we were to wrap 'last' in the context of - other, which provides a special role for 'user3', we should see no - effect:: - - >>> rm.getRolesInContext(user3, last) - ['Foo'] - >>> set(rm.getRolesInContext(user3, other)) == {'Foobar', 'Foo'} - True - >>> rm.getRolesInContext(user3, last.__of__(other)) - ['Foo'] - - It's also important that methods of objects yield the same local - roles as the objects would - - >>> set(rm.getRolesInContext(user3, other.stupid_method)) == {'Foobar', 'Foo'} - True - - Group Support - ------------- - - This plugin also handles roles granted to user groups, calling up - the adapters to get roles for any groups the user might belong - to:: - - >>> user4 = DummyUser('bogus_user4', ('Group1', 'Group2')) - >>> user4.getGroups() - ('Group1', 'Group2') - >>> rm.getRolesInContext(user4, last) - ['Foo'] - >>> class Adapter3(LessSimpleLocalRoleProvider): - ... userid = 'Group2' - ... roles = ('Foobar',) - - >>> provideAdapter(Adapter3, adapts=(Interface,), name='group_adapter') - >>> set(rm.getRolesInContext(user4, last)) == {'Foobar', 'Foo'} - True - - - Wrong User Folder - ----------------- - - Finally, to ensure full test coverage, we provide a user object - which pretends to be wrapped in such a way that the user folder - does not recognize it. We check that it always gets an empty set - of roles and a special 0 value when checking access:: - - >>> class BadUser(DummyUser): - ... def _check_context(self, obj): - ... return False - >>> bad_user = BadUser('bad_user') - >>> rm.getRolesInContext(bad_user, ob) - [] - >>> rm.checkLocalRolesAllowed(bad_user, ob, ['Bar', 'Foo', 'Baz']) - 0 + ILocalRolesProvider adapters (when available) and granting local roles + appropriately. + + First we need to make and register an adapter to provide some roles:: + + >>> from zope.interface import implementer, Interface + >>> from zope.component import adapter + >>> from borg.localrole.tests import SimpleLocalRoleProvider + >>> from borg.localrole.tests import DummyUser + >>> from zope.component import provideAdapter + >>> provideAdapter(SimpleLocalRoleProvider, adapts=(Interface,)) + + + We need an object to adapt, we require nothing of this object, + except it must be adaptable (e.g. have an interface):: + + >>> @implementer(Interface) + ... class DummyObject(object): + ... pass + >>> ob = DummyObject() + + And we need some users that we'll check the permissions of:: + + >>> user1 = DummyUser('bogus_user') + >>> user2 = DummyUser('bogus_user2') + + Now we're ready to make one of our RoleManagers and try it out. + First we'll verify that our users have the 'Foo' role, then we'll + make sure they can access objects which require that role, but not + others:: + + >>> rm = WorkspaceLocalRoleManager('rm', 'A Role Manager') + >>> rm.getRolesInContext(user1, ob) + ['Foo'] + >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Foo', 'Baz']) + 1 + >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Baz']) is None + True + >>> rm.getAllLocalRolesInContext(ob) + {'bogus_user': {'Foo'}} + + + Multiple Role Providers + ----------------------- + + It is a bit more interesting when we have more than one adapter + registered. We register it with a name so that it supplements, + rather than conflict with or override the existing adapter:: + + >>> class LessSimpleLocalRoleProvider(SimpleLocalRoleProvider): + ... userid = 'bogus_user2' + ... roles = ('Foo', 'Baz') + ... def getRoles(self, userid): + ... '''Grant bogus_user2 the 'Foo' and 'Baz' roles''' + ... if userid == self.userid: + ... return self.roles + ... return () + ... + ... def getAllRoles(self): + ... yield (self.userid, self.roles) + + >>> provideAdapter(LessSimpleLocalRoleProvider, adapts=(Interface,), + ... name='adapter2') + + This should have no effect on our first user:: + + >>> rm.getRolesInContext(user1, ob) + ['Foo'] + >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Foo', 'Baz']) + 1 + >>> rm.checkLocalRolesAllowed(user1, ob, ['Bar', 'Baz']) is None + True + >>> expected = {'bogus_user': {'Foo'}, 'bogus_user2': {'Foo', 'Baz'}} + >>> rm.getAllLocalRolesInContext(ob) == expected + True + + But our second user notices the change, note that even though two + of our local role providers grant the role 'Foo', it is not duplicated:: + + >>> set(rm.getRolesInContext(user2, ob)) == {'Foo', 'Baz'} + True + >>> rm.checkLocalRolesAllowed(user2, ob, ['Bar', 'Foo', 'Baz']) + 1 + >>> rm.checkLocalRolesAllowed(user2, ob, ['Bar', 'Baz']) + 1 + >>> rm.checkLocalRolesAllowed(user2, ob, ['Bar']) is None + True + + + Role Acquisition and Blocking + ----------------------------- + + This plugin will acquire role definitions from parent objects, + unless explicitly blocked. To test this, we need some objects + which support acquisition:: + + >>> from Acquisition import Implicit + >>> class DummyImplicit(DummyObject, Implicit): + ... def stupid_method(self): + ... return 1 + >>> root = DummyImplicit() + >>> next = DummyImplicit().__of__(root) + >>> last = DummyImplicit().__of__(next) + >>> other = DummyImplicit().__of__(root) + + So we now have /root/next/last and /root/other, we'll create and + register special adapters for our next and other objects. + + >>> class ISpecial1(Interface): + ... pass + >>> class ISpecial2(Interface): + ... pass + >>> from zope.interface import directlyProvides + >>> directlyProvides(next, ISpecial1) + >>> directlyProvides(other, ISpecial2) + >>> @adapter(ISpecial1) + ... class Adapter1(LessSimpleLocalRoleProvider): + ... + ... userid = 'bogus_user' + ... roles = ('Bar',) + >>> @adapter(ISpecial2) + ... class Adapter2(LessSimpleLocalRoleProvider): + ... + ... userid = 'bogus_user3' + ... roles = ('Foobar',) + >>> user3 = DummyUser('bogus_user3') + + We'll register these to override the existing unnamed adapter: + + >>> provideAdapter(Adapter1) + >>> provideAdapter(Adapter2) + + Now we can show how acquisition of roles works, first we look at the + 'last' item, which should have roles provided by + SimpleLocalRoleProvider, and LessSimpleLocalRoleProvider, as well + as acquired from Adapter1 on 'next': + + >>> set(rm.getRolesInContext(user1, last)) == {'Foo', 'Bar'} + True + + >>> set(rm.getRolesInContext(user2, last)) == {'Foo', 'Baz'} + True + + If we look at the parent, we get the same results, because the + SimpleLocalRoleProvider adapter also applies to the 'root' + object. However, if we enable local role blocking on 'next' we + won't see the roles from the 'root':: + + >>> set(rm.getRolesInContext(user1, next)) == {'Foo', 'Bar'} + True + >>> next.__ac_local_roles_block__ = True + >>> rm.getRolesInContext(user1, next) + ['Bar'] + + The checkLocalRolesAllowed and getAllLocalRolesInContext methods + take acquisition and blocking into account as well:: + + >>> rm.checkLocalRolesAllowed(user1, last, ['Bar']) + 1 + >>> rm.checkLocalRolesAllowed(user1, next, ['Foo', 'Baz']) is None + True + >>> expected = {'bogus_user': {'Foo', 'Bar'}, 'bogus_user2': {'Foo', 'Baz'}} + >>> rm.getAllLocalRolesInContext(last) == expected + True + + It's important to note, that roles are acquired only by + containment. Additional wrapping cannot change the security on an + object. For example if we were to wrap 'last' in the context of + other, which provides a special role for 'user3', we should see no + effect:: + + >>> rm.getRolesInContext(user3, last) + ['Foo'] + >>> set(rm.getRolesInContext(user3, other)) == {'Foobar', 'Foo'} + True + >>> rm.getRolesInContext(user3, last.__of__(other)) + ['Foo'] + + It's also important that methods of objects yield the same local + roles as the objects would + + >>> set(rm.getRolesInContext(user3, other.stupid_method)) == {'Foobar', 'Foo'} + True + + Group Support + ------------- + + This plugin also handles roles granted to user groups, calling up + the adapters to get roles for any groups the user might belong + to:: + + >>> user4 = DummyUser('bogus_user4', ('Group1', 'Group2')) + >>> user4.getGroups() + ('Group1', 'Group2') + >>> rm.getRolesInContext(user4, last) + ['Foo'] + >>> class Adapter3(LessSimpleLocalRoleProvider): + ... userid = 'Group2' + ... roles = ('Foobar',) + + >>> provideAdapter(Adapter3, adapts=(Interface,), name='group_adapter') + >>> set(rm.getRolesInContext(user4, last)) == {'Foobar', 'Foo'} + True + + + Wrong User Folder + ----------------- + + Finally, to ensure full test coverage, we provide a user object + which pretends to be wrapped in such a way that the user folder + does not recognize it. We check that it always gets an empty set + of roles and a special 0 value when checking access:: + + >>> class BadUser(DummyUser): + ... def _check_context(self, obj): + ... return False + >>> bad_user = BadUser('bad_user') + >>> rm.getRolesInContext(bad_user, ob) + [] + >>> rm.checkLocalRolesAllowed(bad_user, ob, ['Bar', 'Foo', 'Baz']) + 0 """ + meta_type = "Workspace Roles Manager" security = ClassSecurityInfo() @@ -386,19 +383,20 @@ def _get_userfolder(user, obj): need to rewrap""" context = user while context is not None: - if hasattr(context, 'getId'): - if context.getId() == 'acl_users': + if hasattr(context, "getId"): + if context.getId() == "acl_users": break context = aq_parent(aq_inner(context)) else: return None return aq_inner(context) + # # ILocalRolesPlugin implementation # def _getAdapters(self, obj): - adapters = getAdapters((obj, ), ILocalRoleProvider) + adapters = getAdapters((obj,), ILocalRoleProvider) # this is sequence of tuples of the form (name, adapter), # we don't really care about the names return (a[1] for a in adapters) @@ -408,11 +406,11 @@ def _parent_chain(self, obj): local role blocker""" while obj is not None: yield obj - if getattr(obj, '__ac_local_roles_block__', None): + if getattr(obj, "__ac_local_roles_block__", None): return new = aq_parent(aq_inner(obj)) # if the obj is a method we get the class - obj = getattr(obj, '__self__', new) + obj = getattr(obj, "__self__", new) def _get_principal_ids(self, user): """Returns a list of the ids of all involved security @@ -442,16 +440,11 @@ def getRolesInContext(self, user, object): roles.update(a.getRoles(pid)) # XXX: BBB code, kicks in only if there's no proper adapter if count == -1: - workspace = IGroupAwareWorkspace( - obj, - IWorkspace(obj, None) - ) + workspace = IGroupAwareWorkspace(obj, IWorkspace(obj, None)) if workspace is not None: roles.update(workspace.getLocalRolesForPrincipal(user)) for group in self._groups(obj, user, workspace): - roles.update( - workspace.getLocalRolesForPrincipal(group) - ) + roles.update(workspace.getLocalRolesForPrincipal(group)) return list(roles) @security.private @@ -512,6 +505,7 @@ def getAllLocalRolesInContext(self, object): rolemap.update(workspace.getLocalRoles()) return rolemap + # XXX: for BBB only @security.private @@ -520,7 +514,7 @@ def _groups(self, obj, user, workspace): a getGroups() method, yield each group_id returned by that method. """ if IGroupAwareWorkspace.providedBy(workspace): - getGroups = getattr(user, 'getGroups', None) + getGroups = getattr(user, "getGroups", None) if getGroups is not None: acl_users = aq_parent(aq_inner(self)) for group_id in getGroups(): diff --git a/setup.py b/setup.py index 26a4d9e..a7f13ec 100644 --- a/setup.py +++ b/setup.py @@ -2,69 +2,69 @@ from setuptools import setup -name = 'borg.localrole' -version = '3.1.10.dev0' +name = "borg.localrole" +version = "3.1.10.dev0" -readme = open('README.rst').read() -history = open('CHANGES.rst').read() +readme = open("README.rst").read() +history = open("CHANGES.rst").read() setup( name=name, version=version, - description='A PAS plugin which can manage local roles via an ' - 'adapter lookup on the current context', - long_description=readme + '\n' + history, - keywords='Plone PAS local roles', - author='Borg Collective', - author_email='borg@plone.org', - url='https://pypi.org/project/borg.localrole', - license='LGPL', + description="A PAS plugin which can manage local roles via an " + "adapter lookup on the current context", + long_description=readme + "\n" + history, + keywords="Plone PAS local roles", + author="Borg Collective", + author_email="borg@plone.org", + url="https://pypi.org/project/borg.localrole", + license="LGPL", packages=find_packages(), - namespace_packages=['borg'], + namespace_packages=["borg"], include_package_data=True, - platforms='Any', + platforms="Any", zip_safe=False, extras_require=dict( test=[ - 'plone.app.testing', + "plone.app.testing", ], ), install_requires=[ - 'setuptools', - 'six', - 'zope.annotation', - 'zope.component', - 'zope.deferredimport', - 'zope.interface', - 'Products.CMFCore', - 'Products.GenericSetup', - 'Products.PlonePAS >= 5.0.1', - 'Products.PluggableAuthService', - 'plone.memoize', - 'Acquisition', + "setuptools", + "six", + "zope.annotation", + "zope.component", + "zope.deferredimport", + "zope.interface", + "Products.CMFCore", + "Products.GenericSetup", + "Products.PlonePAS >= 5.0.1", + "Products.PluggableAuthService", + "plone.memoize", + "Acquisition", "Zope2;python_version<'3'", "Zope;python_version>='3.6'", ], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Framework :: Plone', - 'Framework :: Plone :: 5.0', - 'Framework :: Plone :: 5.1', - 'Framework :: Plone :: 5.2', - 'Framework :: Plone :: Core', - 'Intended Audience :: Other Audience', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', # noqa - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Topic :: Software Development :: Libraries :: Python Modules', + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Plone", + "Framework :: Plone :: 5.0", + "Framework :: Plone :: 5.1", + "Framework :: Plone :: 5.2", + "Framework :: Plone :: Core", + "Intended Audience :: Other Audience", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", # noqa + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Libraries :: Python Modules", ], )