Forum OpenACS Development: Best Practices for permissions, straw man

I would like to start some discussion on how we should code permissions in new OpenACS applications. My starting assumptions:
  • OpenACS has a fully recursive, granular, etc etc permissions model
  • You only get to inherit this for free if you use acs objects
  • Even if you do use acs objects, you'll still need to check permissions from time to time
  • Before looking at what we do in specific OpenACS packages, I want to get a clear model that is internally consistent. Then we can adjust terminology to match what we use in openacs
  • Best Practices has two main parts: a system of privileges (action Y) and permissions (user X can do Y on object Z), and the "rubber-hits-the-road" aspect - that developers need to use the API to test these permissions where appropriate. I am starting with the first part, but I invite people to recommend/point to docs

Starting with one basic permission: "see other peoples' objects". (We could further differentiate between "aware of the existence of objects," "able to enumerate objects," and "see all information about an object" if we wanted to get pedantic.) This is the READ privilege. Someone who has been granted READ on a collection of objects has the "READER" role.

Next step:

  • add objects
  • see objects you added
  • edit an object you added
  • delete an object you added

This collection of privs, I propose to call "WRITE". Typically it would be bundled with READ in collection of privs I suggest to call "WRITER." (One could argue that we should only use WRITER to refer to someone with WRITE and nothing else ....)

Next:

  • see other people's objects
  • edit other people's objects
  • delete other people's objects

Lets call this collection of privs EDIT. An EDITOR role includes READ, WRITE, and EDIT.

Next,

  • The power to grant READ to other people
  • The power to grant WRITE to other people
  • The power to grant EDIT to other people

A MODERATOR is, I think, someone with READ, WRITE, EDIT (the power to moderate other people's work), GRANT READ and GRANT WRITE (the power to moderate other people's access).

The last step:

  • The power to grant any priv or group of priv, which implies also having all of those privs directly. This we call ADMIN.

References: OpenACS 4.x Permissions Tediously Explained,

Joel,

THe model already supports this, although not in exactly the way you are thinking.

We have CREATE, and WRITE. Write corresponds to your EDIT privilge.

READ does not currently inherit from WRITE.

They way to group permissions without adding a new privilege to the hierarchy is to create a role and a corresponding relational_segment and grant the correct permissions to that relational_segment.

The problem is that this is not handled well in the user interface.

In an applicaiton UI the role of Administrator, Moderator, Editor can be shown the the administrator. The admin can then add a new user in one of these roles. Internally the user is aded the to the relational segment, probably as part of an application group for a particular package instance.

So is your idea to change the way permissions are handled in the user interface or the way they are handled in the code?

Collapse
Posted by Tom Jackson on

Two points: we don't have roles in OpenACS, and privileges don't have fixed meanings.

No Roles: a role is a collection/group of rights that can be granted to a user, currently there is no way to group rights, they have to be granted one by one. Actually this is just done implicitly by each application. When I become a member of the main site, I implicitly can do a number of things. Bug tracker uses roles explicitly. I have used an owner_id in a data model to allow me to select rows. Query-Writer uses membership in a rel_segment to check if a user can assume a role (which is a list of actions on objects).

Privileges have no meaning: You can use the existance of any privilege for any purpose (as a developer).

Collapse
Posted by Tom Jackson on

Sorry, I mentioned the bug tracker package, actually I meant the workflow package, which is used by bug tracker.

Collapse
Posted by Jeff Davis on
Tom, grouping rights can be done by granting the permissions to a group and then adding a user to that group to grant the set of permissions (I think its equivalent to the way roles work on oracle for example). Of course as far as I am aware nothing exploints this (well dotlrn sort of does I guess).
Collapse
Posted by Dave Bauer on
Tom,

You can define roles as acs_rel_types between a group and a user. Then a relational_segment of all users which are members of a group in a certain role can be created.

Right now I think built in are membership_rel and admin_rel.
An application can define additional rel_types to create a new role.

You are correct there is no built in meaning to privileges, they are application specific.

The system is very flexible, in that it imposes very few rules on how it is used. There is a need for better examples of how it can be used.

Collapse
Posted by Tom Jackson on

Dave, a 'role' is not a sub-group, which is essentially what a rel_segment represents. A rel_segment is a group of users which have a defined relationship to a specific group. A group, in OpenACS is a collection of parties. A 'role' is a collection of rights on objects. We don't have an explicit 'role' in OpenACS. If you assign someone to an admin rel_segment of a group, they don't get anything from that. The application has to 'assume' a meaning of that membership type, implicitly creating a role. But there is no data model, no table where roles are defined in OpenACS.

I think workflow has it's own user->role map. My query-writer package uses rel_segments to place users in a role, but the role itself is defined in separate tables, separate from the application.

Collapse
Posted by Dave Bauer on
Tom,

You are correct. There isn'y anything specifically called "Role" in OpenACS. And a relational_segment does not have built in permissions.

To use it to define a role, you grant the correct permmissions to the relational segment.

Perhaps I left that part out. I have used this to create several different roles in a project management site. So to the administrator, the user iterface says "Add a user as Administrator or Add user as a Staff member" and the application defines the relational segments on each group, grants permissions to the relational segments, and adds or removes users from the relational segments.

My point is that this makes much more sense to users of OpenACS applications so instead of redirecting an admin to the acs-subsite built in permissions page, an alternative would be to build a user interface around the concept of roles.

Its only one model that can be built in OpenACS. Of course even the improved interface with checkboxes for each privilege is a great improvement over the old page.

Collapse
Posted by Dave Bauer on
Hmm, I totally didn't answer Tom's point about roles.

I see the difference here.

Joel is looking for a way to define which permissions as assigned as a role.

Tom and I have both stated that you can do this yourself, be building an application that grants the permissions to users with a certain relationship to a group, but there is no facility to define several permissions that go together to create a "role".

Joel seems to be advocating openacs sitewide role definitions instead of the current system where each application defines and grants its own permissions.

Collapse
Posted by Don Baccus on
Well, we've talked about using standard system privileges within packages and getting rid of custom privileges except when it is really necessary.

My main problem with Joel's proposal is that we already have standard system-wide privileges and if we diddle their meaning, or create new ones to replace the semantics of old ones, existing code will break.

Including code written by folks for customized sites.  As usual, unless there's an extremely good reason to do this, we shouldn't.

Adding new system-wide privileges that don't interfere with existing ones is a different story.  For instance a "moderate" privilege which groups READ, WRITE, EDIT.

We've talked about implementing a group/role style admin UI to simplify management, using rel-segments and permissions under the hood, as Dave suggests.  At one time Lars and I had talked about the workflow role stuff being implemented the same way, but he's stayed with the role/user mapping idea instead.

Collapse
Posted by Tom Jackson on

It is probably best for me to give an example of what I think is the distinction between a role and a privilege (or the sum of a user's privileges).

The easiest example of the difference is that we don't have an explicit 'create' privilege in OpenACS. How can we, privileges apply to objects, and you can't have a privilege on a object before it exists. So the 'create' privilege (which I would call a single element of a role) is usually assumed by the existence of another privilege on another object (or more than one object in some cases). The point is information, which makes up part of a 'role' isn't in a database table, it is implied by the UI.

Another example is the 'update' privilege. Privileges are based on objects. However the UI defines/enforces several roles. Most packages have a 'user' mode and an 'admin' mode, or an admin area. Usually the admin area offers greater access to all the attributes of an object. The admin area may not even check the actual permissions on an object, being able to access this area is enough proof that the user has assumed the admin role on the package.

Collapse
Posted by Don Baccus on
We could rename the privilege "CREATE SUBOBJECTS" ...

I think we're splitting semantic hairs here ... do roles have to be explicitly modeled in the datamodel to be implemented in the toolkit, or is a standard UI and API sufficient?

Are you suggesting we add a separate role facility in addition to permissions?  I'm cold to that idea because the permissions systems is flexible enough to provide the foundation for a role facility via API and UI and does much more, and I don't like having parallel mechanisms for accomplishing the same thing.

Collapse
Posted by Tom Jackson on

Don, I'm not suggesting creating roles. I was just pointing out that roles are something that is handled implicitly by the UI. That actually makes roles very flexible. A nice way of assigning a role is to create a rel_segment. You don't even necessarily need to add privileges to the rel_segment, simple membership can be enough to assign a role in certain situations since the UI is doing the work.

Collapse
Posted by Don Baccus on
Ahh, OK, gotcha ...
Collapse
Posted by Joel Aufrecht on
I may not have been sufficiently clear about my goal. I'm not proposing any changes to OpenACS. My sole goal in this thread is to produce fodder for a "How to Code Permissions in a 5.0 Package" chapter for the Engineering Standards section of the documentation. While we have documentation about how the permissions model works, we don't have instructions on how to use it. I'm also not (yet) aiming for tutorial-level detail.

My plan for doing this is:

  1. Describe a permissions model in sufficient detail that it makes sense and people can tell what I'm talking about
  2. Solicit terminology corrections so that my model matches what's already in OpenACS
  3. Adjust the recommended permissions model so that's it's a useful default model, including looking at non-OpenACS best practices to avoid re-inventing the wheel
  4. As a group, figure out the best way to implement the generic permission system in a new package, including one which uses acs_objects/cr_items, one which doesn't, and one which uses a blend. This task has several parts
    1. Figure out how to get maximum benefit from the toolkit with minimum new code. (Example: If I make the core table of my package an acs_object table, but add no other permission-specific code, how complete a permissions system do I get? What's missing, and how do I put it in? This is tutorial-level stuff)
    2. Figure out best practices for implementing permissions. (Examples: recommend how to set up rel_segs to group privileges, and *which* rel_segs with *which* default names to use to provide the generic model; cull the API for things we should deprecate; etc)

So, I think we're somewhere in step 2. Let me summarize what I think I've heard:

  • What I called EDIT, the power to change other people's objects (which includes constitution privs for seeing, changing, and deleting), is currently called WRITE. So where I proposed READ, WRITE, EDIT, we have READ, ??, WRITE.
  • A privilege is just a label - it does not have any intrinsic code or refer to any subjects or objects. When a priv is associated with a subject (a party) and an object (an object), the resulting record is called a permission.
  • Privs can be bundled together, so that three or four privs can all be assigned or revoked as a group. This is done by creating parent-child relationships between the group priv and the individual privs in acs_privilege_hierarchy. Thus, a priv can be a leaf or a node in a tree, but it's still just a collection of labels. (E.g., "create" could include "create forum," "create message," and "create comment," but that's literally all it is - three names. If a function tries to determine if party X can "create comment" on object Z, and party X can do "create", then party X can also do "create comment.")
  • Instead of assigning privs to an invididual person, you can instead assign privs to a group. (I'm less sure on this bit than on the others - please scrutinize.) Group is a node in the party hierarchy, so you can add and remove users and groups of users to groups. You could thus make an "editor" group and a "moderator" group with mostly non-overlapping permissions and put some people in both groups, or even put some people in a "super" group which is in both the "editor" and "moderator" groups.
  • An example of everything so far:
    • create a bunch of privs and create a parent priv called "edit"
    • create a group called "Editors" within your new package,
    • make a permission record that says "Editor has priv 'edit' on object '$package_id'"

Before looking at implementation issues, like Tom's point that an "add" priv must apply to a parent object since it can't apply to an object that doesn't exist yet, I want to bang at a few more details in our abstract "best practices" model:

  • Do we want to recommend a distinction between "read an object" and "be aware of the existence of an object"? One example is, you can often see subsites listed which you don't have the permission to enter. One solution is that, if you can't read/enter something, you shouldn't be able to see it on lists. The drawback to this is that sometimes you want people to see things so that they see how to ask for access. And sometimes you don't. So I see three choices for best practices:
    • If you don't have "read", you shouldn't see something in lists, and it shouldn't show up in counts
    • If you don't have "read," you can't drill into something, but it's still in lists and counts
    • There are distinct "read" and "see" privs. Showing a list of available subsites hinges on the "see" priv; clicking on that subsite and seeing the contents is requires "read" access (this is an illustrative example, not a suggestion to recode acs_subsite).
  • So what's the correct priv tree? My updated straw man:
    • "see own"
    • "read own"
    • "add"
    • "edit own"
    • "delete own"
    • "see other"
    • "read other"
    • "edit other"
    • "delete other"
    • 9 more privs of the form "grant X"
    • "read": "see other" and "read other"
    • "create": "add", "see own", "read own", "edit own", "delete own"
    • "write": "see other", "read other", "edit other", "delete other"
    • "creator": "create" and "read"
    • "editor": "read" and "create" and "write"
    • "grant 'read'"
    • "grant 'create'"
    • "moderator": "editor", "grant 'read'", "grant 'create'"
Collapse
Posted by Tom Jackson on

Joel, the problem of just adding terms, new privileges, sub-privileges, whatever, is that it doesn't solve any issue, and in fact, makes things worse. If 'read', 'write' and 'admin' have camellian like properties, subdividing these isn't going to help anything. The UI determines the meaning of this data.

Here is my analysis of the situation, expressed in code in my query-writer package.

An object is composed of attributes, so I divide up objects into attributes. You can only do a few things to object attributes: create, update, view. You can also delete an entire object, but I view this as delete of the object_id, so delete only applies to the object_id attribute.

To control access to an object I create roles. Actually in query-writer two roles are 'assumed': users who have admin on the security root context, are in the admin role, otherwise they are in the default role. Otherwise new roles can be created by creating a rel_segment and placing users in it. But what a role is allowed to do, is defined outside of the acs object system, it is part of query-writer. So first query-writer picks a role, then validates that a request to create, update or delete object attributes is possible within the role. Once this validation passes, for updates I require 'write' permission on the object, and for delete I require 'admin'.

Also certain roles can be limited to certain value choices for an attribute. For instance if an object attribute was 'publish_p', you might want to limit setting the value to '1' to the 'publisher' role. Although both the writer and editor would have write access to the content related attributes, they shouldn't have access to other metadata attributes.

I'm sure the workflow package addresses the same issues because it allows variable access to objects depending on a user's role defined in the workflow. But in the end this is necessary when you want to auto generate variable access to objects. If you can hand write the UI, you don't need a formal model to follow, and requiring one might be counterproductive.

Collapse
Posted by Don Baccus on
I think one way to get a handle on what generic permissions we should define and how to define their semantics would be to survey a bunch of existing packages.  Some of them go way to far with the granularity the provide (OF tended to go that direction IMO).  Others don't.  But at least you can get a handle on what kind of access problems they were trying to solve.

Your GRANT privs currently are implemented using the single ADMIN privilege, and I personally have never run into the need for greater granularity.  I realize RDBMS security models tend  to provide an extremely granular solution but IMO these also tend to be more complex than we need - and I'd argue more complex than they generally need, too.

Tom's discussion regarding restricting actions on attributes could easily be solved within the permissions system if we made attributes objects.  I'm not suggesting we pay that implementation cost, only that Tom's solution to this problem is made necessary by a compromise in the permissions/object model.

Collapse
Posted by Tom Jackson on

I wouldn't go so far to say that my solution was necessary because of problems with the permissions/object model. Permissions apply to individual objects, or if attributes were objects, they would apply to individual attributes. Roles apply to object types. In most applications, except workflow, roles are implicitly defined by the ui. The UI gives meaning to the permissions. You can add privileges and subprivileges all day long, but that doesn't do anything about defining a role. A role exists before individual objects are created and continues to exist independent of individual objects. But when you remove an object, you have to remove permissions first.

Collapse
Posted by Don Baccus on
Well that raises the old issue of proxy objects for types (since types aren't objects and making them so at this point would be difficult).

The person who began work on general ratings and I came up with this idea when we started discussing the notion of wanting to be able to say "this party can administer any rating of this type".

Collapse
Posted by Malte Sussdorff on
I think we should have a *simple* modell which should make it into the coding standards and annotations on how *best* to extend it.

I do want to have clarity how read, write, edit, create, delete, admin should be used. Furthermore, if we want to support inheritance in the basic model (create implies write) we should make that clear as well.

Therefore I really like the original modell and maybe we could agree to make a TIP out of it for best coding practices.

Collapse
Posted by Joel Aufrecht on
Moving forward with step 2: Solicit terminology corrections so that my model matches what's already in OpenACS.

The big glitch so far is in permission X, the power to add items and edit and delete the items you have created, and permission Y, the power to edit and delete other people's items. OpenACS calls X "create" and Y "write." I think that "write" is bad in that it doesn't convey the essential difference between X and Y, which is that X applies to your stuff and Y to other people's stuff. My current suggestion is "write" and "edit," but I don't think that's sufficiently better than what we have to merit any refactoring. In fact, it probably makes more sense to just go along with "create" and "write."

And moving forward on step 3: Adjust the recommended permissions model so that's it's a useful default model.

This is my current straw man best practice. By "best practice" I mean a description of how to implement permissions in a typical new OpenACS package, plus code examples. My requirements are:

  1. New developers can easily understand it
  2. It makes is easy/trivial/automatic to implement common permissions functionality in a new package
  3. It is easy to extend to more sophisticated permissions schemes where needed
  4. It is easy to handle special cases

The straw man, second draft

In OpenACS, a permission means that a specific user can accomplish a specific function on a specific object. For example, a user #222 can look at a page which contains a list of widgets. Each place in the code that you provide functionality that shouldn't be available to everybody all the time, you need to perform a permissions test. OpenACS uses a standard set of tokens, called "privileges," that are generic enough to apply to most functions.

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

This seems very straightforward, until you consider: exactly which object are you testing, how did it get its permissions set, how do you set permissions on objects, and how do you determine which privileges a specific user should have? (notes on assigning privileges to users via groups deferred for a later post)

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:

  • 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).

This is the direction I'm going with the permissions tutorial. Remaining topics are, checking permissions within ad_form (four checks, I think, one each for new_request, edit_request, new_data, and edit_data) and checking permissions on delete. Before I finish it at this level, I'm soliciting feedback.

  • Is this how you do it in your packages?
  • In a lot of places we don't even use permissions per se but just use acs_object.creation_user as an implicit "admin" priv. This is at least minimally acceptable because we can still hide it within the permission API via via permission::write_permission_p, which returns true if a user has "write" priv or is the creation_user. Do we want to solidify this approach as a best practice? Or push towards explicit priv granting on objects? Or explain a tradeoff?
Collapse
Posted by Tom Jackson on

I guess I should say again that privileges don't have an inherent meaning. This is a good thing. For best practice, it depends entirely on the application. In general the best practice is to use privileges efficiently to achieve the desired level of security. 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, and you cannot assign privileges until after an object is created. Efficiency means using one privilege as a proxy for multiple permission checks. For instance whenever you visit a package, the request processor already checks the user's ability to 'read' the package_id. Therefore adjusting the context_id of a package can allow easy control for an entire package UI. I think a best practices document should explore various use cases and demonstrate how to use privileges. When folks discover new uses for the permissions system, they can be added to the list.

Another underexploited idea is the use of roles. An explicit role system doesn't exist in OpenACS, this is analogous to the fact that the meaning of privileges is defined in the UI, not in the database. Using relational segments to serve as a container for members of a role is one way of modeling roles. The actual meaning of a role will still be defined in the UI, but 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. The advantage of using roles instead of additional privileges (like 'create' or 'forum_create'), is that in the case of using privileges you get weird artifacts such as having 'forum_create' on your photo album folder.

Collapse
Posted by Dave Bauer on
Joel,

I would change it like so:

# 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 or delete records check for the "write" privilege. (is there also a delete privilege?)
# For functions that grant permissions to other people, check for the "admin" privilege.

Packages should be explicitly setting permissions on objects. Some packages assign The Public or Registered Users permissions by default. For example, file-storage grants write to Registered Users by default. This could lead to unexpected behavior if a package is mounted under a subsite. It probably should at least default to the application group of the closest subsite (which is equivilant to registered users for the Main Subsite, i believe.)

Packages should be checking for read permission on individual objects. I don't think there is a performance penalty for displaying a reaonable list of objects generated in a single query.

A problem is shown in the search package which effectively runs a permission query for every object which is not efficient enough to use even on a small site.

Collapse
Posted by Dave Bauer on
Tom I agree with all your points except the one about checking for relational segment membership. If I use relational segments to define roles, I still grant permissions to the relational segment and check for the permission on a object for the party_id, usually [ad_conn user_id].
Collapse
Posted by Don Baccus on
# For functions that create records, change records, or delete records, test for the "create" privilege.
# For functions that alter or delete records that belong to other people, check for the "write" privilege.
Joel, Dave's modifcation above already addresses my concern with the above but I want to make a point to you in order to make sure you understand why the above is not a good idea.

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.

In your suggested practices, you suggest we check a different permission for the "delete" action depending on whether or not the object "belongs" to the person doing it or not. This breaks a fundamental design element of the permissions system, because you have to check the history of the object then check for one of two permissions. This is a big no-no.

Of course in addition there is already a "delete" permission and we should continue to make use of it, I think, in order to be consistent with existing usage.

Tom, I agree with Dave regarding roles. And I don't see how you end up with a "forums_create" privilege on a photo album. Why would the forums package grant "forums_create" on an album??? How would it even know of the existence of photo-album?

(of course anachronisms like forums_create need to go away, hopefully in 5.1, another of my pet projects that have been on the back burner...)

Collapse
Posted by Tom Jackson on

Site wide admins, for instance, have every privilege on every object. If 'forum_create' is a privilege, the site wide admins have that privilege on every object. It doesn't make sense, but that is join magic at work.

I've pointed out before that roles are needed for fine grain control of what users can do to an object. A rel_segment is not a role (IMO), just a container for who can play a role. The role is defined in the UI. Even if you go to the trouble of granting 'write' on object '1' to a rel_segment, this doesn't define or control what users who are members of the rel_segment can do to the object.

As an example, say we have an news application, for simplicity, just one instance. We create a group G, with rel_segments 'writer_rel', 'editor_rel', 'publisher_rel'. If a user has a writer_rel to the group G, they can create new 'news' objects. Writers and Editors can make changes, but only Publishers can make the news go live.

If you have two instances of the news package, you might write it so that new news items have the context_id set to the package, and assign permissions on the package_id of the news package (that is assign read, write, admin to users on the package_id), not on the relational segments. So that even though all Writers, Editors, Publishers might have the 'write' permission on all the news items, each can only do certain things to the items.

Collapse
Posted by Don Baccus on
It's not join magic, it's the fact that "forums_create" is a child of "admin".  But I would ask that you not bring site-wide admins into the discussion, as this is a special case and does nothing to help us with more general design issues.

The workflow case you mention above is, well, workflow.  Fine-grained workflow permissions combined with object state information could implement what you're talking about but Lars chose to do it using roles, I think?  That's how old acs-workflow worked, I haven't looked at the new one, yet.

I guess I don't see your point, though.  I could implement workflow with roles via perms and I challenge you to come up with a role scheme that can't be addressed by combining permissions and workflow.

We don't need to add another mechanism to handle roles ...

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 ]

Collapse
Posted by Don Baccus on
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.
Naw, "create" is just shorthand for "allow the creation of a child object" ...

How is this different than the write privilege on a Unix directory, which says "you can write this file, and that includes adding a child file or deleting a child file"?

This is my entire point. Permissions do not have an inherent meaning, but derive meaning by how you use them in the application.
Of course. The whole point of developing a coding standard for permissions is to make the use of permissions consistent because the permissions system does not define semantics.

Are you suggesting it should? If so, make a solid suggestion. I'm personally comfortable with the fact that it doesn't, because in practice permissions are simple and easy to use and have proven to be both flexible and (with some tweaking on my part) efficient.

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.
Create two container objects, which if you're using the content repository would be of type "content_folder" and put the create child permission on that object.

If you're going to segment content between two user communities like this doesn't it make sense to reflect that in the hierarchical structure of objects you build?

Collapse
Posted by Tom Jackson on
Create two container objects, which if you're using the content repository would be of type "content_folder" and put the create child permission on that object.

So are you saying stick another layer of objects between the parent and the child.

How is this different than the write privilege on a Unix directory, which says "you can write this file, and that includes adding a child file or deleting a child file"?

My point exactly, it isn't different. Unix doesn't have a separate create privilege. In this case, as you say, the directory is a container. If you are going to create separate objects just for permissions, you don't need extra privileges.

Are you suggesting it should? If so, make a solid suggestion. I'm personally comfortable with the fact that it doesn't, because in practice permissions are simple and easy to use and have proven to be both flexible and (with some tweaking on my part) efficient.

I've said exactly the same thing a few times in this thread already: the flexibility is a good thing, it is efficient to use a proxy, usually parent permissions. My only suggestion is to remove the create privilege, and I've made that suggestion pretty solidly. However, I agree that it can be useful. Agreeing with this, I would have to ask: why not others? Why not a delete privilege?

I'll drop the attribute level discussion, since it appears to be off topic.

Anyway, I must have missed the whole point of the discussion, as usual. The read priv is for reading, write for writing, admin for admining and create for creating. It really would take a numbskull to mess this up.

Collapse
Posted by Don Baccus on
There is a "delete" privilege.

Now ... why a "create"?  It's already there, with proper documentation it should make sense to people, and I can't think of any reason to remove it.  I mean ... this would be a very interesting discussion if we were starting from scratch but we're not.

Collapse
Posted by Don Baccus on
Also if we give "create" on a folder that would allow someone to create files under it without having the right to change the folder's name etc.

I mean ... it could and that could be useful I suppose.  Offhand I don't know if the CR or any of its clients (file storage) do that, hmmm!

Collapse
Posted by Carl Coryell-Martin on
My vote is that I would check for WRITE permissions on the parent object before creating a child rather than a separate CREATE.

-carl

Collapse
Posted by Jeff Davis on
Carl, how would you propose we deal with the situation Don mentioned where you want people to be able to add files to a folder in file storage but not edit the folder itself?
Collapse
Posted by Joel Aufrecht on
"How is this different than the write privilege on a Unix directory, which says 'you can write this file, and that includes adding a child file or deleting a child file'?"

If create and write are the same thing, how do we differentiate between permission to change your own objects and permission to change other people's objects?  It seems to me that the way it should work is:  Alice has the create privilege on object #1, and this (is tested for in a UI which ) allows her to create object #2, which is in some sense "inside" object #1.  (Obvious tangent that we've touched on before and which I want to come back to later: is Alice explicitly granted some privileges on object #2?).  Meanwhile, Bob has the write privilege for object #2.  This means that Bob can delete or edit object #2.  It does _not_ mean that Bob can allow other users to delete or edit object #2.

Do I have that example right?

Collapse
Posted by Don Baccus on
Yes, I think so ...