aliroAuthoriser.php

Go to the documentation of this file.
00001 <?php
00002 
00003 /*******************************************************************************
00004  * Aliro - the modern, accessible content management system
00005  *
00006  * Aliro is open source software, free to use, and licensed under GPL.
00007  * You can find the full licence at http://www.gnu.org/copyleft/gpl.html GNU/GPL
00008  *
00009  * The author freely draws attention to the fact that Aliro derives from Mambo,
00010  * software that is controlled by the Mambo Foundation.  However, this section
00011  * of code is totally new.  If it should contain any fragments that are similar
00012  * to Mambo, please bear in mind (1) there are only so many ways to do things
00013  * and (2) the author of Aliro is also the author and copyright owner for large
00014  * parts of Mambo 4.6.
00015  *
00016  * Tribute should be paid to all the developers who took Mambo to the stage
00017  * it had reached at the time Aliro was created.  It is a feature rich system
00018  * that contains a good deal of innovation.
00019  *
00020  * Your attention is also drawn to the fact that Aliro relies on other items of
00021  * open source software, which is very much in the spirit of open source.  Aliro
00022  * wishes to give credit to those items of code.  Please refer to
00023  * http://aliro.org/credits for details.  The credits are not included within
00024  * the Aliro package simply to avoid providing a marker that allows hackers to
00025  * identify the system.
00026  *
00027  * Copyright in this code is strictly reserved by its author, Martin Brampton.
00028  * If it seems appropriate, the copyright will be vested in the Aliro Organisation
00029  * at a suitable time.
00030  *
00031  * Copyright (c) 2007 Martin Brampton
00032  *
00033  * http://aliro.org
00034  *
00035  * counterpoint@aliro.org
00036  *
00037  * aliroAuthoriser is a singleton class that handles questions concerning the Role Based
00038  * Access Control (RBAC) system for Aliro.  It is a companion to aliroAuthorisationAdmin
00039  * which is the class that deals with updating the RBAC information.  Since the information
00040  * used in this class is often particular to the current user, it makes poor sense to
00041  * have a general cache.  Instead, information is cached using session variables. An
00042  * exception to this principle is the linking structure that enables implied roles to
00043  * be derived - e.g. a Publisher implicitly also has the rights belonging to an Editor.
00044  * Since this information is tricky to construct and general to all users, it is cached
00045  * in the file system.
00046  *
00047  * The code is complicated by an effort to achieve a good degree of backwards compatibility
00048  * with Mambo 4.x and Joomla 1.x.  A few of the GACL methods are emulated.
00049  *
00050  */
00051 
00052 class aliroAuthoriserCache extends cachedSingleton {
00053     protected static $instance= __CLASS__;
00054 
00055     private $linked_roles = array();
00056     private $user_roles = array();
00057     private $all_roles = array();
00058     private $all_subjects = array();
00059     private $translations = array (
00060         'Registered' => 'Registered(translated)',
00061         'Visitor' => 'Visitor(translated)',
00062         'Nobody' => 'Nobody(translated)',
00063         'none' => 'None of these(trans)'
00064         );
00065 
00066     protected function __construct () {
00067         // Making private enforces singleton
00068         $database = aliroCoreDatabase::getInstance();
00069         $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");
00070         $links = $database->loadObjectList();
00071         if ($links) foreach ($links as $link) {
00072             $this->all_roles[$link->role] = $link->role;
00073             $this->linked_roles[$link->role][$link->implied] = 1;
00074             foreach ($this->linked_roles as $role=>$impliedarray) {
00075                 foreach ($impliedarray as $implied=>$marker) {
00076                     if ($implied == $link->role OR $implied == $link->implied) {
00077                         $this->linked_roles[$role][$link->implied] = 1;
00078                         if (isset($this->linked_roles[$link->implied])) foreach ($this->linked_roles[$link->implied] as $more=>$marker) {
00079                             $this->linked_roles[$role][$more] = 1;
00080                         }
00081                     }
00082                 }
00083             }
00084         }
00085         $user_roles = $database->doSQLget("SELECT role, access_id FROM #__assignments WHERE access_type = 'aUser' AND (access_id = '*' OR access_id = '0')");
00086         foreach ($user_roles as $role) $this->user_roles[$role->access_id][$role->role] = 1;
00087         if (!isset($this->user_roles['0'])) $this->user_roles['0'] = array();
00088         if (isset($this->user_roles['*'])) $this->user_roles['0'] = array_merge($this->user_roles['0'], $this->user_roles['*']);
00089         $allsubject = $database->doSQLget("SELECT role, control, action FROM #__permissions WHERE subject_type = '*' AND subject_id - '*'");
00090         foreach ($allsubject as $asub) $this->all_subjects[$asub->role] = $asub;
00091         
00092     }
00093 
00094     public static function getInstance () {
00095         return is_object(self::$instance) ? self::$instance : (self::$instance = parent::getCachedSingleton(self::$instance));
00096     }
00097 
00098     public function getTranslatedRole ($role) {
00099         if (isset($this->translations[$role])) return $this->translations[$role];
00100         else return $role;
00101     }
00102 
00103     public function getAllRoles ($addSpecial=false) {
00104         $roles = $this->all_roles;
00105         if ($addSpecial) foreach ($this->translations as $raw=>$translated) $roles[$raw] = $translated;
00106         return $roles;
00107     }
00108 
00109     public function barredRole ($role) {
00110         if (isset($this->translations[$role])) return true;
00111         else return false;
00112     }
00113 
00114     public function getLinkedRoles () {
00115         return $this->linked_roles;
00116     }
00117 
00118     public function getUserRoles ($id) {
00119         return isset($this->user_roles[$id]) ? array_keys($this->user_roles[$id]) : array();
00120     }
00121     
00122     public function canRoleAccessAll ($role, $action, $control) {
00123         if (isset($this->all_subjects[$role])) {
00124             $asub = $this->all_subjects[$role];
00125             if ($action == $asub->action AND ($control & $asub->control)) return true;
00126         }
00127         return false;
00128     }
00129 
00130 }
00131 
00132 
00133 class aliroAuthoriser {
00134     private static $instance = __CLASS__;
00135 
00136     private $subj_found = array();
00137     private $permissions = array();
00138     private $access_found = array();
00139     private $access_roles = array();
00140 
00141     private $linked_roles = array();
00142     private $auth_vars = array ('subj_found', 'permissions', 'access_found', 'access_roles', 'refused');
00143     private $old_groupids = array ('Registered' => 18, 'Author' => 19, 'Editor' => 20, 'Publisher' => 21, 'Manager' => 23, 'Administrator' => 24, 'Super Administrator' => 25);
00144 
00145     private $handler = null;
00146     private $database = null;
00147 
00148     private function __construct () {
00149         // Make sure session started
00150         aliroSessionFactory::getSession();
00151         // Use session data as the source for cached user related data
00152         foreach ($this->auth_vars as $one_var) {
00153             if (!isset($_SESSION['aliro_auth'][$one_var])) $_SESSION['aliro_auth'][$one_var] = array();
00154             $this->$one_var =& $_SESSION['aliro_auth'][$one_var];
00155         }
00156         $this->handler = aliroAuthoriserCache::getInstance();
00157         $this->linked_roles = $this->handler->getLinkedRoles();
00158         $this->database = aliroCoreDatabase::getInstance();
00159     }
00160 
00161     private function __clone () {
00162         // Enforce singleton class
00163     }
00164 
00165     public static function getInstance () {
00166         return is_object(self::$instance) ? self::$instance : (self::$instance = new self::$instance());
00167     }
00168 
00169     public function clearCache () {
00170         $this->subj_found = $this->permissions = $this->access_found = $this->access_roles = $this->refused_cache = array();
00171     }
00172 
00173     public function getAllRoles ($addSpecial=false) {
00174         return $this->handler->getAllRoles($addSpecial);
00175     }
00176 
00177     public function getTranslatedRole ($role) {
00178         return $this->handler->getTranslatedRole($role);
00179     }
00180 
00181     public function minimizeRoleSet ($roleset) {
00182         if (0 == count($roleset)) return $roleset;
00183         $first = array_shift($roleset);
00184         foreach ($roleset as $key=>$role) {
00185             if (isset($this->linked_roles[$first][$role])) unset ($roleset[$key]);
00186             if (isset($this->linked_roles[$role][$first])) return $this->minimizeRoleSet ($roleset);
00187         }
00188         array_unshift($roleset, $first);
00189         return $roleset;
00190     }
00191 
00192     private function getSubjectData ($subject, $id, $action) {
00193         $stamp = time();
00194         if (isset($this->subj_found[$subject][$action][$id]) AND (($stamp - $this->subj_found[$subject][$action][$id]) < _ALIRO_AUTHORISER_SESSION_CACHE_TIME)) return;
00195         if (isset($this->subj_found[$subject][$action]['*']) AND ($stamp - $this->subj_found[$subject][$action]['*'] < _ALIRO_AUTHORISER_SESSION_CACHE_TIME)) return;
00196         $this->database->setQuery("SELECT COUNT(*) FROM `#__permissions` WHERE `subject_type`='$subject' AND (`action`='$action' OR `action`='*')");
00197         if ($this->database->loadResult() < 100) {
00198             $this->database->setQuery("SELECT `role`, `control`, `subject_id`, `action` FROM `#__permissions` WHERE `subject_type`='$subject' AND (`action`='$action' OR `action`='*')");
00199             $new_permissions = $this->database->loadObjectList();
00200             unset($this->subj_found[$subject][$action]);
00201             $this->subj_found[$subject][$action]['*'] = $stamp;
00202         }
00203         else {
00204             $this->database->setQuery("SELECT role, control, subject_id, action FROM #__permissions WHERE subject_type='$subject' AND (subject_id='$id' OR subject_id='*') AND (action='$action' OR action='*')");
00205             $new_permissions = $this->database->loadObjectList();
00206             unset($this->subj_found[$subject][$action][$id]);
00207         }
00208         if ($new_permissions) {
00209             foreach ($new_permissions as $permit) {
00210                 $this->permissions[$subject][$permit->action][$permit->subject_id][$permit->role] = $permit->control;
00211                 $this->subj_found[$subject][$permit->action][$permit->subject_id] = $stamp;
00212             }
00213         }
00214     }
00215 
00216     public function getAccessorRoles ($type, $id) {
00217         if ('aUser' == $type AND ('0' == $id OR '*' == $id)) return $this->handler->getUserRoles($id);
00218         if (isset($this->access_found[$type][$id])) {
00219             if ((time() - $this->access_found[$type][$id]) < _ALIRO_AUTHORISER_SESSION_CACHE_TIME) {
00220                 return $this->mergeAccessorResults($type, $id);
00221             }
00222             unset ($this->access_found);
00223             $this->access_roles = array();
00224         }
00225         $sql = "SELECT role, access_id FROM #__assignments AS a WHERE a.access_type='$type'";
00226         $sql .= isset($this->access_found[$type]) ? " AND a.access_id='$id'" : " AND (a.access_id='$id' OR a.access_id='*' OR a.access_id='+')";
00227         $this->database->setQuery($sql);
00228         if ($results = $this->database->loadObjectList()) {
00229             foreach ($results as $result) {
00230                 $this->access_roles[$type][$result->access_id][$result->role] = 1;
00231             }
00232         }
00233         $this->access_found[$type][$id] = time();
00234         return $this->mergeAccessorResults($type, $id);
00235     }
00236 
00237     private function mergeAccessorResults ($type, $id) {
00238         if (isset($this->access_roles[$type][$id])) $result = $this->access_roles[$type][$id];
00239         else $result = array();
00240         if (isset($this->access_roles[$type]['*'])) $result = array_merge($result, $this->access_roles[$type]['*']);
00241         if ($id AND isset($this->access_roles[$type]['+'])) $result = array_merge($result, $this->access_roles[$type]['+']);
00242         if ('aUser' == $type AND $id) $result['Registered'] = 1;
00243         if (count($result)) return array_keys ($result);
00244         else return array();
00245     }
00246 
00247     private function blanket ($action, $type) {
00248         return (isset($this->permissions[$type][$action]['*']) AND count($this->permissions[$type][$action]['*']));
00249     }
00250 
00251     private function specific ($action, $type, $id) {
00252         return (isset($this->permissions[$type][$action][$id]) AND count($this->permissions[$type][$action][$id]));
00253     }
00254 
00255     private function accessorPermissionOrControl  ($mask, $a_type, $a_id, $action, $s_type='*', $s_id='*') {
00256         $this->getSubjectData ($s_type, $s_id, $action);
00257         if ('*' != $s_type AND 2 == $mask AND !$this->blanket($action, $s_type) AND !($this->specific($action, $s_type, $s_id))) return 1;
00258         if ((!isset($this->permissions[$s_type][$action][$s_id]) OR 0 == count($this->permissions[$s_type][$action][$s_id]))
00259         AND (!isset($this->permissions[$s_type][$action]['*']) OR 0 == count($this->permissions[$s_type][$action]['*']))) return 1;
00260         $roles = $this->getAccessorRoles ($a_type, $a_id);
00261         return $this->rolePermissionOrControl ($mask, $roles, $action, $s_type, $s_id);
00262     }
00263 
00264     public function checkPermission ($a_type, $a_id, $action, $s_type='*', $s_id='*') {
00265         return $this->accessorPermissionOrControl(2, $a_type, $a_id, $action, $s_type, $s_id);
00266     }
00267 
00268     public function checkUserPermission ($action, $s_type='*', $s_id='*') {
00269         $user = aliroUser::getInstance();
00270         return $this->checkPermission ('aUser', $user->id, $action, $s_type, $s_id);
00271     }
00272 
00273     public function checkControl ($a_type, $a_id, $action, $s_type='*', $s_id='*') {
00274         return $this->accessorPermissionOrControl(1, $a_type, $a_id, $action, $s_type, $s_id);
00275     }
00276 
00277     public function checkGrant ($a_type, $a_id, $action, $s_type='*', $s_id='*') {
00278         return $this->accessorPermissionOrControl(4, $a_type, $a_id, $action, $s_type, $s_id);
00279     }
00280 
00281     private function rolePermissionOrControl ($mask, $roles, $actions, $s_type, $s_id) {
00282         foreach ((array) $roles as $role) {
00283             foreach ((array) $actions as $action) if ($this->handler->canRoleAccessAll ($role, $action, $mask)) return 1;
00284         }
00285         foreach ((array) $actions as $action) $this->getSubjectData ($s_type, $s_id, $action);
00286         if (in_array('Visitor', (array) $roles)) {
00287             foreach ((array) $actions as $action) {
00288                 if (empty($this->permissions[$s_type][$action][$s_id])) return 1;
00289             }
00290         }
00291         if (count((array) $roles)) foreach ($this->permissions[$s_type] as $act=>$level2) {
00292                 if (!in_array($act, (array) $actions) AND !in_array('*', (array) $actions)) continue;
00293             foreach ($level2 as $id=>$level3) {
00294                 if ($id != $s_id AND $id != '*') continue;
00295                 foreach ($level3 as $role=>$control)
00296                     if (in_array($role, (array) $roles) AND ($mask & $control)) {
00297                         return 1;
00298                     }
00299             }
00300         }
00301         return 0;
00302     }
00303 
00304     public function checkRolePermission  ($role, $action, $s_type, $s_id) {
00305         return $this->rolePermissionOrControl(2, $role, $action, $s_type, $s_id);
00306     }
00307 
00308     public function checkRoleControl  ($role, $action, $s_type, $s_id) {
00309         return $this->rolePermissionOrControl(1, $role, $action, $s_type, $s_id);
00310     }
00311 
00312     public function checkRoleGrant  ($role, $action, $s_type, $s_id) {
00313         return $this->rolePermissionOrControl(4, $role, $action, $s_type, $s_id);
00314     }
00315 
00316     function getRefusedList ($a_type, $a_id, $s_type, $actionlist) {
00317         $roles = $this->getAccessorRoles($a_type, $a_id);
00318         $actions = explode(',', $actionlist);
00319         foreach ($actions as $i=>$action) $actions[$i] = trim($action);
00320         $alist = implode("','", $actions);
00321         if (isset($this->refused[$s_type][$alist])) $ids = $this->refused[$s_type][$alist];
00322         else {
00323             $ids = array();
00324             $results = $this->database->doSQLget("SELECT role, subject_id, action FROM #__permissions WHERE subject_type = '$s_type' AND action IN('$alist')");
00325             foreach ($results as $result) $ids[$result->subject_id][$result->action][] = $result->role;
00326             $this->refused[$s_type][$alist] = $ids;
00327         }
00328         if (count($ids)) {
00329             $refused = array_keys($ids);
00330             foreach ($ids as $id=>$actionset) {
00331                 foreach ($actions as $action) if (!isset($actionset[$action])) $permits[$id] = 1;
00332                 if (!isset($permits[$id])) foreach ($actionset as $action=>$permittedroles) {
00333                     if (count(array_intersect($permittedroles, $roles))) $permits[$id] = 1;
00334                 }
00335             }
00336             if (isset($permits)) $refused = array_diff ($refused, array_keys($permits));
00337         }
00338         else $refused = array();
00339         return $refused;
00340     }
00341 
00342     public function getRefusedListSQL ($a_type, $a_id, $s_type, $actionlist, $keyname) {
00343         $refused = $this->getRefusedList ($a_type, $a_id, $s_type, $actionlist);
00344         if (count($refused)) {
00345             $excludelist = implode("','", $refused);
00346             return " CAST($keyname AS CHAR) NOT IN ('$excludelist')";
00347         }
00348         return '';
00349     }
00350 
00351     public function listPermissions ($a_type, $a_id, $action) {
00352         $roles = $this->getAccessorRoles ($a_type, $a_id);
00353         $role_list = "IN ('".implode("','", $roles)."')";
00354         $this->database->setQuery("SELECT DISTINCT subject_type FROM #__permissions WHERE role $role_list AND action='$action' AND (control & 2) ORDER BY subject_type");
00355         $subjects = $this->database->loadResultArray();
00356         return $subjects;
00357     }
00358 
00359     public function &listUserPermissions ($action) {
00360         $user = aliroUser::getInstance();
00361         $results = $this->listPermissions ('aUser', $user->id, $action);
00362         return $results;
00363     }
00364 
00365     public function listAccessors ($accessor_type, $role) {
00366         $this->database->setQuery("SELECT access_id FROM #__assignments WHERE access_type = '$accessor_type' AND role = '$role'");
00367         $result = $this->database->loadResultArray();
00368         return $result ? $result : array();
00369     }
00370 
00371     //***** The following are emulations of old ACL functions for backwards compatibility
00372     public function acl_check($aco_section_value, $aco_value,
00373         $aro_section_value, $aro_value, $axo_section_value=NULL, $axo_value=NULL) {
00374         if ($axo_section_value == 'components') return $this->checkUserPermission ($aro_value, 'aliroComponent', $axo_value);
00375         return false;
00376     }
00377 
00378     public function getAroGroup ($id) {
00379         $old_roles = array_keys ($this->old_groupids);
00380         array_unshift($old_roles, '');
00381         $roles = $this->getAccessorRoles('aUser', $id);
00382         $max = 0;
00383         foreach ($roles as $role) {
00384             $key  = array_search($role, $old_roles);
00385             if ($key AND $key > $max) $max = $key;
00386         }
00387         $result = new stdClass();
00388         $result->name = $old_roles[$max];
00389         return $result;
00390     }
00391 
00392     public function get_group_name ($gid) {
00393         if (is_int($gid)) {
00394             $group = array_search($gid, $this->old_groupids);
00395             return $group;
00396         }
00397         return $gid;
00398     }
00399 
00400     public function get_group_children_tree ($root_id=null, $root_name=null, $inclusive=true) {
00401         if (null == $root_id AND true == $inclusive) {
00402             if ('Registered' == $root_name) {
00403                 $result = unserialize('a:4:{i:0;O:8:"stdClass":2:{s:5:"value";s:2:"18";s:4:"text";s:17:"-&nbsp;Registered";}i:1;O:8:"stdClass":2:{s:5:"value";s:2:"19";s:4:"text";s:49:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;Author";}i:2;O:8:"stdClass":2:{s:5:"value";s:2:"20";s:4:"text";s:85:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;Editor";}i:3;O:8:"stdClass":2:{s:5:"value";s:2:"21";s:4:"text";s:124:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;Publisher";}}');
00404                 return $result;
00405             }
00406             if ('Public Backend' == $root_name) {
00407                 $result = unserialize('a:4:{i:0;O:8:"stdClass":2:{s:5:"value";s:2:"30";s:4:"text";s:21:"-&nbsp;Public Backend";}i:1;O:8:"stdClass":2:{s:5:"value";s:2:"23";s:4:"text";s:50:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;Manager";}i:2;O:8:"stdClass":2:{s:5:"value";s:2:"24";s:4:"text";s:92:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;Administrator";}i:3;O:8:"stdClass":2:{s:5:"value";s:2:"25";s:4:"text";s:134:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;Super Administrator";}}');
00408                 return $result;
00409             }
00410             trigger_error('Aliro emulation of get_group_children_tree needs extending');
00411         }
00412         else {
00413             foreach ($this->getAllRoles(true) as $i=>$role) $option[] = aliroHTML::makeOption($role, $role);
00414             return isset($option) ? $option : array();
00415         }
00416     }
00417 
00418     public function get_object_groups ($object_section_value, $object_value, $object_type=NULL) {
00419         return $this->getAllRoles(true);
00420     }
00421 
00422     public function get_group_children ($root_id=null, $root_name=null, $inclusive=true) {
00423         return array();
00424     }
00425 
00426 }

Generated on Wed May 14 13:01:55 2008 for ALIRO by  doxygen 1.5.5