Forum OpenACS Development: Permission system in needs of revisit?

Our permission system works fine, no doubt about it. Yet I have seen two additional approaches which made me curious if we should not revisit our permission system and make it "do more".

Szenario One: Function based permissions.

At the moment OpenACS has object driven permissions with a given set of standard "functions" like "read,write,delete,admin". This does not work though if you need permissions based on functionality.

Example: I want to give access to a list column depending on the users permission to see this column regardless of object permissions (e.g. he has to have object permissions for the object to show up on the list, but he needs the functional permission to see the column).

Szenario Two: Profile based permissions (https://openacs.org/forums/message-view?message_id=291499).

A role of users (e.g. employees) should have access to a set of objects.

Example: All employees of company X should be able to see the profit column of all customers' projects while the customers employees should only be able to see their own projects and not the profit column

Szenario Three: Object Type permissions

A financial manager is able to edit all payments (object type) regardless of the actual object. He is not though able to see the project information for all customers (otherwise we could just give him write on /). The accountant for a company is able to see and edit all payments of a given company.

Obviously all of these could be managed with object based permissions and clever setting of context_ids and groups. But maybe we should take a look and revisit our way of handling things and allow for an extension?

P.S.: I know that PO and AIMS have solved these issues already, so it is not a "we need to do so much new coding". It is more a decision if we want these additional features and put them back into core or not.

Collapse
Posted by Dave Bauer on
Malte,

If the problem is already solved :) and is necessary for your custom situation why not use it?

Perhaps we can get some links to code examples or design documents that reference the implementations?

XoWiki also has some idea of how to do this as well doesn't it?

Collapse
Posted by Malte Sussdorff on
Dave, I don't need it in my custom solution. I just use AIMS / PO out of the box. But I don't assume that the szenarios above are only for the selected many that use ]po[ but are also useful for a wider OpenACS audience 😊. Let's just say that I am trying to spark discussion around it to improve the overall toolkit. And the fact that we have various permission extensions show to me that the stock permissions are not useful for more than just one szenario.

]po[ Permissions: http://www.project-open.org/doc/intranet-core/permissions.html

XoWiki Permissions (based on rules): http://media.wu-wien.ac.at/download/xowiki-doc/#security-management

I will see what I can get for AIMS permissions.

Collapse
Posted by Don Baccus on
I get really discouraged when I read stuff like this:

We have prefered the the use of these "admin groups" as oposed to using the default OACS permission system because we have to aks frequently: "Which users are member of this project". This type of query is disencouraged in the OACS permission system.

The OACS permission system wasn't designed to answer that question, nor is there any reason on God's good earth as to why it should.

We provide groups and rels to answer that kind of question, and so, how does Frank solve it?

Currently (February 2004) we only use the "membership_rel" relationship and the "admin_rel"...
Good boy! Frank uses rels to solve the problem rels were intended to solve, rather than try to use the permissions system for something it wasn't designed to solve (though he seems to think it should've been solved).

This kind of muddled thinking brings back memories of long discussion threads with Frank about permissions.

I'm pretty sure you could implement the PO stuff with permissions on rels/groups/etc though if you do really have 100,000,000 objects and 10,000 parties, the denormalized index will become very large. That's not the space most of us are working in, though, and bolting on another permissions system isn't going to make the existing permissions go away ...

If I were to write up a top-ten list of "what's wrong with OpenACS", permissions would not be on that list.

Collapse
Posted by Tom Jackson on
One thing which vastly complicates the use of permissions is providing generic interfaces into the object system. If an object can be created in one application but displayed in a completely unrelated one somewhere else, then you have to add permissions for two cases.

If you avoid this situation, you can use proxy objects, and the best one to use is a relational segment, for instance 'Main Site Members' is a relational segment. You can create your own, very specialized segments and I don't think they explode the mapping tables like adding a privilege subtype. (Is this correct?).

Another great example of using a proxy object is the /admin directories. Directory access triggers special checking, so you don't have to add additional permissions to every object under /admin.

But the premise of this thread is that there is some limitation on what an object and/or permission represents. It is incorrect to say that permissions do not, and cannot apply to functions. Every application page which checks for permissions is making an assertion: if you want to apply the following functions, you need a certain permission. The fact that it is on a tcl page and not inside a procedure doesn't mean you can't do a check inside a proc.

If the check isn't inside the procedure it will be extremely difficult to force the check, and you are back to the problem with generic access to objects. If you want to access every object through the same generic interface, you are essentially ignoring the application data model. This is why I have never come up with a generic select function for query-writer: select creates new objects, either as a combination, or devoid of the context in which they are useful to the application. Specifying permissions to handle this in a generic way may not be impossible, but serves no benefit for the creating application. I have yet to see a generic interface for accessing objects which does anything interesting, unless simple display is interesting. It is no wonder that permissions would appear to be a problem in such a system (there is no design going on folks, just rows and rows of stuff).

Query-writer handles both 'profile' type permissions and 'object type' permissions. These are all bundled up in a role, so one role can span multiple objects types. A profile is like a filter. In query-writer, these can be set beyond the attribute level. For instance, you could have an object forum_message with attribute approved_p which has two values '1' or '0'. You could either disallow a role from setting this attribute, or not let them set it to '1'. You could allow a user to create the forum_message object, but disallow them from deleting it. An admin role on the same object type could delete it or set approved_p to '1'. Another overlooked use is to eliminate access to administrative columns like those going into the acs_objects table: user ip, who created or last changed the object. Generic interfaces do not easily handle the distinction.

Since it has been done, it can be done, and it didn't require any changes to the permissions system. And it is vastly more efficient to assign a user to a rel_segment and check this for each operation (just to see if they can perform the role, not individual checks) than to fill up the permission system with rows and rows of who knows what, and still have a system which can go around all of it. Ahh, heck, in query-writer you can go around it too, but it is orders of magnitude easier to use the system and not go around it.

Collapse
Posted by Malte Sussdorff on
Okay, maybe my statement was misleading. Obviously you can have functional permissions as you can have things like general_comments_create "functional permission", in addition to "read", "write", "delete" aso. But you caught my intention correctly when I asked about generalization.

With the listbuilder example given above, this is solved if Dave's changes with regards to listbuilder views (or stored settings) makes these objects as planned. Then you can easily work around the "has permission to see column XYZ" by giving him access to the view. Which probably covers most szenarios (as you usually want to limit certain columns or group of columns for access).

For other functional permissions I am only curious about a naming convention then, which makes it easy for the admin interface to assign new types of permissions.

Profile based permissions I will first dig deeper into query rewriter to be able to understand the posting. Hopefully at the end I can come up with a document on how to use permissions effectively.

Now, Don, as per your comment, sadly I am usually confronted with websites that have these many objects and parties, if not even more. Add to that the fact that people do not understand the permission system correctly and you suddenly end up with 750.000 entries into acs_permissions per day. This being said, it is still remarkable how fast the permission querying works (okay, up to 1.5 seconds for the query, but what do we have permission caching for). It is only slow on delete (when you remove a permission). Therefore, in my opinion, the goal should be to limit the entries to the permissions table as good as possible (in the 750.000 example I did this by "correct" inheritance) and I will take a look at the approaches suggested by Tom for the above mentioned howto / best practice, permissions for dummies text.

Collapse
Posted by Tom Jackson on
I have a habit of using query-writer as an example of how some tiny detail can be handled. For instance, the name is query-writer, but here we are talking about roles. In general, query-writer specifies an interface to database objects, such as tables or functions. But this primary interface covers everything which can be done, what is needed is a user-interface. This is where roles come in. Roles expose a subset of the functionality provided by the primary interface. Usually there is an admin role and a user role (btw, query-writer uses the term 'group' for role, because the concept is that there are a group of possible things you can do, spanning all objects) Beyond the built in roles, you can add new ones using a rel_segment. Making sure that users don't do any more than they are allowed is the usual goal of permissions, for instance this is why we have admin pages, so we can provide the concept of a role. This isn't fully handled by permissions, so you require an admin directory and admin pages which might allow more functionality. That is okay for the simple case, but what if you have two user roles, neither admin? Do you need two sets of pages? How can you ensure that users with one role will not subvert the system and use the wrong set of pages?

In query-writer you can use the same set of pages and only show what a user in a particular role can do. This is possible because query-writer can produce a set of flags for each possible element in the role. These flags can be used to construct the UI, even as far as showing only select options which are allowed for the role. This particular functionality is completely independent of the query-writing functions, you could use it to construct roles and use the flags to determine if a particular set of columns was viewable by a role. Since the query-writer objects don't have to point to a database table or anything else, the object could just be a placeholder for defining roles. Since roles span objects (and objects sometimes span tables), you can construct join queries based upon the allowed columns in the different objects.

The comments found in the query-writer data model, are helpful:
http://rmadilo.com/files/query-writer/source/query-writer/

under sql/postgresql. Comments for qw_attrs is in a separate file from the table def.

Also interesting is the data which goes into the tables, this is usually called bootstrap data in the tcl directory for a package. Bootstrap data allows package developers to move query-writer data around once it has been created via the UI. But this data is so hard to read in bootstrap format, it might be interesting to see it in a slightly easier to read form (only slightly easier!):

http://whogeenie.com/qw/twist/

You can check out the WSDL to get a list of object types in the enumeration:

http://whogeenie.com/qw/twist/?WSDL

(The TWiST config is at index.txt)

I've also put a listing of the nsv arrays at:

http://whogeenie.com/qw/twist/nsv.html

The nsv arrays contain all of the information needed to run query-writer, none of the data in the query-writer tables are used during runtime, just loaded at startup.

Collapse
Posted by Malte Sussdorff on
I agree that proxy objects are great. I am wondering though if we want to give permissions per object type if we should not provide an easier method than using the query-writer approach by "just" making object_types objects.

I am now faced with the situation that I need to give permission to create various object types through an external interface. The only requirement is that the users can create, write and delete (for the sake of discussion) "tasks, projects,files,comments". Write and delete is a piece of cake, though it implies to either be stuck with a lot of entries into the permission table or to group them into proxy objects / roles in the code.

But what should I do with create? Here I need the proxy object, because I cannot give create permissions on a not yet created object. We could argue to say that "if you have create permissions on the context_id then you are allowed to create the object". But is this right? And wouldn't I still have to check if I am allowed to have write permission on the object_type?

This being said, it can all be done with the existing permission system and making object_types objects has nothing to do with it. But I am still unsure how "create" should behave 😊.

Now to something more interesting:

Assuming object_types are objects (or I use rel_segments), if I have read access to an object_type (rel_segment) I have access to the object of that type if I have access to the context_id. Meaning: If I have read access on tasks in general, I am able to view a task if I have read access on the project.

I will ponder about this a little bit more while working out how to map an external permission system (meaning remote, not in OpenACS) to the generic way of permissions in OpenACS so I do not have to touch every part of OpenACS to get those external permissions working and do not have to query the external permissions on every page but can "cache" them in the OpenACS permission system.

Collapse
Posted by Tom Jackson on
But what should I do with create? Here I need the proxy object, because I cannot give create permissions on a not yet created object. We could argue to say that "if you have create permissions on the context_id then you are allowed to create the object". But is this right? And wouldn't I still have to check if I am allowed to have write permission on the object_type?

With a role, you don't give role permissions to the individual objects, just the object type, so there is nothing different with create (in query-writer this is the 'new' operation). But another point to keep in mind with roles is that the not only filter the base objects, but the role can span multiple object types.

Otherwise, in order to allow users to serve in a role, you would have to assign multiple permissions. This could get confusing as roles may overlap, and if you remove a user from a role, you might damage the other role configuration.

Here I'm just speaking in general about a role and how it differs from objects and permissions on objects. In other words, think of roles as something layered on top of the main objects of interest. It is a description of what you can do, and a container for allowing users to assume a particular role.

In query-writer, I have one default admin group. It doesn't require any extra permissions, the user has to belong to the admin rel_segment (for the site). Even then, you could use a separate admin rel_segment to create a less expansive admin role. What I have found works best is for each package to configure their own roles which don't extend beyond the objects in that package, however, these could include objects which were initially created by another package. So for a particular package, the role assignment happens once for each user (for each role they can assume). For most applications, you don't even need to do this, the defaults work for an admin and regular user of the package.

Collapse
Posted by Dave Bauer on
I agree with Tom and Don, the system we have seems to work for us, there are many ways to implement what you want, and Tom has been explaining the best way to do this over and over again in the forums now for a while. Thanks Tom.
Collapse
Posted by Malte Sussdorff on
One last thing: Permission caching.

My experience is that it vastly improves performance. I am aware of the consequences if you are not using the TCL API to insert/delete/change permission records and I make sure that this condition is met (talking of which, how could we do an automated test that checks if people are using custom DB calls to change permissions?)

My question though: It uses util_memoize. If I understood Don, he would like to get away with util_memoize in general and use ns_cache directly. Not entirely sure how / why, but for permission caching I was wondering if we should have a separate cache? Is there any downside I am not aware of?

The one I can think of is the fact that I need to (re-)write cluster wide permission flushing for permissions. Anything else ?

Collapse
Posted by Malte Sussdorff on
Heh, revisiting the ns_cache statistics before posting helps. One of the large benefits of not using util_memoize for permissions is that util_memoize will not run out of entries. This being said I dug a little bit deeper into permission::permission_p and got confused:

a) We could probably change the util_memoize usage to a db_cache_pool caching, though we would probably run into the same issue as with util_memoize.

b) Looking at permission::permission_p_not_cached I see this:

    # We have a thread-local cache here
    global permission__permission_p__cache
    if { ![info exists permission__permission_p__cache($party_id,$object_id,$privilege)] } {
        set permission__permission_p__cache($party_id,$object_id,$privilege) [db_0or1row select_permission_p {}]
    }
    return $permission__permission_p__cache($party_id,$object_id,$privilege)

How much sense does the thread level cache make if we have caching using util_memoize or a different caching mechanism?  I can only think that this acts as a protection against permissions changes during execution of the script, which i guess is unwanted. But if that is the case, shouldn't this script level caching happen in permission::permission_p and before the util_memoize / permission_cache_pool caching?

Additionally, reading up on https://openacs.org/doc/current/programming-with-aolserver.html, shouldn't this better be called "script-local" cache as the array is destroyed once the script has run through (or am I missing something here)?

Collapse
Posted by Gustaf Neumann on
The array permission__permission_p__cache just prevents that - during a single request - the same permission is only looked-up once from the database. This double-lookup-prevention happens no matter what other caching options are used. I think, this is a sensible behavior. Yes, the array is deleted in the thread after the request.

There is a small bug in permission::permission_p_not_cached: the unused non-positiional argument "-no_cache" should be most probably removed.

Collapse
Posted by Malte Sussdorff on
Yes, this lookup prevention is great, which is why I would put it outside the "not_cached" version and put it into permission::permission_p, because otherwise we hit the util_memoize cache more often than we need to. And my assumption is that the util_memoize lookup is slower than going for the array.

In my understanding the first thing permission::permission_p should do is to look up the array and if it finds an entry return that value, skipping all other checks and procedure calls. Does this make sense ?

Collapse
Posted by Dave Bauer on
The extra unused parameter is there because the procedure name is aliased on startup if you have caching turned off.
Collapse
Posted by Dave Bauer on
Malte create behaves as it always has.

That is, objects have a "container". Traditionally a package_id or folder_id. You grant create on that and check create permission on that object.

Why does it have to be any more complicated?

Collapse
Posted by Tom Jackson on
There are several problems here, mostly created by with the terminology we use in the object/privilege/permission system.

The privileges read/write/admin have no fixed meaning with regard to what can be done to a particular object. But they are hierarchical. Admin implies read/write.

However, additional privileges are difficult to maintain, they have to be granted individually, and the lead to an explosion in the size of internal mapping tables.

I would also wonder if using a privilege on a particular object to is really what you want to use when deciding to create a subobject.

In fact, this idea is only a little improved over Malte's suggestion to make acs object types into objects.

Here is the problem: privileges granted on a particular object, like read, write, admin apply to manipulation of that object. That is, the data in a row of the object types table. Otherwise you are making an exception of how object privileges are used. Turning object types into objects and assigning permissions on these objects is a complete abuse of the idea of permissions: they apply to the objects themselves (the type object).

The concept of proxy object and how it should be used in this case is confusing. I don't think I do a good job of explaining it. There are two parts.

First the rel_segment places a user in a particular segment of a group. A group is a party, a party can own things. A party is the 'container' you ultimately need for application data, but applications are complex, so you need a complex party: a group with segments of users, roles.

When you ask a role type question, you are looking at membership, not permissions. Membership is the container for the role, but it isn't a permission question.

Privileges on a particular object comes after you look at if and how a particular user can interact with objects of that type.

However for creating new objects which exist in a hierarchy, it is common to require either admin or write privilege on the context_id for the object, sometimes this is the same as a parent_id, but in any case, permission checks should be done for any object_ids used in an object. This is confusingly similar to a role, but has a completely different purpose.

One final distinction between privilege and role. If you do use 'create' on the parent object to imply the right to create children, then you are stuck wih the need for another type of create for each role. This is exactly what you don't want. It doesn't help the object/permission system to add privileges.