PHP Access Control - PHP5 CMS Framework Development

The SQL generated depends on whether there is already a permission with the same parameters, in which case only the control bits are updated. Otherwise an insertion occurs. The reason for having to do a SELECT fi rst, and then decide on INSERT or UPDATE is that the index on the relevant fi elds is not guaranteed to be unique, and also because the subject ID is allowed to be much longer than can be included within an index. It is therefore not possible to use ON DUPLICATE KEY UPDATE.
Wherever possible, it aids effi ciency to use the MySQL option for ON DUPLICATE KEY UPDATE. This is added to the end of an INSERT statement, and if the INSERT fails by virtue of the key already existing in the table, then the alternative actions that follow ON DUPLICATE KEY UPDATE are carried out. They consist of one or more assignments, separated by commas, just as in an UPDATE statement. No WHERE is permitted since the condition for the assignments is already determined by the duplicate key situation.

A simple method allows deletion of all permissions for a particular action and subject:

public function dropPermissions ($action, $subject_type, $subject_id)
{
$sql = "DELETE FROM #__permissions WHERE action='$action' AND
subject_type='$subject_type'AND subject_id='$subject_id'
AND system=0";
$this->doSQL($sql, true);
}

The final set of methods relates to assigning accessors to roles. Two of them refl ect the obvious need to be able to remove all roles from an accessor (possibly preparatory to assigning new roles) and the granting of a role to an accessor. Where the need is to assign a whole set of roles, it is better to have a method especially for the purpose. Partly this is convenient, but it also provides an extra operation, minimization of the set of roles. The method is:

public function assign ($role, $access_type, $access_id, $clear=true)
{
if ($this->handler->barredRole($role)) return false;
$this->database->setQuery("SELECT id FROM #__assignments WHERE
role='$role' AND access_type='$access_type' AND
access_id='$access_id'");
if ($this->database->loadResult()) return true;
$sql = "INSERT INTO #__assignments (role, access_type, access_id)
VALUES ('$role', '$access_type', '$access_id')";
$this->doSQL($sql, $clear);
return true;
}
public function assignRoleSet ($roleset, $access_type, $access_id)
{
$this->dropAccess ($access_type, $access_id);
$roleset = $this->authoriser->minimizeRoleSet($roleset);
foreach ($roleset as $role) $this->assign ($role, $access_type,
$access_id, false);
$this->clearCache();
}
public function dropAccess ($access_type, $access_id)
{
$sql = "DELETE FROM #__assignments WHERE
access_type='$access_type' AND access_id='$access_id'";
$this->doSQL($sql, true);
}

The method assign links a role to an accessor. It checks for barred roles fi rst, these are simply the special roles discussed earlier, which cannot be allocated to any accessor. As with the permitSQL method, it is not possible to use ON DUPLICATE KEY UPDATE because the full length of the accessor ID is not part of an index, so again the existence of an assignment is checked fi rst. If the role assignment is already in the database, there is nothing to do. Otherwise a row is inserted, and the cache is cleared.

Getting rid of all role assignments for an accessor is a simple database deletion, and is implemented in the dropAccess method. The higher level method assignRoleSet uses dropAccess to clear out any existing assignments. The call to the authorizer object to minimize the role set refl ects the implementation of a hierarchical model. Once there is a hierarchy, it is possible for one role to imply another as consultant implied doctor in our earlier example. This means that a role set may contain redundancy. For example, someone who has been allocated the role of consultant does not need to be allocated the role of doctor. The minimizeRoleSet method weeds out any roles that are superfl uous. Once that has been done, each role is dealt with using the assign method, with the clearing of the cache saved until the very end.

The General RBAC Cache

As outlined earlier, the information needed to deal with RBAC questions is cached in two ways. The fi le system cache is handled by the aliroAuthoriserCache singleton class, which inherits from the cachedSingleton class and is described fully in Chapter 8, on caches. This means that the data of the singleton object will be automatically stored in the fi le system whenever possible, with the usual provisions for timing out an old cache, or clearing the cache when an update has occurred. It is highly desirable to cache the data both to avoid database operations and to avoid repeating the processing needed in the constructor. So the intention is that the constructor method will run only infrequently. It contains this code:

protected function __construct()
{
// Making private enforces singleton
$database = aliroCoreDatabase::getInstance();
$database->setQuery("SELECT role, implied FROM #__role_link UNION
SELECT DISTINCT role, role AS implied FROM
#__assignments UNION SELECT DISTINCT role,
role AS implied FROM #__permissions");
$links = $database->loadObjectList();
if ($links) foreach ($links as $link)
{
$this->all_roles[$link->role] = $link->role;
$this->linked_roles[$link->role][$link->implied] = 1;
foreach ($this->linked_roles as $role=>$impliedarray)
{
foreach ($impliedarray as $implied=>$marker)
{
if ($implied == $link->role OR $implied == $link->implied)
{
$this->linked_roles[$role][$link->implied] = 1;
if (isset($this->linked_roles[$link->implied])) foreach
($this->linked_roles[$link->implied] as $more=>$marker)
{
$this->linked_roles[$role][$more] = 1;
}
}
}
}
}
$database->setQuery("SELECT role, access_id FROM #__assignments
WHERE access_type = 'aUser' AND (access_id = '*'
OR access_id = '0')");
$user_roles = $database->loadObjectList();
if ($user_roles) foreach ($user_roles as $role) $this-
>user_roles[$role->access_id][$role->role] = 1;
if (!isset($this->user_roles['0'])) $this->user_roles['0']
= array();
if (isset($this->user_roles['*'])) $this->user_roles['0'] =
array_merge($this->user_roles['0'], $this->user_roles['*']);
}

All possible roles are derived by a UNION of selections from the permissions, assignments, and linked roles database tables. The union operation has overheads, so that alone is one reason for favoring the use of a cache. The processing of linked roles is also complex, and therefore worth running as infrequently as possible. Rather than working through the code in detail, it is more useful to describe what it is doing. The concept is much simpler than the detail! If we take an example from the backwards compatibility features of Aliro, there is a role hierarchy that includes the role Publisher, which implies membership of the role Editor. The role Editor also implies membership of the role Author. In the general case, it is unreasonable to expect the administrator to fi gure out the implied relationships. In this case, it is clear that the role Publisher must also imply membership of the role Editor. But these linked relationships can plainly become quite complex. The code in the constructor therefore assumes that only the least number of connections have been entered into the database, and it fi gures out all the implications.

The other operation where the code is less than transparent is the setting of the user_roles property. The Aliro RBAC system permits the use of wild cards for specifi cation of identities within accessor, or subject types. An asterisk indicates any identity. For accessors whose accessor type is user, another wild card available is zero. This means any user who is logged in, and is not an unregistered visitor. Given the relatively small number of role assignments of this kind, it saves a good deal of processing if all of them are cached. Hence the user_roles processing is done in the constructor.

Other methods in the cache class are simple enough to be mentioned rather than given in detail. They include the actual implementation of the getTranslatedRole method, which provides local translations for the special roles. Other actual implementations are getAllRoles with the option to include the special roles, getTranslatedRole, which translates a role if it turns out to be one of the special ones and barredRole, which in turn, tests to see if the passed role is in the special group. It may therefore not be assigned to an accessor.

Article Type: 
How-to
0
Average: 5 (1 vote)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)