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).
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.
This section describes in more detail the required interface for specifying a GizmoBuilder subclass's permissions information. Briefly,
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.
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].
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.
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.
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.
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.
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:
is_a
name
id
save
uid
parent_id
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.
&get_parent
if class objects can have parents. This method must return a parent object which must also belong to Auditable.
$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.
save_permissions()
from the object's
save()
routine. See the Gizmo class for an example.
save_permissions()
from the object's
add()
method (or from whichever method commits a new object of the class to the database).
delete_permissions()
from the class destructor.
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.
www_edit_permissions
and call
edit_permissions_form()
from it (see
Gizmo::www_edit_permissions
for example).
&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. This section describes the Auditable interface. This interface is implemented by GizmoBuilder, and all new gizmos should inherit from GizmoBuilder.
&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.
&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).
&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.