Index: openacs-4/packages/acs-core-docs/www/permissions-tediously-explained.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/permissions-tediously-explained.html,v diff -u -r1.6 -r1.7 --- openacs-4/packages/acs-core-docs/www/permissions-tediously-explained.html 20 Aug 2003 16:20:16 -0000 1.6 +++ openacs-4/packages/acs-core-docs/www/permissions-tediously-explained.html 14 Oct 2003 11:02:58 -0000 1.7 @@ -1,7 +1,6 @@ - -OpenACS 4.x Permissions Tediously Explained

OpenACS 4.x Permissions Tediously Explained

+OpenACS 4.x Permissions Tediously Explained

OpenACS 4.x Permissions Tediously Explained

by Vadim Nasardinov. Modified and converted to Docbook XML by Roberto Mello -

Overview

+

Overview

The general permissions system has a relatively complex data model in OpenACS 4.x. Developers who haven't had the time to learn the internals of the data model may end up writing seemingly correct code that crashes their system in @@ -15,9 +14,9 @@ system internals.

In OpenACS 4.x, most of the interesting tables are expected to extend (subtype) - the acs_objects table, i.e. they are expected to have an integer - primary key column that references the object_id column of - acs_objects. + the acs_objects table, i.e. they are expected to have an integer + primary key column that references the object_id column of + acs_objects.

 create table acs_objects (
       object_id             integer
@@ -43,14 +42,14 @@
 );
     

This means that any interesting entity (object) - in the system has an entry in the acs_objects. This + in the system has an entry in the acs_objects. This allows developers to define relationships between any two entities A and B by defining a relationship between their corresponding entries - in the acs_objects table. One of the applications of this + in the acs_objects table. One of the applications of this powerful capability is the general permissions system.

- At the heart of the permission system are two tables: acs_privileges - and acs_permissions. + At the heart of the permission system are two tables: acs_privileges + and acs_permissions.

   create table acs_privileges (
       privilege           varchar2(100) not null
@@ -73,20 +72,20 @@
           primary key (object_id, grantee_id, privilege)
   );
     

- The acs_privileges table stores + The acs_privileges table stores named privileges like read, write, delete, create, and - admin. The acs_permissions + admin. The acs_permissions table stores assertions of the form:

- Who (grantee_id) can do what (privilege) - on which object (object_id). + Who (grantee_id) can do what (privilege) + on which object (object_id).

The naive approach to managing system security would be to require application developers to store permission information explicitly about every object, i.e. if the system has 100,000 and 1,000 users who have the read privilege on all objects, then we would need to store 100,000,000 entries of the form: -

Table�11.1.�

object_idgrantee_idprivilege
object_id_1user_id_1'read'
object_id_1user_id_2'read'
...
object_id_1user_id_n'read'
object_id_2user_id_1'read'
object_id_2user_id_2'read'
...
object_id_2user_id_n'read'
...
...
object_id_muser_id_1'read'
object_id_muser_id_2'read'
...
object_id_muser_id_n'read'

+

Table�11.1.�

object_idgrantee_idprivilege
object_id_1user_id_1'read'
object_id_1user_id_2'read'
...
object_id_1user_id_n'read'
object_id_2user_id_1'read'
object_id_2user_id_2'read'
...
object_id_2user_id_n'read'
...
...
object_id_muser_id_1'read'
object_id_muser_id_2'read'
...
object_id_muser_id_n'read'

Although quite feasible, this approach fails to take advantage of the fact that objects in the system are commonly organized hierarchally, and permissions usually follow the hierarchical structure, so that if user @@ -98,42 +97,42 @@ necessity to explicitly maintain security information for every single object. There are three kinds of hierarchies involved. These are discussed in the following sections. -

Context Hierarchy

+

Context Hierarchy

Suppose objects A, B, ..., and F form the following hierarchy. -

Table�11.2.�

A

- object_id=10 +

Table�11.2.�

A

+ object_id=10

B

- object_id=20 + object_id=20

C

- object_id=30 + object_id=30

D

- object_id=40 + object_id=40

E

- object_id=50 + object_id=50

F

- object_id=60 + object_id=60

- This can be represented in the + This can be represented in the acs_objects table by the following entries: -

Table�11.3.�

object_idcontext_id
2010
3010
4020
5020
6030

+

Table�11.3.�

object_idcontext_id
2010
3010
4020
5020
6030

The first entry tells us that object 20 is the descendant of object 10, and the third entry shows that object 40 is the descendant of object 20. By running a CONNECT BY query, we can compute that object 40 is the second-generation descendant of object 10. With this in mind, if we want to record the fact that user Joe has the read privilege on objects A, ..., F, we only need to record one entry in the - acs_permissions table. -

Table�11.4.�

objectgranteeprivilege
AJoeread

+ acs_permissions table. +

Table�11.4.�

objectgranteeprivilege
AJoeread

The fact that Joe can also read B, C, ..., and F can be derived by ascertaining that these objects are children of A by traversing the context hierarchy. As it turns out, hierarchical queries are expensive. As Rafael Schloming put it so aptly, Oracle can't deal with hierarchies for shit.

One way to solve this problem is to cache a flattened view of the context tree like so: -

Table�11.5.�

objectancestorn_generations
AA0
BB0
BA1
CC0
CA1
DD0
DB1
DA2
EE0
EB1
EA2
FF0
FC1
FA2

+

Table�11.5.�

objectancestorn_generations
AA0
BB0
BA1
CC0
CA1
DD0
DB1
DA2
EE0
EB1
EA2
FF0
FC1
FA2

Note that the number of entries in the flattened view grows exponentially with respect to the depth of the context tree. For instance, if you have a fully populated binary tree with a depth of n, then the number of entries @@ -143,7 +142,7 @@ Despite its potentially great storage costs, maintaining a flattened representation of the context tree is exactly what OpenACS 4.x does. The flattened context tree is stored in the - acs_object_context_index table. + acs_object_context_index table.

   create table acs_object_context_index (
       object_id
@@ -154,7 +153,7 @@
           constraint acs_obj_context_idx_anc_id_fk references acs_objects(object_id),
       n_generations	    integer
           not null
-          constraint acs_obj_context_idx_n_gen_ck check (n_generations >= 0),
+          constraint acs_obj_context_idx_n_gen_ck check (n_generations >= 0),
       constraint acs_object_context_index_pk
           primary key (object_id, ancestor_id)
   ) organization index;
@@ -168,8 +167,8 @@
       has, and exponentially
       with respect to the depth of the context tree. 
     

- The acs_object_context_index is kept in sync with the - acs_objects + The acs_object_context_index is kept in sync with the + acs_objects table by triggers like this:

 create or replace trigger acs_objects_context_id_in_tr
@@ -199,40 +198,40 @@
     end if;
 end;
 

- One final note about + One final note about acs_objects. By setting - an object's security_inherit_p column to 'f', you can stop permissions + an object's security_inherit_p column to 'f', you can stop permissions from cascading down the context tree. In the following example, Joe does not have the read permissions on C and F. -

Table�11.6.�


+

Table�11.6.�


A
-object_id=10
+object_id=10
readable�by�Joe
������


B
-object_id=20
+object_id=20
readable�by�Joe
��������������


C
-object_id=30
+object_id=30
security_inherit_p�=�'f'
not�readable�by�Joe
������


D
-object_id=40
+object_id=40
������


E
-object_id=50
+object_id=50
������


F
-object_id=60
+object_id=60
security_inherit_p�=�'f'
not�readable�by�Joe
- ������

Privilege Hierarchy

+ ������

Privilege Hierarchy

Privileges are also organized hierarchically. In addition to the five main system privileges defined in the ACS Kernel data model, application developers may define their own. For instance, the Bboard package defines the following privileges: -

Table�11.7.�

privilege
create_category
create_forum
create_message
delete_category
delete_forum
delete_message
moderate_forum
read_category
read_forum
read_message
write_category
write_forum
write_message

+

Table�11.7.�

privilege
create_category
create_forum
create_message
delete_category
delete_forum
delete_message
moderate_forum
read_category
read_forum
read_message
write_category
write_forum
write_message

By defining parent-child relationship between privileges, the OpenACS data model makes it easier for developers to manage permissions. Instead of granting a user explicit read, write, delete, @@ -241,9 +240,9 @@ privilege to which the first four privileges are tied. To give a more detailed example, the Bboard privileges are structured as follows. -

Table�11.8.�

admin
createdeletereadwritemoderate forum
create categorycreate forumcreate messagedelete categorydelete forumdelete messageread categoryread forumread messagewrite categorywrite forumwrite message

+

Table�11.8.�

admin
createdeletereadwritemoderate forum
create categorycreate forumcreate messagedelete categorydelete forumdelete messageread categoryread forumread messagewrite categorywrite forumwrite message

The parent-child relationship between privileges is represented in - the acs_privilege_hierarchy table: + the acs_privilege_hierarchy table:

   create table acs_privilege_hierarchy (
       privilege
@@ -284,10 +283,10 @@
       reasonably small, there is no pressing need to cache the flattened ansector-descendant
       view of the privilege hierarchy in a specially maintained table like
       it is done in the case of the context hierarchy.
-    

Party Hierarchy

+

Party Hierarchy

Now for the third hierarchy playing a promiment role in the permission system. The party data model is set up as follows. -

+    

   create table parties (
       party_id
           not null
@@ -326,9 +325,9 @@
       group_name           varchar2(100) not null
   );
     

- Recall that the grantee_id column of the + Recall that the grantee_id column of the acs_permissions table references - parties.party_id. + parties.party_id. This means that you can grant a privilege on an object to a party, person, user, or group. Groups represent aggregations of parties. The most common scenario that you are likely to encounter is a group that is a collection of users, although you could also @@ -339,7 +338,7 @@ a group named Pranksters, you can assign membership to Pete, Poly, and Penelope. The fact that these users are members of the Pranksters group will be recorded in the - membership_rels and acs_rels tables: + membership_rels and acs_rels tables:

   create table acs_rels (
       rel_id
@@ -369,9 +368,9 @@
            check (member_state in ('approved', 'banned', 'rejected', 'deleted'))
   );
     

- The acs_rels + The acs_rels table entries would look like so: -

Table�11.10.�

rel_typeobject_oneobject_two
+

Table�11.10.�

rel_typeobject_oneobject_two
membership_rel Pranksters @@ -395,8 +394,8 @@ of Pranksters. We say that the Pranksters group is composed of groups Merry Pranksters and Sad Pranksters. This - information is stored in the acs_rels - and composition_rels tables. + information is stored in the acs_rels + and composition_rels tables.

 create table composition_rels (
     rel_id
@@ -405,8 +404,8 @@
 );
     

The relevant entries in the - acs_rels look like so. -

Table�11.11.�

rel_typeobject_oneobject_two
+ acs_rels look like so. +

Table�11.11.�

rel_typeobject_oneobject_two
composition_rel Pranksters @@ -472,17 +471,17 @@ primary key (member_id, group_id, rel_id) ) organization index;

- The group_component_index table stores a flattened representation of the - group composition hierarchy that is maintained in sync with the acs_rels - and composition_rels tables through triggers. + The group_component_index table stores a flattened representation of the + group composition hierarchy that is maintained in sync with the acs_rels + and composition_rels tables through triggers.

- As far as the group_member_index table goes, I am not sure I understand its + As far as the group_member_index table goes, I am not sure I understand its purpose. It maintains group-member relationships that are resolved with respect to group composition. Note that information stored in - group_member_index can be trivially derived by joining - membership_rels, - acs_rels, - and group_component_index. Here + group_member_index can be trivially derived by joining + membership_rels, + acs_rels, + and group_component_index. Here is a view that does it. (This view is not part of the OpenACS Kernel data model.)

 create or replace view group_member_view
@@ -507,8 +506,8 @@
   mr.rel_id = r.rel_id
   and r.object_id_one = gci.component_id;
     

- A heuristic way to verify that group_member_view is essentially identical - to group_member_index is to compute the + A heuristic way to verify that group_member_view is essentially identical + to group_member_index is to compute the symmetric difference between the two:

 select
@@ -531,12 +530,12 @@
     

The query returns no rows. The important point is, if we have a flattened view of the composition hierarchy -- like one provided - by the group_component_index table -- + by the group_component_index table -- membership relationship resolution can be computed trivially with no hierarchical queries involved. There is no need to keep the view in a denormalized table, unless doing so results in substantial performance gains. -

Putting It All Together

- Security information is queried by calling the acs_permission.permission_p +

Putting It All Together

+ Security information is queried by calling the acs_permission.permission_p function in OpenACS 4.x.

  
   create or replace package body acs_permission
@@ -563,13 +562,13 @@
   end acs_permission;
     

The function simply queries - acs_object_party_privilege_map, + acs_object_party_privilege_map, which is a humongous view that joins three flattened hierarchies: the context tree, the privilege hierarchy, the party composition (and membership) hierarchy. As such, it contains an extremely large number of rows. About the only kind of query you can run against it is the one - performed by the acs_permission.permission_p + performed by the acs_permission.permission_p function. Anything other than that would take forever to finish or would ultimately result in an Oracle error.

@@ -593,9 +592,9 @@ for rec in cur loop acs_permission.revoke_permission ( - object_id => rec.object_id, - grantee_id => rec.party_id, - privilege => 'foo_create' + object_id => rec.object_id, + grantee_id => rec.party_id, + privilege => 'foo_create' ); end loop; @@ -605,7 +604,7 @@ end; /

- The acs_permission.revoke_permission function merely runs a + The acs_permission.revoke_permission function merely runs a delete statement like so:

  
   delete from
@@ -615,9 +614,9 @@
      and grantee_id = revoke_permission.grantee_id
      and privilege = revoke_permission.privilege;
     

- Note that in the above example, acs_permissions had only + Note that in the above example, acs_permissions had only one entry that needed to be deleted: -

Table�11.12.�

object_idgrantee_idprivilege
+

Table�11.12.�

object_idgrantee_idprivilege
default_context registered_users @@ -626,8 +625,8 @@

The above script would never get around to deleting this entry because it had to loop through a gazillion rows in the humongous - acs_object_party_privilege_map view. -

Appendix: Various View Definitions

+      acs_object_party_privilege_map view. 
+    

Appendix: Various View Definitions

 create or replace view acs_object_party_privilege_map
 as
 select
@@ -690,4 +689,4 @@
   container_id
 from
   group_member_index;
-    
View comments on this page at openacs.org
+
View comments on this page at openacs.org