ILIAS  release_8 Revision v8.23
class.ilRbacSystem.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
23 
34 {
35  private const MAX_CACHE_ENTRIES = 1000;
36 
37  protected static ?ilRbacSystem $instance = null;
38 
39  protected array $mem_view = [];
40 
41  protected static array $user_role_cache = [];
42 
43  // Cache accesses to RBAC PA
44  private static array $_paCache = [];
45 
46  // Cache outcomes of calls to checkAccessOfuser
47  private static array $_checkAccessOfUserCache = [];
48 
49  protected ilObjUser $user;
50  protected ilDBInterface $db;
51  protected ilRbacReview $review;
53  protected ilTree $tree;
55  protected Factory $refinery;
56 
60  protected function __construct()
61  {
62  global $DIC;
63 
64  $this->user = $DIC->user();
65  $this->db = $DIC->database();
66  $this->review = $DIC->rbac()->review();
67  $this->objectDataCache = $DIC['ilObjDataCache'];
68  $this->tree = $DIC->repositoryTree();
69  $this->http = $DIC->http();
70  $this->refinery = $DIC->refinery();
71  }
72 
73  public static function getInstance(): ilRbacSystem
74  {
75  if (self::$instance === null) {
76  self::$instance = new self();
77  }
78  return self::$instance;
79  }
80 
84  public static function resetCaches(): void
85  {
86  self::$user_role_cache = [];
87  self::$_paCache = [];
88  self::$_checkAccessOfUserCache = [];
89  }
90 
108  public function checkAccess(string $a_operations, int $a_ref_id, string $a_type = ""): bool
109  {
110  return $this->checkAccessOfUser($this->user->getId(), $a_operations, $a_ref_id, $a_type);
111  }
112 
113  public function checkAccessOfUser(int $a_user_id, string $a_operations, int $a_ref_id, string $a_type = ""): bool
114  {
115  // Create the user cache key
116  $cacheKey = $a_user_id . ':' . $a_operations . ':' . $a_ref_id . ':' . $a_type;
117 
118  // Create the cache if it does not yet exist
119  if (!is_array(self::$_checkAccessOfUserCache)) {
120  self::$_checkAccessOfUserCache = [];
121  }
122  // Try to return result from cache
123  if (array_key_exists($cacheKey, self::$_checkAccessOfUserCache)) {
124  return self::$_checkAccessOfUserCache[$cacheKey];
125  }
126 
127  // Check For owner
128  // Owners do always have full access to their objects
129  // Excluded are some of the permissions like create, perm, learning progress.
130  // This method call return all operations that are NOT granted by the owner status
131  if (!$a_operations = $this->filterOwnerPermissions($a_user_id, $a_operations, $a_ref_id)) {
132  // Store positive outcome in cache.
133  // Note: we only cache up to 1000 results to avoid memory overflows
134  if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
135  self::$_checkAccessOfUserCache[$cacheKey] = true;
136  }
137  return true;
138  }
139 
140  // get roles using role cache
141  $roles = $this->fetchAssignedRoles($a_user_id, $a_ref_id);
142 
143  // exclude system role from rbac
144  if (in_array(SYSTEM_ROLE_ID, $roles)) {
145  // Store positive outcome in cache.
146  // Note: we only cache up to 1000 results to avoid memory overflows
147  if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
148  self::$_checkAccessOfUserCache[$cacheKey] = true;
149  }
150  return true;
151  }
152 
153  // Create the PA cache if it does not exist yet
154  $paCacheKey = $a_user_id . ':' . $a_ref_id;
155  if (!is_array(self::$_paCache)) {
156  self::$_paCache = array();
157  }
158 
159  if (array_key_exists($paCacheKey, self::$_paCache)) {
160  // Return result from PA cache
161  $ops = self::$_paCache[$paCacheKey];
162  } else {
163  // Data is not in PA cache, perform database query
164  $q = "SELECT * FROM rbac_pa " .
165  "WHERE ref_id = " . $this->db->quote($a_ref_id, 'integer');
166 
167  $r = $this->db->query($q);
168 
169  $ops = [];
170  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
171  if (in_array((int) $row->rol_id, $roles)) {
172  $ops = array_merge($ops, unserialize(stripslashes($row->ops_id)));
173  }
174  }
175  // Cache up to 1000 entries in the PA cache
176  if (count(self::$_paCache) < self::MAX_CACHE_ENTRIES) {
177  self::$_paCache[$paCacheKey] = $ops;
178  }
179  }
180 
181  $operations = explode(",", $a_operations);
182  foreach ($operations as $operation) {
183  if ($operation == "create") {
184  if (empty($a_type)) {
185  throw new DomainException(
186  'checkAccess(): ' . "Expect a type definition for checking a 'create' permission"
187  );
188  }
189  $ops_id = ilRbacReview::_getOperationIdByName($operation . "_" . $a_type);
190  } else {
191  $ops_id = ilRbacReview::_getOperationIdByName($operation);
192  }
193  if (!in_array($ops_id, (array) $ops)) {
194  if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
195  self::$_checkAccessOfUserCache[$cacheKey] = false;
196  }
197  return false;
198  }
199  }
200 
201  // Store positive outcome in cache.
202  // Note: we only cache up to 1000 results to avoid memory overflows
203  if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
204  //$ilLog->write('PERMISSION: '.$a_ref_id.' -> '.$ops_id.' granted');
205  self::$_checkAccessOfUserCache[$cacheKey] = true;
206  }
207  return true;
208  }
209 
210  public function preloadRbacPaCache(array $a_ref_ids, int $a_user_id): void
211  {
212  $ref_ids = [];
213  $roles = $ops = [];
214  foreach ($a_ref_ids as $ref_id) {
215  if (!isset(self::$_paCache[$a_user_id . ":" . $ref_id])) {
216  $roles[$ref_id] = $this->fetchAssignedRoles($a_user_id, $ref_id);
217  $ops[$ref_id] = [];
218  $ref_ids[] = $ref_id;
219  }
220  }
221 
222  if ($ref_ids !== []) {
223  // Data is not in PA cache, perform database query
224  $q = "SELECT * FROM rbac_pa " .
225  "WHERE " . $this->db->in("ref_id", $ref_ids, false, "integer");
226 
227  $r = $this->db->query($q);
228 
229  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
230  if (in_array($row->rol_id, $roles[(int) $row->ref_id])) {
231  $ops[(int) $row->ref_id] = array_merge(
232  $ops[(int) $row->ref_id],
233  unserialize(stripslashes($row->ops_id))
234  );
235  }
236  }
237  foreach ($a_ref_ids as $ref_id) {
238  // #11313
239  if (!isset(self::$_paCache[$a_user_id . ":" . $ref_id])) {
240  self::$_paCache[$a_user_id . ":" . $ref_id] = $ops[$ref_id];
241  }
242  }
243  }
244  }
245 
249  public function checkPermission(int $a_ref_id, int $a_rol_id, string $a_operation): bool
250  {
251  $ops = [];
252  $query = 'SELECT ops_id FROM rbac_operations ' .
253  'WHERE operation = ' . $this->db->quote($a_operation, 'text');
254  $res = $this->db->query($query);
255  $ops_id = 0;
256  while ($row = $this->db->fetchObject($res)) {
257  $ops_id = (int) $row->ops_id;
258  }
259 
260  $query = "SELECT * FROM rbac_pa " .
261  "WHERE rol_id = " . $this->db->quote($a_rol_id, 'integer') . " " .
262  "AND ref_id = " . $this->db->quote($a_ref_id, 'integer') . " ";
263  $res = $this->db->query($query);
264 
265  while ($row = $this->db->fetchObject($res)) {
266  $ops = array_merge($ops, unserialize($row->ops_id));
267  }
268  return in_array($ops_id, $ops);
269  }
270 
271  protected function filterOwnerPermissions(int $a_user_id, string $a_operations, int $a_ref_id): string
272  {
273  // member view constraints
274  if (($this->mem_view['active'] ?? null) and $a_user_id == $this->user->getId()) {
275  if (in_array($a_ref_id, $this->mem_view['items'])) {
276  return $a_operations;
277  }
278  }
279 
280  if ($a_user_id != $this->objectDataCache->lookupOwner($this->objectDataCache->lookupObjId($a_ref_id))) {
281  return $a_operations;
282  }
283  // Is owner
284  $new_ops = '';
285  foreach (explode(",", $a_operations) as $operation) {
286  if ($operation != 'cat_administrate_users' &&
287  $operation != 'edit_permission' &&
288  $operation != 'edit_learning_progress' &&
289  $operation != 'read_learning_progress' &&
290  !preg_match('/^create/', $operation) &&
291  $operation != 'read_outcomes'
292  ) {
293  continue;
294  }
295  if (!strlen($new_ops)) {
296  $new_ops = $operation;
297  } else {
298  $new_ops .= (',' . $operation);
299  }
300  }
301  return $new_ops;
302  }
303 
308  private function fetchAssignedRoles(int $a_usr_id, int $a_ref_id): array
309  {
310  // Member view constraints
311  if (isset($this->mem_view['active']) && $this->mem_view['active'] && $a_usr_id == $this->user->getId()) {
312  // check if current ref_id is subitem of active container
313  if (in_array($a_ref_id, $this->mem_view['items']) && $this->mem_view['role']) {
314  // Return default member role
315  return [$this->mem_view['role']];
316  }
317  }
318 
319  if (isset(self::$user_role_cache[$a_usr_id]) and is_array(self::$user_role_cache)) {
320  return self::$user_role_cache[$a_usr_id];
321  }
322  return self::$user_role_cache[$a_usr_id] = $this->review->assignedRoles($a_usr_id);
323  }
324 
325  public function initMemberView(): void
326  {
328  $member_view_activation = null;
329  if ($this->http->wrapper()->query()->has('mv')) {
330  $member_view_activation = $this->http->wrapper()->query()->retrieve(
331  'mv',
332  $this->refinery->kindlyTo()->bool()
333  );
334  }
335  $ref_id = 0;
336  if ($this->http->wrapper()->query()->has('ref_id')) {
337  $ref_id = $this->http->wrapper()->query()->retrieve(
338  'ref_id',
339  $this->refinery->kindlyTo()->int()
340  );
341  }
342  if ($member_view_activation === true) {
343  if ($this->checkAccess('write', $ref_id)) {
344  $settings->toggleActivation($ref_id, true);
345  self::resetCaches();
346  }
347  }
348  if ($member_view_activation === false) {
349  $settings->toggleActivation($ref_id, false);
350  }
351  if (!$settings->isActive()) {
352  $this->mem_view['active'] = false;
353  $this->mem_view['items'] = [];
354  $this->mem_view['role'] = 0;
355  } else {
356  $this->mem_view['active'] = true;
357  $this->mem_view['items'] = $this->tree->getSubTreeIds($settings->getContainer());
358  $this->mem_view['items'] = array_merge($this->mem_view['items'], array($settings->getContainer()));
359  $this->mem_view['role'] = ilParticipants::getDefaultMemberRole($settings->getContainer());
360  }
361  }
362 
363  public function addTemporaryRole(int $a_usr_id, int $a_role_id): void
364  {
365  if (!in_array($a_role_id, self::$user_role_cache[$a_usr_id])) {
366  self::$user_role_cache[$a_usr_id][] = $a_role_id;
367  }
368  }
369 
370  public function resetPACache(int $a_usr_id, int $a_ref_id): void
371  {
372  $paCacheKey = $a_usr_id . ':' . $a_ref_id;
373  unset(self::$_paCache[$paCacheKey]);
374  }
375 } // END class.RbacSystem
Interface GlobalHttpState.
preloadRbacPaCache(array $a_ref_ids, int $a_user_id)
$res
Definition: ltiservices.php:69
array $settings
Setting values (LTI parameters, custom parameters and local parameters).
Definition: System.php:200
filterOwnerPermissions(int $a_user_id, string $a_operations, int $a_ref_id)
const SYSTEM_ROLE_ID
Definition: constants.php:29
static getDefaultMemberRole(int $a_ref_id)
static resetCaches()
Reset internal caches.
checkAccessOfUser(int $a_user_id, string $a_operations, int $a_ref_id, string $a_type="")
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
checkPermission(int $a_ref_id, int $a_rol_id, string $a_operation)
check if a specific role has the permission &#39;$a_operation&#39; of an object
global $DIC
Definition: feed.php:28
checkAccess(string $a_operations, int $a_ref_id, string $a_type="")
checkAccess represents the main method of the RBAC-system in ILIAS3 developers want to use With this ...
ilRbacReview $review
$ref_id
Definition: ltiauth.php:67
static http()
Fetches the global http state from ILIAS.
addTemporaryRole(int $a_usr_id, int $a_role_id)
ilObjectDataCache $objectDataCache
GlobalHttpState $http
static ilRbacSystem $instance
$query
ilDBInterface $db
static array $_paCache
static _getOperationIdByName(string $a_operation)
get operation id by name of operation
static array $_checkAccessOfUserCache
resetPACache(int $a_usr_id, int $a_ref_id)
static array $user_role_cache
__construct()
Constructor.
fetchAssignedRoles(int $a_usr_id, int $a_ref_id)
Fetch assigned roles This method caches the assigned roles per user.