Project

General

Profile

Actions

Rights Management

This page explains how chamilo works with rights management on the system. The philosophy behind the rights system is that rights can be set for a user / a group / a template for a specific location on the system.

Locations

A location for the rights management system is a very abstract thing. A location can be a component on the system but can also be a category, a publication, or even a content object. This means that rights can be set on a very specific context in the system.
This also means that the available amount of locations will be gigantic. To make sure that a user does not need to set rights for each of these locations separately the locations are connected to each other by means of nested trees. The location's rights can be inherited from their parents.

A nested tree is a tree structure which is stored in the database and which has several properties that speed up the retrieval of parent locations without the use of recursive methods. Where a normal tree has a parent_id only, a nested tree has a parent_id, a left value and a right value. When creating the nested tree, the left and right values are assigned in a logical order from top to bottom first, and then to the right. This means that the one of the first left / right values will be handed out to the deepest nodes in the tree.

To retrieve the parents / children of an object we can just look at the left and right values and execute a simple select to retrieve all the locations:

  • Parents: select all the locations where the left value is smaller then the current left value and the right value is bigger then the current right value.
  • Children: select all the locations where the left value is bigger then the current

The disadvantages of using nested trees is that when we need to insert records very frequently the system will need to update the left and right values very often which will be a lot slower.

The rights

Each application can define his own rights.

The combination

A right is always given to the combination of a user / group / template with a location.

How the system checks for rights

When we are at a location we can ask the rights system if a user has a specific right at that current location. The system runs through a series of steps to check for rights:

  • If the user is a system administrator then he has all the rights in the system.

In any other case the system will run through an algorithm that does the following things:

  1. Check if the user has rights directly in the combination of right - user - location
  2. Check if the user has rights through on of his platform groups / parent platform groups with right - group - location
  3. Check if the user has rights with on of his templates that where assigned to either the user or his groups with right - template - location
  4. Check if the location inherits rights
  5. Do step 1 - 4 again for the parent locations
  6. If no parent locations found then the user has no right.

Available Rights

Available Rights

Practical

If you want to use the global rights system into your application you need to define your locations, provide the available rights / define the enums, provide the helper functions, check for the rights in your code and use the rights editor submanager to make it possible to set rights.

Define the locations

The locations can be defined statically and dynamically. Some locations are already known in the system like the components and are called static locations. These static locations are registered while the package is being installed. Static locations are defined in an xml file in the php/rights/{package}_locations.xml file of the package. This file is not optional and must a least define the root of the application as a location. The definition of the xml file is as following:

<?xml version="1.0" encoding="UTF-8"?>
<location name="cda" type="0" identifier="0">
    <children>
        <location name="Languages" type="1" identifier="1" />
        <location name="Language Packs" type="1" identifier="2" />
        <location name="Translator Applications" type="1" identifier="3" />
        <location name="Variable Translations" type="1" identifier="4" />
        <location name="Variables" type="1" identifier="5" />
    </children>
</location>

Notice that types and identifiers are always numeric because of performance issues. These types / identifiers are described in the rights class of your package.

Locations can also be created dynamically. This means that whenever a record is created in the database (for example a publication), a location is also created for that specific record. To create a location whenever a record is created you need to add the following code to your create method in your dataclass. This is usually done by the rights class of your package, so a simple call to your rights class would do the trick:

$parent = CdaRights :: get_languages_subtree_root_id();
CdaRights :: create_location_in_languages_subtree($this->get_english_name(), CdaRights :: TYPE_LANGUAGE, $this->get_id(), $parent);

Notice that the example makes use of a subtree. Subtrees are introduced to make sure that the nested trees do not become too big and cause performance issues. Most of the time subtrees are defined because they do not have a real relation with the root / components of the application. Subtrees are often defined for a bunch of dynamic locations. For example in the courses we have a subtree for each available course with locations for the tools, categories and the publications. Subtrees are created during the installation and need to be defined by the creator of the application. They are installed using the install_extra in the installer class. To create a subtree we need to use the rights utilities class:

RightsUtilities :: create_subtree_root_location(CdaManager :: APPLICATION_NAME, 0, CdaRights :: TREE_TYPE_LANGUAGES);

Notice that the tree type is defined as a numeric value to avoid performance issues. These tree types are also defined in the rights class of your package.

Provide the available rights and define other enums

If you want to use the rights system you need a class where you can define your rights and type / tree type / static location identifiers. The class is an extension of the RightsUtilities class and can be found in php/lib/{package}_rights.class.php

class CdaRights extends RightsUtilities

The rights, types, tree types and static location identifiers are defined as numeric values to avoid performance issues and are defined in the class as constants:

const VIEW_RIGHT = '1';
const ADD_RIGHT = '2';
const EDIT_RIGHT = '3';
const DELETE_RIGHT = '4';

const LOCATION_LANGUAGES = '1';
const LOCATION_LANGUAGE_PACKS = '2';
const LOCATION_TRANSLATOR_APPLICATIONS = '3';
const LOCATION_VARIABLE_TRANSLATIONS = '4';
const LOCATION_VARIABLES = '5';

const TREE_TYPE_LANGUAGES = 1;

const TYPE_LANGUAGE = 1;

Helper functions

A few helper functions can be added to call generic functions from the RightsUtilities which embed certain parameters :

function is_allowed($right, $location, $type)
{
    return RightsUtilities :: is_allowed($right, $location, $type, CdaManager :: APPLICATION_NAME);
}

function get_location_by_identifier($type, $identifier)
{
    return RightsUtilities :: get_location_by_identifier(CdaManager :: APPLICATION_NAME, $type, $identifier);
}

function get_location_id_by_identifier($type, $identifier)
{
    return RightsUtilities :: get_location_id_by_identifier(CdaManager :: APPLICATION_NAME, $type, $identifier);
}

function get_root_id()
{
    return RightsUtilities :: get_root_id(CdaManager :: APPLICATION_NAME);
}

function get_root()
{
    return RightsUtilities :: get_root(CdaManager :: APPLICATION_NAME);
}

static function create_location_in_languages_subtree($name, $type, $identifier, $parent)
{
    return RightsUtilities :: create_location($name, CdaManager :: APPLICATION_NAME, $type, $identifier, 0, $parent, 0, 0, self :: TREE_TYPE_LANGUAGES);
}

static function get_languages_subtree_root()
{
    return RightsUtilities :: get_root(CdaManager :: APPLICATION_NAME, self :: TREE_TYPE_LANGUAGES);
}

static function is_allowed_in_languages_subtree($right, $location, $type)
{
    static $is_allowed = array(); // Caching already retrieved results, for speed.
    if (!isset($is_allowed[$right][$location][$type]))
    {
        $is_allowed[$right][$location][$type] = RightsUtilities :: is_allowed($right, $location, $type, CdaManager :: APPLICATION_NAME, null, 0, self :: TREE_TYPE_LANGUAGES);
    }
    return $is_allowed[$right][$location][$type];
}

There is also a function which gives the available rights of the package. This is used in the rights editor manager to know what rights to display.

function get_available_rights()
{
    $reflect = new ReflectionClass('CdaRights');
    $rights = $reflect->getConstants();

    foreach($rights as $key => $right)
    {
        if(substr(strtolower($key), -5) != 'right')
        {
            unset($rights[$key]);
        }
    }
    return $rights;
}

Check for rights

To check if a user has a right on the system we need to call the helper functions from our rights class. We use either is_allowed or is_allowed_in_{type}_tree. We need to provide a right, a location (which is a identifier of a record) and a type of the location. For example:

CdaRights :: is_allowed_in_languages_subtree(CdaRights :: VIEW_RIGHT, $cda_language->get_id(), CdaRights :: TYPE_LANGUAGE);

Use the rights editor manager

To provide the interface to set the rights we need to create a component which can run the rights editor submanager.
The rights editor submanager needs to have the location(s) where the rights need to be set on.

The submanager can be launched with the following function:

$manager = new RightsEditorManager($this, $locations);
$manager->run();

The locations are real right locations and must be retrieved in the component and given as an array to the rights editor manager

$locations[] = CdaRights :: get_location_by_identifier_from_languages_subtree($language_id, CdaRights :: TYPE_LANGUAGE);

We can limit the functionality of the rights editor manager by limiting on users, groups, templates through the following functions:

$manager->limit_users($user_ids);
$manager->limit_groups($group_ids);
$manager->limit_templates($template_ids);

$manager->exclude_users($user_ids);
$manager->exclude_groups($group_ids);
$manager->exclude_templates($template_ids);

The current problems

Currently the global rights system is used the most when you are at a specific location and want to retrieve one or 2 rights for the current user.
When we want to have a list of all the locations where a user has the right for it is not so simple because of the complexity of the rights system.
We solved this issue by dividing the rights into two systems. The global system is used when you know the location, and very specific small systems are used when we want to retrieve a list with multiple locations.
This causes a lot of complexity and problems in the code. We need to think about an idea of how we can merge these two system into one fast and easy to use system.

The proposals

  • First idea
    • Retrieve the locations from the application and the given type, optionally treeidentifier and tree type, and add them to an array
    • For each of the locations, retrieve the parents and add them to the array (only if parent is unique)
    • Retrieve all the user groups
    • Retrieve al the user templates
    • Retrieve all the locations joined with the user location relation table and group location relation table and template location relation table where
    • The user, the rights, the groups, the templates and the given locations are in from the given type in the given tree
    • Return the identifiers of these locations
  • Second idea
    • Retrieve all user groups
    • Retrieve all the user templates
    • Retrieve all the locations where a user has a right in the user location relation, group location relation, template location relation in a given tree_type / tree_identifier
    • Return the identifiers where the locations type are equal to the given type
  • Third idea
    • Retrieve all user groups
    • Retrieve all templates
    • Start from the idea that you are in a context where you already have the view right
    • Retrieve all the child locations that inherit the parent and Retrieve all the child locations that not inherit the parent where a user has a right in the context_right_location
    • Join the locations with the current records to retrieve the records in one query in the sortable table.
    • Possible issue, what to do when a right on parent is set to off, but on on a child?

Updated by Sven Vanpoucke over 11 years ago ยท 20 revisions