Forum OpenACS Development: Re: Permission system in needs of revisit?

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.

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.

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.

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:

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!):

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

(The TWiST config is at index.txt)

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

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.

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.

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.