Understanding permissions and security

« Return to page index

Plone uses a combination of low-level Zope permissions, roles, local roles and workflows to manage permissions on objects. Understanding these will help you manage how, and by whom, your Plone site is accessed.

Permissions and roles

The Zope security model is the first thing you need to understand.

Permissions are low-level things - they specify that a user can do exactly A, but not B in a given context. Permissions are used as guards on access to methods, scripts, page templates, workflow transitions and sometimes individual components on a page.

The most common permissions are 'View', 'Modify portal content', 'Access contents information' and 'List folder contents'. These (along with a few others, such as 'Manage portal') are the so-called CMF Core permissions, found in the file 'Products/CMFCore/CMFCorePermissions.py' in variables with names such as 'View' and 'ModifyPortalContent'. In general, most content types use these permissions for accessing and modifying data. You may think that a little strange at first, but you will see later how workflow is used to gain fine-grained control over these permissions.

Permissions are assigned to roles of users, not individual users. So, in folder X, anyone in the RandomRole role can Modify Portal Content. The default roles are 'Member', 'Manager' and 'Reviewer'. There are also some "automatic" roles - these are 'Owner' and 'Anonymous'. The Anonymous role is inhabited by users who are not logged in. Most portal members have the 'Member' role, but it is not guaranteed. 'Owner' only applies where the current user is the owner of a piece of content.

In the Zope Management Interface, (almost) all objects have a 'Security' tab. Here you will see a (rather large) grid of member roles assigned to permissions. Most of these will be set to 'Acquire permission settings', which means that the roles given this permission are the same as that of the parent object. Zope security settings are additive, so if you leave the acquire flag checked and select some additional roles, you will get the roles set at the parent level plus any additional roles set at this level. You can stop this acquistion of roles by unchecking the aquire flag.

In most cases, you will not modify permissions in this way, except to set site-wide defaults for permissions other than the core permissions above. One fairly common example is to restrict the 'Add portal member' permission to managers only at the root level to close your portal to public sign-up: by default it is given to the 'Anonymous' role, which means that anyone can join the site (and thus create a new portal member). However, your first point of call should always be the workflow.

You are perfectly at liberty to add new roles where this makes sense. Since members can be in more than one role simultaneously, you probably only need to worry about a subset of the permissions when it comes to assigning permissions to a new role. Use the form at the bottom fo the 'Security' tab at the portal root to add and remove roles.

Users can be assigned to roles manually using the 'acl_users' user folder at the root of your Plone instance (not the one at the Zope root). However, it is more common to assign roles to groups and then add users to groups, as described in the next section.

Groups in Plone

Plone adds the concept of a group of users to the basic Zope security model. Groups are convenient ways of managing roles (and thus permissions) for a number of users simultaneously.

Users can be in groups - managed via users and groups administration in plone setup. Groups can be given particular roles if need be - all users in that group will gain the given role.

It is very rare that only one user will ever have a given role. More likely, there will be a group of users with the same role in the portal - and even where there is currently only one user (say, only one site manager), it often makes sense to cater for the possibility that there will be more users later on. Since users can be in several groups at the same time, you can get fine grained control over permissions and classifications of users using this mechanism.

The previous page showed how to make new roles in the portal. Using the users and groups administration tool in Plone setup, you can easily assign roles to groups - just tick the relevant boxes and save. Then, click on a user to assign him or her to the relevant groups. Once in a group, the user will gain all roles assigned to that group in addition to any roles assigned only to that user.

It is also legitimate to use groups to logically group users without assigning any special roles. This has the added advantage that members will gain a shared group folder (if turned on in the 'portal_groups' tool). It can also be used to assign local roles to groups - the topic of the next page.

Local roles and sharing

Often, you want to give a user or group specific (usually elevated) permissions in a specific area of your site, but not site-wide. Enter local roles and the 'sharing' tab.

The 'sharing' tab on standard Plone content is what lets you give people different permissions in different areas. If it is not shown, you can find it by appending '/folder_localrole_form' to a URL.

On the local role form, you can search for another user and assign that user roles. You can also assign roles to groups (see the previous page). Most commonly, you will give other users either the Owner or Manager role over your content to give them the ability to modify it, but with custom permissions and roles, you may have other roles to grant.

Note that role selection will acquire down, so if a user has Manager role at the '/stuff' folder, they will also have it at '/stuff/documents/my-document'. Currently (until Plone 2.1, most likely), local roles can be added at a lower level in the acqusition tree, but not taken away. That is, if you give a user Manager permissions at '/stuff', there is no way to prevent him or her from having the Manager permission at '/stuff/documents'. This is summarised in PLIP 16.

A common way of using local roles is to give the members of a particular portal group Manager permissions in a given folder. For example, to give all members of the 'A-Team' group free reins in the '/missions' folder, go to '/missions/folder_localrole_form' either by typing in the URL or clicking the 'sharing' tab in that folder, and assign the Manager local role to the 'A-Team' group.

Controlling access with workflows

In most instances, workflows, managed via the portal_workflow tool, are the correct way of managing permissions on your content.

Workflow states are shown on content objects in the state drop-down at the top right, in the green border on content you have permission to edit. The items in the menu are the transitions you can use between workflow states. For example, to move from the visible (default) to the published state, you use the publish transition. State transitions can have guards based on roles (only members in a certain role in the current context can execute the transition - for example, only Managers and Reviewers are permitted to use the publish transition in the default Plone workflow), permissions or arbitrary expressions. Each content type either has no workflow (rare) or has exactly one workflow associated with it. This is set via the portal_workflow tool. The Contents tab of this tool in the ZMI shows the currently installed workflows. You can add or modify workflows via this GUI, adding states (remember to set a default state!), transitions and the allowed transitions between states. More documentation on the DCWorkflow engine can be found on zope.org.

The workflow engine will manage a certain number of permissions and set them correctly when the state of an object is changed. You should rarely, if ever, need to change permissions at the "Security" tab. Instead, you change them in the workflow associated with the folder or content type in question. This is the mechanism which allows several different content objects to re-use the CMF core permissions. For example, the Anonymous role has the View permission in the published state, but not the visible state. Note that if you modify a workflow's permissions-in-state mapping, the changes will not affect existing objects automatically. You must update the security settings by clicking Update security settings at the bottom of the Workflows tab in portal_workflow in the ZMI.

Thus, workflows serve a dual purpose - to represent the evolution of a piece of content, from creation (the initial state), through a review cycle (if there is one), to a final state, and to control the permissions to the content in each state. If you have particular needs, you can create a new workflow (usually based on an existing workflow), set up the states and transitions necessary, make the workflow manage a certain set of permission and set the role-to-permission mapping for those permissions in each workflow state. Using role or permission guards on transitions, you can then control who is at liberty to alter the permissions. For more details on how to use the DCWorkflow engine, read its documentation.

Using permissions and workflow in your custom products

When you are developing a Plone site, it is usually best to develop your customisations or new content types on the filesystem, as a new Zope product. Setting up workflows programatically using the portal_workflow tool is a bit of a pain, but luckily there are tools to make your life much easier. You also need to ensure you use the correct permission declarations on your objects.

Explaining how to develop products and secure them properly is beyond the scope of this tutorial. Suffice it to say that to create re-usable Zope/Plone components, and for the sake of your own sanity, you should be developing your customisations and add-ons on the filesystem, as Zope products. Raphal Ritz' excellent MySite tutorial will give you a good and in-depth introduction to the topic.

Protecting your code

When creating classes which are intended to be accessed through the web (typically Archetypes content types or portal tools, though this applies to any Zope class derived from ExtensionClass) you must add security assertions on your methods. Much more information about this can be found in the Zope Developer Guide.

The following code example shows a general paradigm that is frequently followed in Archetypes products using the CMF core permissions. Please refer to the developer guide's chapter 6 for more details:

  from AccessControl import ClassSecurityInfo
  from Products.CMFCore import CMFCorePermissions
  # ... (additional imports and initialisation)

  class MyType(BaseContent):

    # ... (initialisation of schema and factory type info)

    # create a class security object to handle our security policy
    security = ClassSecurityInfo()

    # set a method to be publically accessible - all TTW code can
    # call this directly
    security.declarePublic('publicMethod')
    def publicMethod(self):
      # ... (docstring and method body)

    # set a method to be private - no TTW code can call this directly
    security.declarePrivate('privateMethod')
    def privateMethod(self):
      # ... (docstring and method body)

    # set a method to be protected by a given permission.
    # Only users with this permission in the context can call
    security.declareProtecte(CMFCorePermissions.View, 'protectedMethod')
    def protectedMethod(self):
      # ... (docstring and method body)

  # ... (type initialisation)

This rather amputated example shows how a class creates a ClassSecurityInfo object and uses it to declare its methods public (accessible to all through-the-web code), private (not accessible through the web) or protected by some permission - in this case the CMF core permission View. As with all matters of security, you should always protect your methods by the most limiting permission possible to ensure security is not breached.

Creating new workflows

Workflows can be created through the portal_workflow user interface. If you wish to ship such a workflow with your product, you must dump it to a python file and register it during Install.py. There is a dedicated product for this called DCWorkflowDump. When installed (just put it in your Products folder, no Plone installation necessary), it will give you a Dump tab when viewing a workflow in the ZMI to create a file containing the setup code for your workflow.

Typically, this workflow definition is dumped into your product's Extensions/ folder and called during Install.py to set up your workflow. If you subsequently wish to make changes to your workflow, you may wish to modify this dumped code directly rather than use the GUI and re-dump, as it is likely to be quicker. The dumped code should be fairly easy to understand once you know a bit about workflows.

An alternative way of creating workflow definitions is ArchGenXML. Using this tool, you can create UML state diagrams to represent your workflows. See the ArchGenXML documentation for details.

Finally, once your workflow has been created, you must register it with the workflow tool and associate any types you want with it (ArchGenXML does all of this automatically for you). The following code snippet is adapted from the PloneHelpCenter - the fine software that runs this documentation section - also found in the Collective. You may want to look at that product in more detail (a CVS checkout will be preferable) to see how workflows are used "in real life". In Extensions/Install.py, you may wish to put:

    # ...  (header and imports)

    def install(self):

        # ... (additional installation and setup)

        # Get our workflow and install it
        from Products.MyProduct.Extensions import MyWorkflow

        # The PloneHelpCenter does this, because it modifies the
        # dumped file to put the installation-initiating code inside
        # a method called install(). By default, it is called
        # automatically as soon as the workflow module is imported.

        # HCWorkflow.install()

        # Register the workflow with the tool 
        wf_tool = getToolByName(self, 'portal_workflow')

        # The id of the workflow if my_workflow, and the title is
        # My custom workflow. Due to a dumb API, these names have
        # to be entered verbatim in the argument to the method
        # below, with spaces and parentheses as shown.

        if not 'my_workflow' in wf_tool.objectIds():
            wf_tool.manage_addWorkflow('my_workflow (My custom workflow)',
                                       'my_workflow')

        # Do this after installing all workflows   
        wf_tool.updateRoleMappings()

        # Now tell our custom type MyType to use this workflow
        wf_tool.setChainForPortalTypes(pt_names=['MyType'], 
                                       chain='my_workflow')

        # ... (additional installation and setup)

That should be it - your custom workflow should be available for use after you install (or re-instal) your product. You are encouraged to take a look at the PloneHelpCenter code for a more comprehensive example.