[Zope3-dev] Initial thoughts on the Zope3 security framework

Phillip J. Eby pje@telecommunity.com
Fri, 14 Dec 2001 09:12:51 -0500


At 10:40 PM 12/13/01 -0500, Guido van Rossum wrote:
>Now let me try defining role:
>
>- A role is a set of permissions.  Roles are also dependent on the
>   context.  Roles are used as a shorthand for expressing the mapping
>   from principals to permissions.  Rather than defining the set of
>   permissions assigned to each principals (which would be a lot of
>   work since there are hundreds of permissions and there can be many
>   thousands of principals), you typically define a small number of
>   roles (say a dozen), and then assign each principal a small number
>   of roles (often only one or two).
>
>   Example roles: Manager, Editor, Reviewer.
>
>Contrast this with a group: a group is a set of principals.  In the
>matrix of principals x permissions, if principals are rows and
>permissions are columns, groups are sets of rows and roles are sets of
>columns.  Zope2 doesn't have groups, and Zope3 doesn't have them yet
>-- but we may add them, because they are useful in some situations.
>
>The similarity between groups and roles comes from the fact that if
>you only look at the mapping between principals and permissions that's
>they define, each role defines a set of (principal, permission) pairs
>that is the intersection of some of rows and some columns -- this is
>indistinguishable from the set of (primcipal, permission) pairs
>defined by a group.
>
>I think that Jim & Tres claim that groups and roles are different
>because the other semantics and connotations are different -- possibly
>because groups don't have the context-sensitivity that roles have.
>(Of course this is hard to say because we don't have groups yet. :-)

I prefer to think of a role as defining a *type of relationship* between a 
principal and an object, which then implies permissions.  X is 'Manager' of 
Y, for example.  This is inherently local in nature.  In the SQL and 
LDAP-based applications I usually work on, roles are *computed*, not 
stored, using the relationships of the underlying data model.

Talking about content trees tends to obscure this concept, because the 
content doesn't usually contain such relationships.  But when you're doing 
an SQL case tracking system and somebody puts a case in, it's pretty clear 
that the user object referred to by your "requester" field should be given 
the role of "Requester" in relation to that case.  This then implies the 
permissions they should have in relation to that case.

Clearly, the notion of "group" makes no sense in this context, since the 
application does not grant permissions to all the people who have ever 
requested something.  Group is inherently placeless, or else you are 
talking about different groups.  I would have to refer to the "case X 
requester group" and the "case Y requester group", not the "requester 
group".  Role is inherently placeful, in that you must have a role *in 
relation to something*, even if it's the entire application.

This placefulness is inherently useful for developing complex 
applications.  Groups can also be useful, in that there are often groups of 
people to whom you would like to assign a role.  If for example I have a 
group that represents a department, I can assign them a role of Manager in 
relation to some set of things.  While it's true I'm "just" mapping from a 
set of principals to a set of permissions, the indirection is very useful 
because it allows me to extend or reduce the set of permissions granted to 
a Manager and have those changes apply whether the role is granted to an 
individual or a group, manually or programmatically (i.e. through looking 
at object-to-object relationships).


>A word on the context-sensitivity.  Any object in the object tree (or
>graph) *can* override the mapping between roles and permissions (via
>the security tab in the Zope management interface), and/or the mapping
>between principals and roles (via the "local roles" link in the
>security tab).  Most objects don't change the mappings, however, and
>inherit their parent's mappings, and the implementation is optimized
>for this case.

Speaking of the implementation, I hope that the local roles kludges do get 
fixed in Z3.  The Z2 implementation of local roles expects objects to 
expose a dictionary containing a set of roles for each user.  In many of my 
applications, computing a complete dictionary or even a complete set of 
roles for one user could be quite expensive at the SQL level, but a check 
against a single role or a set of specified roles is relatively cheap.

In order to make these cases work well I essentially had to monkeypatch 
Zope 2 with an altered local roles API; information about that patch has 
been sitting dead in the fishbowl for about 9 months now.  In essence it 
makes Zope ask "hasRole"-type questions instead of "getRole"-type 
questions.  There's really no reason (at least in Zope 2) that the security 
machinery needs to know all the roles a principal has, even against a 
particular object.  It needs only to confirm whether the principal has one 
of the roles that supply the permission needed for the current access attempt.

It is straightforward to write a "Case" object hasRole() method which, when 
asked whether the principal has the "Requester" role, checks its requester 
field to find out.  From there one can even chain to superclass hasRole() 
methods.   It looks something like this (before optimization):

class Case(Thing):

     def hasRole(self, principal, roles):

         if 'Requester' in roles and self.requesterName==principal.getName():
              return 'Requester'

         if 'Responder in roles and self.responderName==principal.getName():
              return 'Responder'

         return Thing.hasRole(self, principal, roles)


And of course somewhere 'hasRole' bottoms out to returning None.

Anyway, this is why it's important (IMHO) for roles to stay around, and for 
the API to change slightly in relation to role checks.  It should never be 
necessary for the security machinery to ask for a complete list of 
roles.  The scheme above still allows caching of roles and permissions to 
take place, it just may require multiple accesses before a complete cache 
is built.  I suspect however that in most transactions the number of 
different roles that a single principal needs to perform the required 
actions against a single object will tend to approach 1.  This is because 
in real applications, principals are usually carrying out the functions of 
their (usually single) role in relation to an object.