Using permissions and workflow in your custom products

by Martin Aspeli last modified Dec 30, 2008 03:04 PM

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":/documentation/link/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":http://www.zope.org/Documentation/Books/ZDG.

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":/products/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":/documentation/tutorial/archgenxml-getting-started 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":http://sf.net/projects/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.