cone.app
uses the security mechanism of
Pyramid
for managing access to parts of the application.
Similar to Zope, security rules are implemented as a
cascade of roles
, where each role defines a set of permissions
. A
user
or a group
of users are then assigned to this roles.
As authentication policy pyramid.authentication.AuthTktAuthenticationPolicy
is used. cone.app
passes the cone.app.security.groups_callback
to the
authentication policy which bridges roles to users and groups.
Authorization is implemented by Access Control Lists. As authorization policy
pyramid.authorization.ACLAuthorizationPolicy
is used. Security declaration
on application model nodes is provided by the __acl__
property, which is
expected by the authorization policy and described in the
cone.app.interfaces.ISecured
interface.
For retrieval of users, groups and the assigned roles, node.ext.ugm
is
used. See User and Group Management for
details.
By default, unauthenticed access to all application model nodes is prohibited.
The permissions used by default in cone.app
are:
The roles which come out of the box with cone.app
are:
view
.authenticated
role forbids. By
default, permissions assigned to this role are authenticated
role
permissions and list
.viewer
role permissions and add
and edit
.editor
role permissions and delete
, cut
, copy
,
paste
, manage_permissions
and change_state
.admin
role permissions and manage
.cone.app.interfaces.IOwnerSupport
. This role is added to
the related model node ACL if the current authenticated user is the creator
of the model node. By default an owner
gets the same permissions granted
as the admin
role.everyone
is login
.The default permission sets for the roles described above are defined in
cone.app.security.DEFAULT_ACL
.
The plumbing behavior cone.app.model.AppNode
already provides the
__acl__
property which always returns the DEFAULT_ACL
if no ACL is
registered for the this application model node.
If it’s desired to globally change the default permission sets for the whole
application the DEFAULT_ACL
must be overwritten.
ACLs can also be defined by overriding the __acl__
property of the
application node.
from cone.app.model import BaseNode
from pyramid.security import ALL_PERMISSIONS
from pyramid.security import Allow
from pyramid.security import Deny
from pyramid.security import Everyone
class ExampleNode(BaseNode):
@property
def __acl__(self):
return [
(Allow, 'role:manager', ['view', 'manage']),
(Allow, Everyone, 'login'),
(Deny, Everyone, ALL_PERMISSIONS)
]
A less immersive way for providing ACLs for model nodes is to use the
ACL registry. The plumbing behavior cone.app.model.AppNode
only returns
the cone.app.security.DEFAULT_ACL
if no dedicated ALC for this node has
been registered in the registry.
Registering a custom ACL for application root which grants view access to the application root model node for unauthenticated uses looks like so:
from cone.app.model import AppRoot
from cone.app.security import acl_registry
from pyramid.security import ALL_PERMISSIONS
from pyramid.security import Allow
from pyramid.security import Deny
from pyramid.security import Everyone
# permission sets
authenticated_permissions = ['view']
viewer_permissions = authenticated_permissions + ['list']
editor_permissions = viewer_permissions + ['add', 'edit']
admin_permissions = editor_permissions + [
'delete', 'cut', 'copy', 'paste', 'change_state',
]
manager_permissions = admin_permissions + ['manage']
everyone_permissions = ['login', 'view']
# custom ACL
custom_acl = [
(Allow, 'system.Authenticated', authenticated_permissions),
(Allow, 'role:viewer', viewer_permissions),
(Allow, 'role:editor', editor_permissions),
(Allow, 'role:admin', admin_permissions),
(Allow, 'role:manager', manager_permissions),
(Allow, Everyone, everyone_permissions),
(Deny, Everyone, ALL_PERMISSIONS),
]
acl_registry.register(custom_acl, AppRoot)
cone.app.model.AppNode.__acl__
tries to find a registered ALC by
self.__class__
and self.node_info_name
, thus application nodes must be
registered by both.
from cone.app.model import BaseNode
from cone.app.model import node_info
@node_info(name='example')
class ExampleNode(BaseNode):
pass
acl_registry.register(
example_acl,
obj=ExampleNode,
node_info_name='example'
)
As described in Roles section, a special role owner
exists which provides dedicated permissions for the current authenticated user
on application model nodes.
Owner support for application model nodes is implemented as plumbing behavior and must be enabled explicitly for every application model node by applying this behavior.
The cone.app.security.OwnerSupport
behavior extends the model node by an
owner
attribute, hooks up to the __init__
function where self.owner
gets set to the current authenticated user id, and extends the ACL by owner
ACE for the current user by hooking up to the __acl__
property.
The owner attribute maps to self.attrs['owner']
and must be
overwritten if owner persistence happens elsewhere.
from cone.app.model import BaseNode
from cone.app.security import OwnerSupport
from plumber import plumbing
@plumbing(OwnerSupport)
class ExampleNodeWithOwnerSupport(BaseNode):
pass
In many applications it’s required to grant access for specific parts of the
application model to specific users and groups. cone.app
ships with a
plumbing behavior providing principal related roles.
The cone.app.security.PrincipalACL
behavior extends the model node by
the role_inheritance
property, which is used to define whether principal
related ACL should be inherited from parent nodes and defaults to False
.
Further the behavior hooks up to the __acl__
property where default model
node ACL gets extended by the principal related ACL.
The property principal_roles
is a dict like mapping where keys represent
the principal id and values are a list of principal related roles. Since by
nature we know nothing about concrete persistence implementation it’s up to the
integration providing this property properly.
The PrincipalACL
relates to the sharing
tile, which provides a user
interface for managing principal related permissions. The vocabulary defining
the available roles shown up in the sharing tile are defined at
cone.app.security.DEFAULT_ROLES
.
A concrete shareable node looks like.
from cone.app.model import BaseNode
from cone.app.security import PrincipalACL
from plumber import plumbing
@plumbing(PrincipalACL)
class ExampleNodeWithPrincipalACL(BaseNode):
role_inheritance = True
@property
def principal_roles(self):
# this must be a persistent mapping between principal id and
# list of roles
return dict()
cone.app
provides User and Group Management via the contract described in
node.ext.ugm.interfaces
.
Configuration is done via application config file. The default file based
implementation for example gets configured as follows in app
section of the
ini file.
[app:example]
ugm.backend = file
ugm.users_file = /path/to/users
ugm.groups_file = /path/to/groups
ugm.roles_file = /path/to/roles
ugm.datadir = /path/to/userdata
cone.app
displays user and group names at several places. The attribute to
use as display names can be configured and defaults to fullname```for users
and ``groupname
for groups.
[app:example]
ugm.user_display_attr = fullname
ugm.group_display_attr = groupname
To provide your own UGM implementation, a cone.app.ugm.UGMFactory
must be
implemented and registered.
from cone.app.ugm import ugm_backend
from cone.app.ugm import UGMFactory
from node.ext.ugm import Ugm
class MyUGM(Ugm):
"""My UGM implementation.
Lots of implementation details goes here. See
``node.ext.ugm.interfaces`` for details.
"""
@ugm_backend('myugm')
class MyUGMFactory(UGMFactory):
"""Custom UGM factory.
It gets registered via ``ugm_backend`` decorator by name.
"""
def __init__(self, settings):
"""Initialize the factory.
Passed ``settings`` contains the application settings from the ini
file. Thus we are free to define and expect any settings we want.
On factory initialization, we simply read settings of interest from
``settings`` dict and remember them.
"""
self.setting_a = settings.get('myugm.setting_a', '')
self.setting_b = settings.get('myugm.setting_b', '')
def __call__(self):
"""Create the UGM instance.
"""
return MyUGM(
self.setting_a,
self.setting_b
)
In order to use our UGM implementation, configure it in the application config file.
[app:example]
ugm.backend = myugm
myugm.setting_a = a
myugm.setting_b = b
For authentication against a remote provider,
cone.app.interfaces.IAuthenticator
is used.
An implementation gets registered as named utility.
from cone.app import main_hook
from cone.app.interfaces import IAuthenticator
from zope.interface import implementer
@implementer(IAuthenticator)
class MyAuthenticator(object):
def authenticate(self, login, password):
# custom authentication goes here
# return id for login if authentication is successful, else None
return login
@main_hook
def example_main_hook(config, global_config, local_config):
# register custom authenticator as named utility.
config.registry.registerUtility(
MyAuthenticator(),
IAuthenticator,
name='my_authenticator'
)
The utility name must be defined in application ini file.
[app:example]
cone.authenticator = 'my_authenticator'
If a UGM implementation is configured, it gets used as fallback for authentication.