Security¶
Kotti security is based on the concepts of users, groups, roles, permissions and workflow.
- User
- A user is an entity that can authenticate himself.
- Group
- A group is a collection of users or other groups.
- Permission
A permission describes what is allowed on an object.
Permissions are never directly assigned to users or groups but always aggregated in roles.
- Role
A Role is a collection of permissions.
Users or groups can have global or local roles.
- Global Roles
- Global roles are assigned to a user or group via Kotti’s user management screens. They apply to every object in a site. You should use them very rarely, maybe only assign the “Adminsitrator” role to the “Administrator” group. This assignment is present by default in a fresh Kotti site.
- Local Roles
- Local roles are assigned to a user or group via the “Sharing” screen of a content object. They apply only to this object and its children.
- Workflow
- The workflow keeps track of the current state of each object lifecycle to manage content security. There is an initial state and you can move to other states thanks to transitions; each state defines a security matrix with roles and permissions. By default Kotti provides a two-state workflow (private and public) for all object types except files and images. Kotti’s workflow implementation is based on repoze.workflow.
How to create a new role¶
Small recipe you can use if you want to create a new role:
from kotti.security import (
Principal,
ROLES,
SHARING_ROLES,
set_roles,
set_sharing_roles,
set_user_management_roles,
)
from kotti_yourpackage import _
def add_role(role_id, role_title):
""" Add role in share view and user management views """
UPDATED_ROLES = ROLES.copy()
UPDATED_ROLES[role_id] = Principal(role_id,
title=role_title)
UPDATED_SHARING_ROLES = list(SHARING_ROLES)
UPDATED_SHARING_ROLES.append(role_id)
set_roles(UPDATED_ROLES)
set_sharing_roles(UPDATED_SHARING_ROLES)
set_user_management_roles(UPDATED_SHARING_ROLES + ['role:admin'])
add_role(u'role:customer', _(u'Customer'))
Practically you can add the code above to any file, as long as it is imported on application startup.
However, good practice would be to add it to your add on’s __init__.py
for small amounts of changes (like in the example) or to a separate file for larger amounts.
Workflows¶
You can use an XML file (zcml) in order to describe your workflow. You can see an example here: workflow.zcml.
As you can see it is quite straightforward to add new states, transitions, permissions, etc. You can easily turn the default 2-state website workflow into something completely different or turn your Kotti app into an intranet application.
The default workflow definition is loaded from your project’s .ini
file (using the kotti.use_workflow
setting).
The kotti.use_workflow
setting’s default value is:
kotti.use_workflow = kotti:workflow.zcml
You can change change the default workflow for your site, register new workflows related to specific content types or disable it completely.
How to disable the default workflow¶
Kotti is shipped with a simple workflow definition based on private and public states. If your particular use case does not require workflows at all, you can disable this feature with a non true value. For example:
kotti.use_workflow = 0
How to override the default workflow for all content types¶
The default workflow is quite useful for websites, but sometimes you need something different.
Just point the kotti.use_workflow
setting to your zcml file:
kotti.use_workflow = kotti_yourplugin:workflow.zcml
The simplest way to deal with workflow definitions is:
- create a copy of the default workflow definition and
- customize it (change permissions, add new states, permissions, transitions, initial state and so on).
If you change workflow settings, you need to reset all your content’s workflow states and thus the permissions for all objects under workflow control using the kotti-reset-workflow
console script.
kotti-reset-workflow command usage¶
If you change workflow settings you’ll need to update security.
$ kotti-reset-workflow --help
Reset the workflow of all content objects in the database.
This is useful when you want to migrate an existing database to
use a different workflow. When run, this script will reset all
your content objects to use the new workflow, while trying to
preserve workflow state information.
For this command to work, all currently persisted states must map
directly to a state in the new workflow. As an example, if
there's a 'public' object in the database, the new workflow must
define 'public' also.
If this is not the case, you may choose to reset all your content
objects to the new workflow's *initial state* by passing the
'--purge-existing' option.
Usage:
kotti-reset-workflow <config_uri> [--purge-existing]
Options:
-h --help Show this screen.
--purge-existing Reset all objects to new workflow's initial state.
How to enable the standard workflow for images and files¶
Images and files are not associated with the default workflow.
If you need a workflow for these items you need to attach the IDefaultWorkflow
marker interface.
You can add the following lines in your includeme function:
from zope.interface import implementer
from kotti.interfaces import IDefaultWorkflow
from kotti.resources import File
from kotti.resources import Image
...
def includeme(config):
...
# enable workflow for images and files
implementer(IDefaultWorkflow)(Image)
implementer(IDefaultWorkflow)(File)
...
How to assign a different workflow to a content type¶
We are going to use the default workflow for standard content types and a custom workflow for content types providing the ICustomContent
marker interface.
All other content types will still use the default workflow.
Third party developers will be able to override our custom workflow without having to touch any line of code (just a .ini
configuration file)
Let’s assume you are starting with a standard Kotti package created with pcreate -s kotti kotti_wf
.
Four steps are needed:
- create a new marker interface ICustomContent,
- change
kotti_wf.resource
(replaceIDefaultWorkflow
with our newICustomContent
), - create the new workflow definition and
- register your workflow definition.
Create a new module kotti_wf/interfaces.py
with this code.
This is optional but it doesn’t hurt, the important thing is to omit the IDefaultWorkflow
implementer from kotti_wf.resources
:
from zope.interface import Interface
class ICustomContent(Interface):
""" Custom content marker interface """
Change your kotti_wf.resources
module like so:
from kotti.resources import Content
from zope.interface import implements
from kotti_wf.interfaces import ICustomContent
class CustomContent(Content):
""" A custom content type. """
implements(ICustomContent)
Here it is, our “custom” workflow definition assigned to our ICustomContent
marker interface:
<configure xmlns="http://namespaces.repoze.org/bfg"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="Kotti">
<include package="repoze.workflow" file="meta.zcml"/>
<workflow
type="security"
name="custom"
state_attr="state"
initial_state="private"
content_types="kotti_wf.interfaces.ICustomContent"
permission_checker="pyramid.security.has_permission"
>
<state name="private" callback="kotti.workflow.workflow_callback">
<key name="title" value="_(u'Private')" />
<key name="order" value="1" />
<key name="inherit" value="0" />
<key name="system.Everyone" value="" />
<key name="role:viewer" value="view" />
<key name="role:editor" value="view add edit delete state_change" />
<key name="role:owner" value="view add edit delete manage state_change" />
</state>
</workflow>
</configure>
Last you have to tell Kotti to register your new custom workflow including our zcml
file:
kotti.zcml_includes = kotti_wf:workflow.zcml
Special cases:
- if you change workflow settings on a site with existing
CustomContent
instances, you need to update the workflow settings using thekotti-reset-workflow
command. - if you assign a new workflow definition to a content that already provides the
IDefaultWorkflow
marker interface (by default all content types except files and images), you will have to create and attach on your workflow definition anelector
function (it is just a function accepting a context and returningTrue
orFalse
)