Fragments of Code

December 22, 2008

My life with Plone, part 3

Filed under: Plone, Zope, code — Tags: , , — David Chandek-Stark @ 11:10 am

A continuation of parts 1 and 2.

Workflow

Plone’s workflow functionality, which is the portal_workflow tool of the underlying Zope CMF, can do just about anything you want it to do, provided that you can figure out how to do it. Plone itself still doesn’t have a UI for creating and configuring workflow, so you have to do it in the ZMI, which AFAICT has changed little in the time I have used Zope and the CMF.  Plone has fairly recently integrated placeful workflow, adding the ability to override sitewide workflows at the folder level, which is an important consideration in a large and complex site.  Hopefully, they’ve fixed the bugs in the UI (both Plone and the ZMI) which I encountered when I first tried using it.

There are a couple things about CMF/Plone workflow that are not exactly intuitive, or at least are different from how other CMS systems implement workflow functionality:

  1. A content item is always under workflow, if its content type is configured to use workflow at all. In other words, a content item doesn’t go “through” workflow; rather, the item is bound to its workflow.
  2. Workflow has no security per se.  In other words, access to objects in various workflow states is determined by the security settings on the objects in the ZODB. Workflow transitions implement changes in ZODB security settings.

An access control gotcha

Zope security settings are, so to speak, atomic.  Inheritance of permissions can be toggled on/off at any level of the object hierarchy and explicitly overridden on any object.  So far, so good.  However, unlike file systems, access to objects is not strictly dependent on access to their containers.  For example, a “private” folder which is not publicly accessible can contain “published” or “public draft” items which are.  The security problem is that content authors may assume, not without reason, that any content they put in a private folder will also be private.  This is a deep issue because CMF workflow explicitly sets object permissions and Zope security only considers the permissions on an object, not its container (factoring in inherited permissions, of course).  A workaround I have implemented is a modification of the default workflow to force setting the initial state of an object to “private” if its parent object is in the private state.  At least this provides a modicum of protection to unwitting content authors; they have to explicity “make visible” or “publish” these items to make them more widely accessible.

1. Wrote a script hideInPrivateParent:

## Script (Python) "hideInPrivateParent"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=state_change
##title=Transition state_change.object to private state if parent object is private
##
wftool = context.portal_workflow
PRIVATE_TRANSITION = 'hide'
PRIVATE_STATE = 'private'
obj = state_change.object
parent = obj.aq_inner.getParentNode()
if parent is not None:
    try:
        if wftool.getInfoFor(parent, 'review_state') == PRIVATE_STATE:
            wftool.doActionFor(obj, PRIVATE_TRANSITION)
    except:
        pass

2. Created a new workflow state called “initialized” and set it as the default workflow state.

3. Created a new workflow transition call “initialize”.  Set the “trigger type” to “automatic”.  Set the “destination state” to “visible”. Set “script (after)” to hideInPrivate Parent.

The result is that when a new content item is created with this workflow, its state will be private if its parent object (typically a folder) is private, otherwise it will be “visible”.

Continued in Part 4.

December 14, 2008

My life with Plone, part 2

Filed under: Plone, Python, Zope — Tags: , , , , — David Chandek-Stark @ 12:15 am

In part 1 I discussed documentation, support for previous releases, upgrading, backup/restore and versioning.

The object database

As I said in Part 1, the ZODB is very solid. I find, however, that the tools for analyzing the contents of the database — at least those that ship with Plone’s Zope/CMF — are relatively weak. It feels like the system needs a GUI app for administrators. As it is, you either deal with the limited functionality of the ZMI, or you use plain old Python. I miss the power of relational databases to generate ad hoc reports with SQL statements, or even something like Django’s ORM methods. Part of the problem I suppose is poor documentation of the Zope API, and it can take some detective work and trial and error (even if you do use DocFinderTab) to find not only the methods you need, but the correct way to call them. This in turn is to some extent the fault of the Python language itself in that function parameters and return values are not typed as in Java, for example. There are certainly advantages to Python’s approach — and everything considered Python is still my preferred programming language — but I do think it often results in weaker documentation. Even the official documentation of the Python language is, to me, often maddeningly vague with respect to input and output of standard library functions.

Managing permissions, users and groups

Zope has a complex, flexible, and extremely granular security model, almost too much so for a web-based management UI. I generally try to minimize the amount of manual permissions tweaking I do in the system, as it feels a bit risky, partly because of issues with the database noted above. Managing users and groups in Plone is no better or worse than most decent content management systems or complex web applications, which is to say that it’s serviceable but not great. Again, it seems that this aspect of the system could benefit from a GUI app, or perhaps the web UI just needs more development, maybe adding some Ajax functionality to reduce page loads, etc.  A major positive development in user/group management has been the maturation of the Pluggable Authentication Service (PAS) and its integration into Plone.  Since I have a requirement to use an external authentication system (Shibboleth) via my Apache front-end, I started in the pre-PAS days by using a product called RemoteUserFolder, which worked well with Zope, but did not integrate with Plone. Now I use apachepas and AutoMemberMaker (I just learned that these products have been replaced by WebServerAuth). Since PAS is now the default user folder in Plone, these products integrate seamlessly and they have proven solid.

Continued in Part 3.

December 8, 2008

Django group_required decorator

Filed under: Django, code — Tags: , , — David Chandek-Stark @ 4:51 pm

A simple extension of the user_passes_test decorator:

from django.contrib.auth.decorators import user_passes_test

def group_required(*group_names):
    """
    Requires user membership in at least one of the groups passed in.

    Checks is_active and allows superusers to pass regardless of group
    membership.
    """
    def in_group(u):
        return u.is_active and (u.is_superuser or bool(u.groups.filter(name__in=group_names)))
    return user_passes_test(in_group)

Thanks to Michael Sanders for his comment. I revised the decorator to check is_active and also to allow superusers to pass.

December 5, 2008

Authenticated access to directory indexes in Apache

Filed under: Apache, code — Tags: , — David Chandek-Stark @ 2:18 pm

You don’t want your media directories (images, css, javascript) to be anonymously browseable, right? But wouldn’t it be nice if you could browse them? Here’s how to do it:

<Directory /path/to/media>
    Options +Indexes
    Order Deny,Allow
    Allow from all
    Satisfy all
    <Files index.html>
        [authorization directives]
    </Files>
</Directory>

Depending on how your authentication mechanism works, you may also have to add a RewriteRule directive to deal with an explicit request for index.html:

RewriteRule ^/url/to/media/(.*/)?index.html$ /url/to/media/$1 [R,L]

In my case, using Shibboleth and mod_shib, authentication takes place on an external site and then redirects the client back. I’m guessing that because mod_dir is internally rewriting the URL from */ to */index.html, mod_shib is not handed the “original” URL (with just the trailing slash), but the DirectoryIndex URL.

Blog at WordPress.com.