3. Access Control

3.1 Overview

Metadot's Access Control (AC) system is the infrastructure that allows the system to check whether a given user can perform a given operation over an object. Most of the access control information is contained in a Permissions hash associated with each Gizmo object. The Permissions information is consulted when determining whether or not an operation is allowed.

The permissions information for an object consists of operation bundles, which associate groups of operations on the object with a permissions level for those operations; and an access control list, where users and groups on the list are given `owner' permissions for specified operations.

A bundle specification is defined for each gizmo class. For a given gizmo (such as Item), the developer may define a view bundle, and associate it with the operations show , show_as_bullet , and so on. Then, the developer may define an edit bundle, and associate it with a set of editing operations such as modify , and so on. The Operations class supports modifications of the bundle structures.

For each operations bundle defined for a class, the designer must define a default access level for the bundle, and a minimum access level for the bundle (described in more detail below). The default access level is the level set for the object when it is created. The minimum access level defines the lowest (most permissive) access level to which the bundle can be set. The "access level" corresponds to the status of the user attempting to access the object. There are four possible access levels: public access, logged in, owner, site manager , and admin . As is further described below, when an object is created, its default access settings are used in conjunction with the access levels of the object's parent, to instantiate the new object's Permissions info.

A user's status is matched against the permissions info, as is further described below, to check that the operation requested by the user is allowed. So, for example, if an object's view bundle permission was set to `logged in', then a user of the site can not perform the `view' operations on the object (essentially, can not see it), until they log in. As another example, the Discussion gizmo has a Moderate bundle, for which both the default and minimum access levels are set to " owner ", so that non-owners can not moderate the discussion.

In addition, the Permissions hash for a given object may include an access control list, where the users and groups on the list are given `owner' permissions for specified operations.

The logic of the permissions framework is contained primarily in the Auditable abstract class, from which most of the portal objects are subclassed. Auditable uses an object's Permissions information, in conjunction with the User info, to determine whether the User is allowed to perform a given operation on the object. Certain methods in the Auditable interface need to be implemented by the subclass, and for Gizmos, this implementation is facilitated by using the GizmoBuilder class. It is recommended that any new gizmo classes inherit from GizmoBuilder (see Section 5 for more information on building gizmos using GizmoBuilder).

3.1.1 Permissions-editing interface

All objects that inherit from Auditable, including GizmoBuilder and its subclasses, can support a permissions-editing interface. One of the GizmoBuilder's operations bundles is the edit permissions bundle, which is used to determine whether a given user can edit the permissions for the objects. By default, only the object's owner (and site managers and admins), can edit an object's permissions.

When edit mode is enabled on the site, each object will display a `key' icon next to its `edit' icon, if the user is privileged to edit the object's permissions. When this key is clicked, a form comes up which allows the user to modify the access levels for each of the objects bundles. In the form, there will be one pulldown menu for each of the object's operations bundles.

 

For example, the top of the permissions form for editing a Discussion object looks like this:

 

This form is generated because the developer of the Discussion gizmo has defined bundles for each of the listed classes of operations ( View, Edit,... Post, Moderate ), and has specified which Discussion methods are associated with each bundle. Further down on the form (not shown), is the interface for adding users and groups to the object's access control list . The information in this form essentially describes the contents of the object's permissions hash.

3.2 Specifying Access Control for a GizmoBuilder subclass

This section describes in more detail the required interface for specifying a GizmoBuilder subclass's permissions information. Briefly,

3.2.1 Numeric Access Levels and Bundle Permissions

Access levels are represented numerically in Metadot. They are defined in Auditable.pm, and are as follows:

An Operations object stores information about allowed access levels for an object's operations. This is done by defining permissions bundles , each of which is associated with names of one or more of the object's operations.

Each permissions bundle is defined with a bundle name, a "print name", and the default and minimum permissions levels for that bundle, by using the Operations add_bundle method. For example, where $ac is an Operations object,
$ac->add_bundle('DISP', 'View', '0', '0');
defines a bundle with name `DISP', which will be shown as `View' in the permissions editing form. Both default and minimum permissions levels for `View' operations are 0, which corresponds to `public access'.

Then, a set of operations is associated with the bundle, by using the operations add_op method. For example,
$ac->add_op('DISP','show');
sets the `show' operation to be part of the `View' bundle. This means that in the permissions editing form for the corresponding object, the permissions settings for `View' will be used whenever the object gets a request to perform the `show' operation

The bundle operations effectively describe the external (browser-based) API for the gizmo: these are the operations that a user can request on the object via a CGI query. Each operation in the bundles must be implemented by an object method. The method name is not the same as the operation name, but rather must be derived from it by prepending `www_' . The mapping from operation name to method name is implemented in Metadotclass .pm, from which all Gizmos are inherited. In Metadotclass , the handle method prepends `www_' to any op passed as a cgi parameter1. In practical terms, this means that when defining an operations bundle for a gizmo class, there must be a `www_<op_name>' method in your class for each <op_name> in the bundle .

 

Table 2 shows the bundle definitions for GizmoBuilder.pm.

 

3.2.2 Implement get_default_permissions

Each GizmoBuilder subclass must implement a get_default_permissions class method , which must return the default Operations object to use for that class. Typically, the default Operations object is stored as a class variable. Usually, it will make sense for a given gizmo to build its bundle permissions by starting from the bundles defined by its parent class, and using the Operations methods to add, delete, and modify entries from those bundles. Note that care must be taken to copy, or clone, the parent's Operation object before modifications if necessary.

In the case of GizmoBuilder, its get_default_permissions method returns a new default Operations object. Thus, its subclasses may use this operations object as a starting point for modifications without needing to clone it. Table 3 shows how the Discussion class modifies the GizmoBuilder's default operations bundle, and then defines its own get_default_permissions method to return the result. [In addition to the `add' methods shown in this example, the Operations class also provides remove_bundle and remove_op methods, which can be used to edit the parent class' bundle settings as well].

3.2.3 Object permissions instantiation and checking

As described above, each gizmo must implement a get_default_permissions method. When a new gizmo is created, it is instantiated with a set of initial bundle permissions. It will inherit bundle permission settings from its parent as appropriate, and uses the defined permissions defaults, via get_default_permissions , where it can not inherit from the parent. An object's permissions are represented by an object of the Permissions class2. These instantiated permissions, for each object, are stored in the permissions table in the db.

The is_allowed_to_do method in Auditable is the method which actually implements the permissions checking logic on an instantiated object. This method will obtain the permissions structure for the object, and will check, given a User object and an operation, whether or not the user is allowed to do that operation. All Gizmos inherit from Auditable.

All external requests for an object to perform an operation (e.g., a request from a client browser), go through the index.pl handler. index.pl accesses the referenced object's is_allowed_to_do method to decide whether or not to allow the request for the operation.

The following code sample is from index.pl . Given an incoming CGI request like this one:
http://<your.domain.com>/metadot/index.pl?iid=1118&isa=Discussion&op=show
index.pl will make the following check:

if ($gizmo_object){
    unless($gizmo_object->is_allowed_to_do($op, $USER, 1)) {
	$object->print_click_back( "Error", "index.pl:<BR>Sorry, you are not allowed to do operation:<BR> -$op-<BR>$0");	
	exit(0);      
    }
}

If the current user does not have permissions to `show' the given Discussion, an error page is returned.

 

 

3.3 Groups, Permissions, and Access Control Lists

Via the permissions-editing interface for an object, it is possible to add both groups and individual users to an access control list for the object, whereby the users on the ACL can be treated as "owner" for a specified subset of the object's permissions bundles. An object's access control list is stored as part of its permissions hash. The ACL is used by Auditable's is_allowed_to_do method to identify if the given user should be considered as "owner" for a given operation.

This interface is supported for all classes that implement the Auditable interface, and thus is supported for all GizmoBuilder subclasses.

In Table 4 below, the "Legal" group has been added to the ACL but has not yet been given any special permissions, in addition to "View", for the Discussion object. The user "Bob Jones" has been added to the ACL, and can access the ops in the "Edit" and "Delete and Cut" bundles as if he were the owner. However, he can not access, e.g. the "Post" permissions as owner.

 

If the "User overrides group permissions" box is checked, then this means that the specified individual permissions will be applied even if the user is in the group and the group permissions are less constraining.

3.3.1 Recursive Permissions Propagation and Application

Child objects created for a given object will inherit the parent's ACL for the bundles that they have in common, with the child's default bundle permissions used for the remainder. The bundle settings may be further modified manually after creation.

Note that the child permissions are not reapplied retroactively. That is, changing the permissions for a parent after a child has been created will not automatically affect the child's permissions. However, there are two ways to apply a parent's permissions to its children, one dynamic and one static. See the "Propagating Permissions Changes" section from the Admin's Guide, at <metadot>doc/md_guides/install/recursive_perms.html , for more information.

3.4 The Access Broker

In some cases, you may need finer-grained access control logic than at the operations level. This can happen, e.g., if you want to change part of the implementation of a method based on the current user.

A Metadot::AccessBroker::Default subclass can be defined to support this finer-grained access control. The AccessBroker class can contain the logic for a specific set of access decisions related to a given app or context, and this class can be consulted as necessary. See the code for examples of AccessBroker usage.

3.5 Steps required to build a class which supports AC

For completeness, this section describes in more detail the steps required to build a class which uses Auditable to support access control, for any case where you are not subclassing GizmoBuilder. In most cases, you will not need to use this information. The preferred means of building new gizmos is by subclassing the GizmoBuilder class. In that case, these methods are already implemented, and you need only to follow the instructions in the section above.

For a non-GizmoBuilder class, the steps are as follows:

  1. 1. Inherit from the Auditable class by including Auditable in the @ISA array.
  2. 2. Implement the following methods:

    is_a
    name
    id
    save
    uid
    parent_id

    See Section 3.6 for details on each of them.
  3. 3. Generate the Access Control (AC) information for that object. The preferred way to do this is via the Operations object. See the Operations.pm documentation for more information, and see the GizmoBuilder class for an example of its use. [Other Metadot classes still use a `deprecated' approach and construct a AC hash-- essentially the Operations object --by hand (see Gizmo::Category for an example)]. For any new classes, use the Operations object.
  4. 4. Build a method called get_AC which returns the Operations object (the AC hash). The GizmoBuilder class implements a method called get_default_permissions , which is used to deliver the Operations object. If you are subclassing GizmoBuilder to build a gizmo, then you can override get_default_permissions to add to or modify the Operations information for your subclass. See Gizmo::Discussion for an example. As discussed in the previous sections, the Operations bundles define the set of `permissions' put-downs that the user will see in the `edit permissions' form. This is illustrated in Table 5.
  5.  

  1. 5. Override method &get_parent if class objects can have parents. This method must return a parent object which must also belong to Auditable.
  2. 6. Call $self->init_permissions() at the end of the object's constructor method, after methods in step 2. above are implemented. See the Gizmo class for an example.
  3. 7. Call save_permissions() from the object's save() routine. See the Gizmo class for an example.
  4. 8. Call save_permissions() from the object's add() method (or from whichever method commits a new object of the class to the database).
  5. 9. Call delete_permissions() from the class destructor.
  6. 10. Do permissions enforcement by way of the is_allowed_to_do() method (see index.pl and Category.pm for examples of this). One must be particularly careful in sheltering all Access Controlled code sections around is_allowed_to_do() conditionals.
  7. 11. Implement www_edit_permissions and call edit_permissions_form() from it (see Gizmo::www_edit_permissions for example).
  8. 12. Override &go_back_to_parent_after_editing_permissions to return false for classes that require browsers to return to the object itself whose permissions are being edited, such as Category.

3.6 The Auditable Interface

This section describes the Auditable interface. This interface is implemented by GizmoBuilder, and all new gizmos should inherit from GizmoBuilder.

3.6.1 "Must-Provide" Methods

 

&get_AC: 
Should return the AC hash of the class. 
There should be an AC hash for every non-abstract class in the system.
 
&is_a: Should return the class name, as a string.
 
&name: Should return the name of this particular instance of Auditable, 
which is used to generate the Edit Permissions page.
 
&id: Should return a unique ID associated with this instance. 
If only one instance is expected to ever exist for this class this method should return 1.
 
&save: The only requirement for this method is to call &save_permissions. 
Normally this method will also save non-Auditable object data to disk (e.g. Gizmo::save).
 
&uid: Should return the User ID for the owner of a particular instance of Auditable.
 
&parent_id: Should return the ID of the parent of a particular instance of Auditable. 
If class instances can't have parents the ID must return 0.
3.6.2 OVERRIDEABLE Methods

 

&get_parent: Should return the Auditable object representing the parent of a 
particular instance of Auditable. If class objects can't have parents 
then it shouldn't be overridden.
 
&go_back_to_parent_after_editing_permissions: Should 
return false on classes that require browsers to return to the object itself whose 
permissions are being edited, such as Category. Otherwise it shouldn't be overridden. 
Should be called from www_edit_permissions 
(See Gizmo::www_edit_permissions for example of its use).
3.6.3 NON-OVERRIDEABLE Methods

 

&is_allowed_to_do: Should be called from all places in the class
 code where Access Control enforcement is needed. 
See Category.pm, index.pl and MyPage.pm for examples of this).
 
&edit_permissions_form: Should be called from 
www_edit_permissions and will return the HTML of 
the Edit Permissions form, which should be sent to client browsers.
 
&init_permissions: Should be called at the end of the 
class constructor, right after the "must-provide" methods of 
Section 3.6.1 are guaranteed to return true data.
 
&delete_permissions: Should be called from the class destructor, 
but before the "must-provide" methods of Section 3.6.1 cease to work.
 
&save_permissions: Should be called from save() 
and from any method that commits this objects data to disk for later restoring.
 
&get_bundle_level &set_bundle_level &get_bundle_of_op: 
These are used to modify/query minimum permissions associated with 
a particular instance of Auditable.

 


1. This scheme allows future extensions of the display logic in which methods for different display media, in addition to `www', can be later developed and mapped to object operations.

2. In the database, an object's permissions information is represented as a frozen hash record in the permissions table. In a future version of Metadot, we plan to `pull' the permissions info out of the frozen hash representation so that it is more directly queryable.