ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilRbacSystem.php
Go to the documentation of this file.
1<?php
2
19declare(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;
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 {
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);
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
Builds data types.
Definition: Factory.php:36
User class.
class ilObjectDataCache
static getDefaultMemberRole(int $a_ref_id)
class ilRbacReview Contains Review functions of core Rbac.
static _getOperationIdByName(string $a_operation)
get operation id by name of operation
class ilRbacSystem system function like checkAccess, addActiveRole ... Supporting system functions ar...
ilDBInterface $db
addTemporaryRole(int $a_usr_id, int $a_role_id)
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 ...
fetchAssignedRoles(int $a_usr_id, int $a_ref_id)
Fetch assigned roles This method caches the assigned roles per user.
static resetCaches()
Reset internal caches.
static array $user_role_cache
__construct()
Constructor.
checkAccessOfUser(int $a_user_id, string $a_operations, int $a_ref_id, string $a_type="")
preloadRbacPaCache(array $a_ref_ids, int $a_user_id)
static array $_checkAccessOfUserCache
resetPACache(int $a_usr_id, int $a_ref_id)
ilObjectDataCache $objectDataCache
static array $_paCache
static ilRbacSystem $instance
checkPermission(int $a_ref_id, int $a_rol_id, string $a_operation)
check if a specific role has the permission '$a_operation' of an object
ilRbacReview $review
GlobalHttpState $http
filterOwnerPermissions(int $a_user_id, string $a_operations, int $a_ref_id)
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
const SYSTEM_ROLE_ID
Definition: constants.php:29
Interface GlobalHttpState.
Interface ilDBInterface.
$ref_id
Definition: ltiauth.php:66
static http()
Fetches the global http state from ILIAS.
global $DIC
Definition: shib_login.php:26
$q
Definition: shib_logout.php:23