Forum OpenACS Development: Re: Best Practices for permissions, straw man

Collapse
Posted by Joel Aufrecht on

(I think I've incorporated everybody's feedback into the third draft. If you made a comment that you think should be reflected in the third draft and isn't, I'm sure I don't have to ask you to tell me. Several things I want to tackle directly to make sure I understand:

"Adding a create privilege is unnecessary, and doesn't even make sense in the context of the OpenACS permission system, since privileges apply to objects." I think the answer is that we do have a create privilege, and the trick is that, when you want to create an object, you have to figure out what the "parent" object should be and test for "create" on that object.

"Instead of checking if a user has the 'admin' or 'create' privilege on an object, you could check if the user belongs to a relational segment, that is, the user has a certain relationship with a group." I thought that the point of the permissions system was that this is all behind the curtain from the perspective of permissions testing. My assumption is that for UI we always test with user_id, and if the user ('belongs to a group which ...' or 'belongs to a rel_seg for a group and that rel_seg ...') has been granted a priv on an object, then permission_p sorts that out automagically. Is this correct?

"The notion of the permission system is that I can take a party_id, object_id, and permission and check without any additional effort whatsoever." Did you mean, take a party_id, object_id, and privilege?

One more caveat: I'm not proposing any changes to anything in OpenACS. I'm trying to understand OpenACS 5.0's permissions system and document how to use it in a simple package. If this inspires you to imagine improvements to the system, please do so in a different thread :)

The straw man, third draft

We can divide permissions functionality into two parts: granting permissions and testing permissions. First we'll tackle testing permissions.

Suppose a user is trying to look at a page which lists some widgets. Before we execute the code that makes an HTML page with a list of widgets, we need to answer the question, can user #222 look at widgets #1000 through #1100? We answer the question with a permissions test. A permission test comprises three parts: the "party," the "object," and the "privilege." In this case, the party is user #222, the object is the group of 1100 widgets, and the privilege is "read." We could make a record in the database for each possible combination of party, object, and privilege, but for performance and management reasons we use a lot of shortcuts. This guide will describe the full logical constructions and also identify shortcuts where appropriate. In practice, most permissions tests use some kind of shortcut.

Let's look at privileges for a moment. The basic privileges in OpenACS are read, write, create, delete, and admin. These privileges don't have any intrinsic functionality; they are just a list of words. Their meaning comes from our conventions about which privileges to check when doing different functions. In general:

  • For functions that display records or lists of records, test for the "read" privilege.
  • For functions that create records, test for the "create" privilege.
  • For functions that alter records, check for the "write" privilege.
  • For functions that delete records, check for the "delete" privilege.
  • For functions that grant permissions to other people, check for the "admin" privilege.

This seems very straightforward. But consider: if you want to create a new object, how can you have "create" privileges on it before it exists? And what if you want to create a record that is not an acs_object? The answer is, you have to think hard about which object is appropriate for any permission test.

Let's work through the basic case, a package with a single table of content repository records (see the tutorial for more information). In this package, we need to test for permissions as follows:

  • Does the user have permission to access the UI at all? This is done automatically by the OpenACS request processor. When a user requests a page in a package, the request processor tests for the 'read' privilege on the package_id. Since your new package inherits permissions from its parent, and its parent is an acs_subsite, and a default acs_subsite has "read" permission for "The Public," everybody in The Public will pass this test. (Where is The Public defined? How can I inspect it in the UI?)
  • NOT UPDATED PAST HERE YET:
  • On the index page, which lists all of the records, we do not test for permissions because we want this list to be visible to everyone. If we wanted to restrict access to a specific group, we would grant a permission: permission::grant -party_id 'specific group's id' -object_id 'the package id' -privilege read

    To test this permission, we put the code permission::require_permission -object_id 'the package id' -privilege read at the top of the page. We can omit the party_id, which will then default to the current user id. If we just want to make sure that the viewer is logged in - which implies that they have a valid account - we can skip permissions altogether and just put auth::require_login at the top of the tcl page.

    Note that this is "coarse" permission checking - to show a list of all records in the package, we just check once, at the package level. If we want to list only records which the user has direct "read" access on, we will need (advanced topic goes here, re: both granting and testing per-record, and performance issues).

Collapse
Posted by Tom Jackson on
I think the answer is that we do have a create privilege, and the trick is that, when you want to create an object, you have to figure out what the "parent" object should be and test for "create" on that object.

Btw, the 'trick' is called 'ad hoc'. It isn't a trick, it just points out the fact that one pemission can be a proxy for others. This is my entire point. Permissions do not have an inherent meaning, but derive meaning by how you use them in the application. You could just as easily require 'write' on the parent object in order to create child objects. But please explain how you are going to handle the case of two different types of child objects. One should be created by one group of users, the other created by a second group of users. Some users can create both. The single 'create' privilege on the parent object is no longer enough. If you, or Don can explain how to do this with permissions, without creating new privileges, please explain.

The permission system doesn't support roles, that is you can look in the database all day long and never figure out what role a user plays, or could play. All this is coded in the UI/application layer. Unfortunately it has limitations if you just use the permissions data model.

Also, you will see the limitation of the permissions model when you consider how users can create or modify objects. It is useful to prohibit users from modifying or setting certain attributes of an object. One create/modify permission cannot control or specify this behavior. To an extent a static UI can help. In OpenACS there are two 'roles' which are statically supported. This is the regular user and the admin user. The admin user has access to the package admin directory, thus allowing greater access to the object attributes. Sometimes the admin functions are placed in the regular user section of the package, but they don't work or even show up unless the user is an admin. These are static roles defined in the application layer. One permission is used as a proxy for greater control over a number of objects and attributes. It is efficient and useful.

The only place I know of where roles are explicitly defined is in the workflow package. And why does it use roles? Because they were necessary to generate a UI dynamically. Whenever you move to a dynamic UI, you must use roles.

Look at it this way: a permission is just data, one tiny piece of data. There isn't a lot of entropy there, so you can't expect it to do too much all by itself. If you have a simple situation, you don't need much entropy. But, to all the scientific types out there, complex systems require more data to represent them. The only question is where is the data going to be stored, and in what format. The permissions data model is unsuited for this in its current form because it requires the existence of the object. Otherwise you have to infer meaning using an ad hoc method from other permissions. As long as you have two roles this works well most of the time when combined with the UI conventions.

Collapse
Posted by tammy m on
"Adding a create privilege is unnecessary, and doesn't even make sense in the context of the OpenACS permission system, since privileges apply to objects." I think the answer is that we do have a create privilege, and the trick is that, when you want to create an object, you have to figure out what the "parent" object should be and test for "create" on that object.

It depends on what your object is and its intended use if create should be assigned to it, since privileges have no inherent meaning until one is enforced via the UI (or API depending how the corresponding object is accessed). Like with CR folders, create privilege assigned on the folder object can be taken to mean a given user/group has permission to create objects/documents in the folder.

"Instead of checking if a user has the 'admin' or 'create' privilege on an object, you could check if the user belongs to a relational segment, that is, the user has a certain relationship with a group." I thought that the point of the permissions system was that this is all behind the curtain from the perspective of permissions testing. My assumption is that for UI we always test with user_id, and if the user ('belongs to a group which ...' or 'belongs to a rel_seg for a group and that rel_seg ...') has been granted a priv on an object, then permission_p sorts that out automagically. Is this correct?

You can check for permission with any party_id which means group or user ids. permission::require_permission [ -party_id party_id ] -object_id object_id -privilege privilege

I think what was being said was that the flexibility inherent in OACS perms system lets the developer decide (via her access UI/API) how fine grained of permissions she needs enforced. You could check via the permission::require_permission call if the current user requesting to perform an action has explicit or implicit permissions set (via direct grants, grants from some group they belong to, or from inheritance up the context_id hierarchy). This gives you pretty tight, explicit control over the permissions on the object (especially if you turn off context inheritance since this sometimes inherits more perms than you want by default -- or explicity set your "top" level context object for your app to the package_id or a custom object whose perms you have dwindled down to a restricted set).

If you don't require really fine grained perms (don't have a lot of different actions on your objects with unique sets of those actions being granted to each user), you could just use membership in a subgroup (rel_seg) to mean that permission to do X on this object is implied. Theoretically, verifying membership in a group is faster than running up the explicit perms, implicit perms, context inheritance hierarchy. Though the group membership setup is a hierarchy too I believe. But likely more shallow than the perms on a given object. True? I'm just throwing out what I've gleaned trying to grok this myself!

For functions that grant permissions to other people, check for the "admin" privilege.

I would only add that the admin privilege implies you also have read/write/create/delete since you can grant any privileges you want.

Where is The Public defined?
openacs-dev=# select * from groups ;
 group_id |         group_name                 | join_policy 
-----------+-----------------------------------------+-------------
       -1    | The Public                                 | open
       -2    | Registered Users                     | open
     7417 | Personals (7316) App Group | open
     5498 | Authors                                      | open
(4 rows)

openacs-dev=# select * from rel_segments ;
 segment_id |   segment_name    | group_id |    rel_type    
------------+-------------------+----------+----------------
       2114 | Main Site Members |       -2 | membership_rel
(1 row)
Also, I think this is from Don B. in a post somewhere else... "'the public' is just the group 'registered users' plus user 0, which is what ad_conn user_id returns for users who aren't logged in."

How can I inspect it in the UI?

http://your.openacs_server.com/permissions/one?object_id=-1

Something neat I noticed... If you go to SiteMap and click on "Set Permissions" for your package instance (application) or folder, you can see permissions (privileges) that are inherited by your application (via its context object hierarchy) and those directly set on your application via direct grants to users/groups.
I think the Children displayed here refer to children via context inheritance.
You can also tack any object_id on the URL http://your.openacs_server.com/permissions/one?object_id=-1 and view it's privileges and inherited privileges there.

NOT UPDATED PAST HERE YET: On the index page, which lists all of the records, we do not test for permissions because we want this list to be visible to everyone. If we wanted to restrict access to a specific group, we would grant a permission: permission::grant -party_id 'specific group's id' -object_id 'the package id' -privilege read

To test this permission, we put the code permission::require_permission -object_id 'the package id' -privilege read at the top of the page.

Or I think it's also valid if we have created groups that we've decided implicitly define a specific role, we could just test for group membership. group::member_p [ -user_id user_id ] [ -group_name group_name ] [ -group_id group_id ] [ -cascade ]