ILIAS  trunk Revision v11.0_alpha-1843-g9e1fad99175
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilRbacSystem.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
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 = [];
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 ($row->ops_id === ':') {
172  continue;
173  }
174  if (in_array((int) $row->rol_id, $roles)) {
175  $ops = array_merge($ops, unserialize(stripslashes($row->ops_id)));
176  }
177  }
178  // Cache up to 1000 entries in the PA cache
179  if (count(self::$_paCache) < self::MAX_CACHE_ENTRIES) {
180  self::$_paCache[$paCacheKey] = $ops;
181  }
182  }
183 
184  $operations = explode(",", $a_operations);
185  foreach ($operations as $operation) {
186  if ($operation == "create") {
187  if (empty($a_type)) {
188  throw new DomainException(
189  'checkAccess(): ' . "Expect a type definition for checking a 'create' permission"
190  );
191  }
192  $ops_id = ilRbacReview::_getOperationIdByName($operation . "_" . $a_type);
193  } else {
194  $ops_id = ilRbacReview::_getOperationIdByName($operation);
195  }
196  if (!in_array($ops_id, (array) $ops)) {
197  if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
198  self::$_checkAccessOfUserCache[$cacheKey] = false;
199  }
200  return false;
201  }
202  }
203 
204  // Store positive outcome in cache.
205  // Note: we only cache up to 1000 results to avoid memory overflows
206  if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
207  //$ilLog->write('PERMISSION: '.$a_ref_id.' -> '.$ops_id.' granted');
208  self::$_checkAccessOfUserCache[$cacheKey] = true;
209  }
210  return true;
211  }
212 
213  public function preloadRbacPaCache(array $a_ref_ids, int $a_user_id): void
214  {
215  $ref_ids = [];
216  $roles = $ops = [];
217  foreach ($a_ref_ids as $ref_id) {
218  if (!isset(self::$_paCache[$a_user_id . ":" . $ref_id])) {
219  $roles[$ref_id] = $this->fetchAssignedRoles($a_user_id, $ref_id);
220  $ops[$ref_id] = [];
221  $ref_ids[] = $ref_id;
222  }
223  }
224 
225  if ($ref_ids !== []) {
226  // Data is not in PA cache, perform database query
227  $q = "SELECT * FROM rbac_pa " .
228  "WHERE " . $this->db->in("ref_id", $ref_ids, false, "integer");
229 
230  $r = $this->db->query($q);
231 
232  while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
233  if($row->ops_id === ':') {
234  continue;
235  }
236  if (in_array($row->rol_id, $roles[(int) $row->ref_id])) {
237  $ops[(int) $row->ref_id] = array_merge(
238  $ops[(int) $row->ref_id],
239  unserialize($row->ops_id)
240  );
241  }
242  }
243  foreach ($a_ref_ids as $ref_id) {
244  // #11313
245  if (!isset(self::$_paCache[$a_user_id . ":" . $ref_id])) {
246  self::$_paCache[$a_user_id . ":" . $ref_id] = $ops[$ref_id];
247  }
248  }
249  }
250  }
251 
255  public function checkPermission(int $a_ref_id, int $a_rol_id, string $a_operation): bool
256  {
257  $ops = [];
258  $query_rbac_operations = 'SELECT ops_id FROM rbac_operations ' .
259  'WHERE operation = ' . $this->db->quote($a_operation, 'text');
260  $res_rbac_operations = $this->db->query($query_rbac_operations);
261  $ops_id = 0;
262  while ($row = $this->db->fetchObject($res_rbac_operations)) {
263  $ops_id = (int) $row->ops_id;
264  }
265 
266  $query_rbac_pa = "SELECT * FROM rbac_pa " .
267  "WHERE rol_id = " . $this->db->quote($a_rol_id, 'integer') . " " .
268  "AND ref_id = " . $this->db->quote($a_ref_id, 'integer') . " ";
269  $res_rbac_pa = $this->db->query($query_rbac_pa);
270 
271  while ($row = $this->db->fetchObject($res_rbac_pa)) {
272  if ($row->ops_id === ':') {
273  continue;
274  }
275  $ops = array_merge($ops, unserialize($row->ops_id));
276  }
277  return in_array($ops_id, $ops);
278  }
279 
280  protected function filterOwnerPermissions(int $a_user_id, string $a_operations, int $a_ref_id): string
281  {
282  // member view constraints
283  if (($this->mem_view['active'] ?? null) and $a_user_id == $this->user->getId()) {
284  if (in_array($a_ref_id, $this->mem_view['items'])) {
285  return $a_operations;
286  }
287  }
288 
289  if ($a_user_id != $this->objectDataCache->lookupOwner($this->objectDataCache->lookupObjId($a_ref_id))) {
290  return $a_operations;
291  }
292  // Is owner
293  $new_ops = '';
294  foreach (explode(",", $a_operations) as $operation) {
295  if ($operation != 'cat_administrate_users' &&
296  $operation != 'edit_permission' &&
297  $operation != 'edit_learning_progress' &&
298  $operation != 'read_learning_progress' &&
299  !preg_match('/^create/', $operation) &&
300  $operation != 'read_outcomes'
301  ) {
302  continue;
303  }
304  if (!strlen($new_ops)) {
305  $new_ops = $operation;
306  } else {
307  $new_ops .= (',' . $operation);
308  }
309  }
310  return $new_ops;
311  }
312 
317  private function fetchAssignedRoles(int $a_usr_id, int $a_ref_id): array
318  {
319  // Member view constraints
320  if (isset($this->mem_view['active']) && $this->mem_view['active'] && $a_usr_id == $this->user->getId()) {
321  // check if current ref_id is subitem of active container
322  if (in_array($a_ref_id, $this->mem_view['items']) && $this->mem_view['role']) {
323  // Return default member role
324  return [$this->mem_view['role']];
325  }
326  }
327 
328  if (isset(self::$user_role_cache[$a_usr_id]) and is_array(self::$user_role_cache)) {
329  return self::$user_role_cache[$a_usr_id];
330  }
331  return self::$user_role_cache[$a_usr_id] = $this->review->assignedRoles($a_usr_id);
332  }
333 
334  public function initMemberView(): void
335  {
336  $settings = ilMemberViewSettings::getInstance();
337  $member_view_activation = null;
338  if ($this->http->wrapper()->query()->has('mv')) {
339  $member_view_activation = $this->http->wrapper()->query()->retrieve(
340  'mv',
341  $this->refinery->kindlyTo()->bool()
342  );
343  }
344  $ref_id = 0;
345  if ($this->http->wrapper()->query()->has('ref_id')) {
346  $ref_id = $this->http->wrapper()->query()->retrieve(
347  'ref_id',
348  $this->refinery->kindlyTo()->int()
349  );
350  }
351  if ($member_view_activation === true) {
352  if ($this->checkAccess('write', $ref_id)) {
353  $settings->toggleActivation($ref_id, true);
354  self::resetCaches();
355  }
356  }
357  if ($member_view_activation === false) {
358  $settings->toggleActivation($ref_id, false);
359  }
360  if (!$settings->isActive()) {
361  $this->mem_view['active'] = false;
362  $this->mem_view['items'] = [];
363  $this->mem_view['role'] = 0;
364  } else {
365  $this->mem_view['active'] = true;
366  $this->mem_view['items'] = $this->tree->getSubTreeIds($settings->getContainer());
367  $this->mem_view['items'] = array_merge($this->mem_view['items'], [$settings->getContainer()]);
368  $this->mem_view['role'] = ilParticipants::getDefaultMemberRole($settings->getContainer());
369  }
370  }
371 
372  public function addTemporaryRole(int $a_usr_id, int $a_role_id): void
373  {
374  if (!in_array($a_role_id, self::$user_role_cache[$a_usr_id])) {
375  self::$user_role_cache[$a_usr_id][] = $a_role_id;
376  }
377  }
378 
379  public function resetPACache(int $a_usr_id, int $a_ref_id): void
380  {
381  $paCacheKey = $a_usr_id . ':' . $a_ref_id;
382  unset(self::$_paCache[$paCacheKey]);
383  }
384 } // END class.RbacSystem
preloadRbacPaCache(array $a_ref_ids, int $a_user_id)
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="")
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
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
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:65
static http()
Fetches the global http state from ILIAS.
addTemporaryRole(int $a_usr_id, int $a_role_id)
ilObjectDataCache $objectDataCache
global $DIC
Definition: shib_login.php:22
GlobalHttpState $http
static ilRbacSystem $instance
ilDBInterface $db
static array $_paCache
static _getOperationIdByName(string $a_operation)
get operation id by name of operation
$q
Definition: shib_logout.php:21
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.
$r