3 * FusionForge RBAC engine
5 * Copyright 2010, Roland Mas
6 * Copyright 2012, Thorsten “mirabilos” Glaser <t.glaser@tarent.de>
7 * Copyright 2014,2019, Franck Villaume - TrivialDev
9 * This file is part of FusionForge. FusionForge is free software;
10 * you can redistribute it and/or modify it under the terms of the
11 * GNU General Public License as published by the Free Software
12 * Foundation; either version 2 of the Licence, or (at your option)
15 * FusionForge is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 require_once $gfcommon.'include/RBAC.php';
28 * TODO: Enter description here ...
31 class RBACEngine extends FFError implements PFO_RBACEngine {
32 private static $_instance;
33 private $_cached_roles = array ();
34 private $_cached_available_roles = NULL;
35 private $_cached_global_roles = NULL;
36 private $_cached_public_roles = NULL;
38 // singleton constructor
39 public static function getInstance() {
40 if (!isset(self::$_instance)) {
42 self::$_instance = new $c;
45 return self::$_instance;
49 * @see PFO_RBACEngine::getAvailableRoles()
51 public function getAvailableRoles() {
52 if ($this->_cached_available_roles != NULL) {
53 return $this->_cached_available_roles;
56 $this->_cached_available_roles = array ();
58 $this->_cached_available_roles[] = RoleAnonymous::getInstance();
60 if (session_loggedin()) {
61 $this->_cached_available_roles[] = RoleLoggedIn::getInstance();
62 $user = session_get_user();
64 $res = db_query_params ('SELECT role_id FROM pfo_user_role WHERE user_id=$1',
65 array ($user->getID()));
66 while ($arr = db_fetch_array($res)) {
67 $this->_cached_available_roles[] = $this->getRoleById ($arr['role_id']);
72 $params['current_roles'] = $this->_cached_available_roles;
73 $params['new_roles'] = array();
74 plugin_hook_by_reference('get_extra_roles', $params);
75 foreach ($params['new_roles'] as $r) {
76 $this->addAvailableRole($r);
80 $params['current_roles'] = $this->_cached_available_roles;
81 $params['dropped_roles'] = array();
82 plugin_hook_by_reference('restrict_roles', $params);
83 foreach ($params['dropped_roles'] as $r) {
84 $this->dropAvailableRole($r);
87 return $this->_cached_available_roles;
90 private function addAvailableRole($role) {
92 foreach ($this->_cached_available_roles as $r) {
93 if ($r->getID() == $role->getID()) {
98 $this->_cached_available_roles[] = $role;
102 private function dropAvailableRole($role) {
103 $new_roles = array();
104 foreach ($this->_cached_available_roles as $r) {
105 if ($r->getID() != $role->getID()) {
109 $this->_cached_available_roles = $new_roles;
112 public function getGlobalRoles() {
113 if ($this->_cached_global_roles != NULL) {
114 return $this->_cached_global_roles;
117 $this->_cached_global_roles = array ();
119 $res = db_query_params ('SELECT role_id FROM pfo_role WHERE home_group_id IS NULL',
121 while ($arr = db_fetch_array($res)) {
122 $this->_cached_global_roles[] = $this->getRoleById ($arr['role_id']);
125 return $this->_cached_global_roles;
128 public function getPublicRoles() {
129 if ($this->_cached_public_roles != NULL) {
130 return $this->_cached_public_roles;
133 $this->_cached_public_roles = array ();
135 $res = db_query_params ('SELECT role_id FROM pfo_role WHERE is_public=$1',
137 while ($arr = db_fetch_array($res)) {
138 $this->_cached_public_roles[] = $this->getRoleById ($arr['role_id']);
141 return $this->_cached_public_roles;
144 public function invalidateRoleCaches () {
145 $this->_cached_roles = array();
146 $this->_cached_available_roles = NULL;
147 $this->_cached_global_roles = NULL;
148 $this->_cached_public_roles = NULL;
151 public function getAvailableRolesForUser($user) {
154 $result[] = RoleAnonymous::getInstance();
155 $result[] = RoleLoggedIn::getInstance();
157 $uid_s = is_object($user) ? $user->getID() : $user;
158 $uid = util_nat0($uid_s);
159 if ($uid === false) {
160 /* no valid number; would make Postgres error out */
164 $res = db_query_params ('SELECT role_id FROM pfo_user_role WHERE user_id=$1',
166 while ($arr = db_fetch_array($res)) {
167 $result[] = $this->getRoleById ($arr['role_id']);
174 * @see PFO_RBACEngine::isActionAllowed()
176 public function isActionAllowed ($section, $reference, $action = NULL) {
177 $rlist = $this->getAvailableRoles ();
178 foreach ($rlist as $r) {
179 if ($r->hasPermission ($section, $reference, $action)) {
186 public function isGlobalActionAllowed ($section, $action = NULL) {
187 return $this->isActionAllowed ($section, -1, $action);
190 public function isActionAllowedForUser ($user, $section, $reference, $action = NULL) {
191 $rlist = $this->getAvailableRolesForUser ($user);
192 foreach ($rlist as $r) {
193 if ($r->hasPermission ($section, $reference, $action)) {
200 public function isGlobalActionAllowedForUser ($user, $section, $action = NULL) {
201 return $this->isActionAllowedForUser ($user, $section, -1, $action);
204 public function getRoleById ($role_id) {
205 if (array_key_exists ($role_id, $this->_cached_roles)) {
206 return $this->_cached_roles[$role_id];
208 $res = db_query_params ('SELECT c.class_name, r.home_group_id FROM pfo_role r, pfo_role_class c WHERE r.role_class = c.class_id AND r.role_id = $1',
210 if (!$res || !db_numrows($res)) {
214 $class_id = db_result ($res, 0, 'class_name');
216 case 'PFO_RoleExplicit':
217 $group_id = db_result ($res, 0, 'home_group_id');
218 $group = group_get_object($group_id);
219 $this->_cached_roles[$role_id] = new Role ($group, $role_id);
220 return $this->_cached_roles[$role_id];
221 case 'PFO_RoleAnonymous':
222 $this->_cached_roles[$role_id] = RoleAnonymous::getInstance();
223 return $this->_cached_roles[$role_id];
224 case 'PFO_RoleLoggedIn':
225 $this->_cached_roles[$role_id] = RoleLoggedIn::getInstance();
226 return $this->_cached_roles[$role_id];
228 throw new Exception ("Not implemented");
232 public function getRolesByAllowedAction ($section, $reference, $action = NULL) {
233 $ids = $this->_getRolesIdByAllowedAction ($section, $reference, $action);
235 if (is_array($ids)) {
236 foreach ($ids as $role_id) {
237 $roles[] = $this->getRoleById ($role_id);
243 public function getUsersByAllowedAction ($section, $reference, $action = NULL) {
244 $roles = $this->getRolesByAllowedAction ($section, $reference, $action);
245 if ($roles === false) { // Error
248 $user_ids = array ();
249 foreach ($roles as $role) {
250 foreach ($role->getUsers() as $user) {
251 $user_ids[] = $user->getID();
255 $user_ids = array_unique ($user_ids);
257 return user_get_objects ($user_ids);
260 private function _getRolesIdByAllowedAction ($section, $reference, $action = NULL) {
262 $qpa = db_construct_qpa();
263 $qpa = db_construct_qpa($qpa,
264 'SELECT role_id FROM pfo_role_setting WHERE section_name=$1 AND ref_id=$2 ',
268 // Look for roles that are directly allowed to perform action
273 case 'approve_projects':
275 case 'approve_diary':
276 case 'project_admin':
278 case 'tracker_admin':
281 $qpa = db_construct_qpa($qpa, 'AND perm_val = 1');
287 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
290 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 1');
293 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 2');
300 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
303 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 1');
306 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 2');
313 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
316 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 1');
319 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 2');
322 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 3');
325 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 4');
332 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
335 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 1');
338 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 2');
341 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 3');
344 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 4');
351 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
354 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 1');
357 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 2');
359 case 'unmoderated_post':
360 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 3');
363 $qpa = db_construct_qpa($qpa, 'AND perm_val >= 4');
370 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
373 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 1) = 1');
376 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 2) = 2');
379 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 4) = 4');
382 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 8) = 8');
385 $qpa = db_construct_qpa ($qpa, 'AND (perm_val & 16) = 16');
392 $qpa = db_construct_qpa($qpa, 'AND perm_val != 0');
395 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 1) = 1');
398 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 2) = 2');
401 $qpa = db_construct_qpa($qpa, 'AND (perm_val & 4) = 4');
406 $hook_params = array ();
407 $hook_params['section'] = $section;
408 $hook_params['reference'] = $reference;
409 $hook_params['action'] = $action;
410 $hook_params['qpa'] = $qpa;
411 $hook_params['result'] = $result;
412 plugin_hook_by_reference ("list_roles_by_permission", $hook_params);
413 $qpa = $hook_params['qpa'];
417 $res = db_query_qpa ($qpa);
419 $this->setError('RBACEngine::getRolesByAllowedAction()::'.db_error());
422 while ($arr = db_fetch_array($res)) {
423 $result[] = $arr['role_id'];
426 // Also look for roles that can perform the action because they're more powerful
430 case 'approve_projects':
432 case 'approve_diary':
434 case 'project_admin':
435 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('forge_admin', -1));
438 case 'tracker_admin':
444 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('project_admin', $reference));
447 if ($action != 'tech' && $action != 'vote') {
448 $t = artifactType_get_object ($reference);
449 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('tracker_admin', $t->Group->getID()));
453 if ($action != 'tech') {
454 $t = projectgroup_get_object ($reference);
455 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('pm_admin', $t->Group->getID()));
459 $t = forum_get_object ($reference);
460 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('forum_admin', $t->Group->getID()));
463 $t = frspackage_get_object($reference);
464 $result = array_merge($result, $this->_getRolesIdByAllowedAction('frs_admin', $t->Group->getID(), 'admin'));
467 if ($action != 'tech' && $action != 'vote') {
468 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('tracker_admin', $reference));
472 if ($action != 'tech') {
473 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('pm_admin', $reference));
477 $result = array_merge ($result, $this->_getRolesIdByAllowedAction ('forum_admin', $reference));
480 $result = array_merge($result, $this->_getRolesIdByAllowedAction('frs_admin', $reference, 'admin'));
484 return array_unique ($result);
489 * Check if permission is allowed for an action on a reference in the context of a section
490 * @param string $section
491 * @param int $reference (group_id, ...)
492 * @param string $action
494 function forge_check_perm ($section, $reference, $action = NULL) {
495 $engine = RBACEngine::getInstance();
497 return $engine->isActionAllowed($section, $reference, $action);
501 * TODO: Enter description here ...
502 * @param string $section
503 * @param string $action
505 function forge_check_global_perm ($section, $action = NULL) {
506 $engine = RBACEngine::getInstance();
508 return $engine->isGlobalActionAllowed($section, $action);
511 function forge_check_perm_for_user ($user, $section, $reference, $action = NULL) {
512 $engine = RBACEngine::getInstance();
514 return $engine->isActionAllowedForUser($user, $section, $reference, $action);
517 function forge_check_global_perm_for_user ($user, $section, $action = NULL) {
518 $engine = RBACEngine::getInstance();
520 return $engine->isGlobalActionAllowedForUser($user, $section, $action);
523 function forge_cache_external_roles($group) {
524 global $used_external_roles, $unused_external_roles;
526 $used_external_roles = array();
527 $unused_external_roles = array();
528 $group_id = $group->getID();
530 foreach (RBACEngine::getInstance()->getPublicRoles() as $r) {
531 $grs = $r->getLinkedProjects();
533 foreach ($grs as $g) {
534 if ($g->getID() == $group_id) {
540 $unused_external_roles[] = $r;
544 foreach ($group->getRoles() as $r) {
545 if ($r->getHomeProject() == NULL ||
546 $r->getHomeProject()->getID() != $group_id) {
547 $used_external_roles[] = $r;
551 sortRoleList($used_external_roles, $group, 'composite');
552 sortRoleList($unused_external_roles, $group, 'composite');
557 // c-file-style: "bsd"