Skip to content

Commit

Permalink
move the Role component from grok to grokcore.security too
Browse files Browse the repository at this point in the history
  • Loading branch information
janwijbrand committed Apr 30, 2012
1 parent d6e176d commit 968196a
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 21 deletions.
28 changes: 15 additions & 13 deletions setup.py
Expand Up @@ -25,24 +25,26 @@ def read(*rnames):
description='Grok-like configuration for Zope security components',
long_description=long_description,
license='ZPL',
classifiers=['Intended Audience :: Developers',
'License :: OSI Approved :: Zope Public License',
'Programming Language :: Python',
'Framework :: Zope3',
],

classifiers=[
'Intended Audience :: Developers',
'License :: OSI Approved :: Zope Public License',
'Programming Language :: Python',
'Framework :: Zope3',
],
packages=find_packages('src'),
package_dir = {'': 'src'},
namespace_packages=['grokcore'],
include_package_data=True,
zip_safe=False,
install_requires=['setuptools',
'grokcore.component >= 2.1',
'martian >= 0.13',
'zope.component',
'zope.interface',
'zope.security',
],
install_requires=[
'setuptools',
'grokcore.component >= 2.1',
'martian >= 0.13',
'zope.component',
'zope.interface',
'zope.security',
'zope.securitypolicy',
],
tests_require=tests_require,
extras_require={'test': tests_require},
)
1 change: 1 addition & 0 deletions src/grokcore/security/__init__.py
Expand Up @@ -16,6 +16,7 @@
from grokcore.component import *

from grokcore.security.components import Permission, Public
from grokcore.security.components import Role
from grokcore.security.directive import require, permissions

# Import this module so that it's available as soon as you import the
Expand Down
16 changes: 16 additions & 0 deletions src/grokcore/security/components.py
Expand Up @@ -19,3 +19,19 @@ class Permission(Permission):
pass

Public = 'zope.Public'

from zope.securitypolicy.role import Role as securitypolicy_Role

class Role(securitypolicy_Role):
"""Base class for roles in Grok applications.
A role is a description of a class of users that gives them a
machine-readable name, a human-readable title, and a set of
permissions which users belong to that role should possess::
class Editor(grok.Role):
grok.name('news.Editor')
grok.title('Editor')
grok.permissions('news.EditArticle', 'news.PublishArticle')
"""
10 changes: 5 additions & 5 deletions src/grokcore/security/directive.py
Expand Up @@ -28,9 +28,10 @@ def get(self, directive, component, default):
if (permissions is default) or not permissions:
return default
if len(permissions) > 1:
raise GrokError('grok.require was called multiple times in '
'%r. It may only be set once for a class.'
% component, component)
raise GrokError(
'grok.require was called multiple times in '
'%r. It may only be set once for a class.'
% component, component)
return permissions[0]

def pop(self, locals_, directive):
Expand Down Expand Up @@ -88,8 +89,7 @@ def validate(self, *values):
if martian.util.not_unicode_or_ascii(value):
raise GrokImportError(
"You can only pass unicode values, ASCII values, or "
"subclasses of grokcore.security.Permission to the '%s'"
" directive."
"subclasses of grok.Permission to the '%s' directive."
% self.name)

def factory(self, *values):
Expand Down
1 change: 1 addition & 0 deletions src/grokcore/security/interfaces.py
Expand Up @@ -18,6 +18,7 @@
class IBaseClasses(Interface):
Permission = Attribute("Base class for permissions.")

Role = Attribute("Base class for roles.")

class IDirectives(Interface):

Expand Down
58 changes: 57 additions & 1 deletion src/grokcore/security/meta.py
Expand Up @@ -15,9 +15,12 @@

import martian
import grokcore.component
import grokcore.component.util
import grokcore.security
from martian.error import GrokError

from zope.security.interfaces import IPermission
from martian.error import GrokError


def default_fallback_to_name(factory, module, name, **data):
return name
Expand Down Expand Up @@ -47,3 +50,56 @@ def execute(self, factory, config, name, title, description, **kw):
order=-1 # need to do this early in the process
)
return True


from zope.i18nmessageid import Message
from zope.securitypolicy.rolepermission import rolePermissionManager
from zope.securitypolicy.interfaces import IRole

from grokcore.security.directive import permissions
from grokcore.security.components import Role


class RoleGrokker(martian.ClassGrokker):
"""Grokker for components subclassed from `grok.Role`.
Each role is registered as a global utility providing the service
`IRole` under its own particular name, and then granted every
permission named in its `grok.permission()` directive.
"""
martian.component(Role)
martian.priority(martian.priority.bind().get(PermissionGrokker()) - 1)
martian.directive(grokcore.component.name)
martian.directive(
grokcore.component.title, get_default=default_fallback_to_name)
martian.directive(grokcore.component.description)
martian.directive(permissions)

def execute(self, factory, config, name, title, description,
permissions, **kw):
if not name:
raise GrokError(
"A role needs to have a dotted name for its id. Use "
"grok.name to specify one.", factory)
# We can safely convert to unicode, since the directives makes sure
# it is either unicode already or ASCII.
if not isinstance(title, Message):
title = unicode(title)
if not isinstance(description, Message):
description = unicode(description)
role = factory(unicode(name), title, description)

config.action(
discriminator=('utility', IRole, name),
callable=grokcore.component.util.provideUtility,
args=(role, IRole, name),
)

for permission in permissions:
config.action(
discriminator=('grantPermissionToRole', permission, name),
callable=rolePermissionManager.grantPermissionToRole,
args=(permission, name),
)
return True
2 changes: 1 addition & 1 deletion src/grokcore/security/tests/permissions/directive.py
Expand Up @@ -8,6 +8,6 @@
Traceback (most recent call last):
...
GrokImportError: You can only pass unicode values, ASCII values, or
subclasses of grokcore.security.Permission to the 'permissions' directive.
subclasses of grok.Permission to the 'permissions' directive.
"""
Empty file.
15 changes: 15 additions & 0 deletions src/grokcore/security/tests/role/missing_role_name.py
@@ -0,0 +1,15 @@
"""
A role has to have a name to be defined.
>>> grokcore.security.testing.grok(__name__)
Traceback (most recent call last):
GrokError: A role needs to have a dotted name for its id.
Use grok.name to specify one.
"""

import zope.interface
import grokcore.security
import grokcore.security.testing

class MissingName(grokcore.security.Role):
pass
35 changes: 35 additions & 0 deletions src/grokcore/security/tests/role/permissions.py
@@ -0,0 +1,35 @@
"""
A Role component optionally defines what permission it comprises.
The grok.permissions() directive is used to specify the set of permissions
that are aggregated in the particular Role. The permissions can be referenced
by "name" or by class.
>>> grokcore.security.testing.grok(__name__)
"""

import grokcore.component as grok
import grokcore.security.testing
from grokcore.security import Role
from grokcore.security import Permission, permissions
import zope.interface

class FirstPermission(Permission):
grok.name('first permission')

class SecondPermission(Permission):
grok.name('second permission')

class RoleComprisingTwoPermissionsByName(Role):
grok.name('ByName')
permissions(
'first permission',
'second permission'
)

class RoleComprisingTwoPermissionsByClass(Role):
grok.name('ByClass')
permissions(
FirstPermission,
SecondPermission
)
78 changes: 78 additions & 0 deletions src/grokcore/security/tests/role/role_i18n.py
@@ -0,0 +1,78 @@
"""
A Role component have a title and description, that can be internationalized.
Let's grok this package and check we still have a Message object for the
internationalized title and description of the defined roles.
>>> grokcore.security.testing.grok(__name__)
>>> from zope.securitypolicy.interfaces import IRole
>>> from zope.component import getUtility
>>> from zope.i18nmessageid import Message
A grok.Role without any internationalization.
The id, title and description should be unicode::
>>> role = getUtility(IRole, name="RoleWithoutI18n")
>>> role.id
u'RoleWithoutI18n'
>>> role.title
u'RoleWithoutI18n'
>>> role.description
u'My role without i18n'
>>>
>>> isinstance(role.id, Message)
False
>>> isinstance(role.title, Message)
False
>>> isinstance(role.description, Message)
False
A grok.Role registered with the name and description directives only, both
internationalized.
The id is taken from the name directive and should not be a Message object.
The title is taken from the name directive because the title directive
is not used.
::
>>> role = getUtility(IRole, name="RoleWithI18n")
>>> isinstance(role.id, Message)
False
>>> isinstance(role.title, Message)
True
>>> isinstance(role.description, Message)
True
A grok.Role registered with name, title and description directives::
>>> role = getUtility(IRole, name="RoleWithI18nTitle")
>>> isinstance(role.id, Message)
False
>>> isinstance(role.title, Message)
True
>>> isinstance(role.description, Message)
True
"""

import grokcore.component as grok
import grokcore.security

from grokcore.security import Role
from zope.i18nmessageid import MessageFactory

_ = MessageFactory("testi18n")


class RoleWithoutI18n(Role):
grok.name('RoleWithoutI18n')
grok.description('My role without i18n')


class RoleWithI18n(Role):
grok.name(_('RoleWithI18n'))
grok.description(_(u'My role with i18n'))


class RoleWithI18nTitle(Role):
grok.name('RoleWithI18nTitle')
grok.title(_('RoleWithI18n'))
grok.description(_(u'My role with i18n'))
3 changes: 2 additions & 1 deletion src/grokcore/security/tests/test_all.py
Expand Up @@ -38,8 +38,9 @@ def suiteFromPackage(name):
def test_suite():
suite = unittest.TestSuite()
for name in [
'security',
'permissions',
'role',
'security',
]:
suite.addTest(suiteFromPackage(name))
return suite
Expand Down

0 comments on commit 968196a

Please sign in to comment.