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

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.