ILIAS  trunk Revision v13.0_alpha-52-g21c366bbdd4
class.ilRbacSystem.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
27
38{
39 private const MAX_CACHE_ENTRIES = 1000;
40
41 protected static ?ilRbacSystem $instance = null;
42
43 protected array $mem_view = [];
44
45 protected static array $user_role_cache = [];
46
47 // Cache accesses to RBAC PA
48 private static array $_paCache = [];
49
50 // Cache outcomes of calls to checkAccessOfuser
51 private static array $_checkAccessOfUserCache = [];
52
53
54
58 public function __construct(
59 private UserIdProviderProxy $user,
60 private Connection $db,
61 private ilRbacReview $review,
62 private RepositoryTreeAccessProxy $tree,
63 private GlobalHttpState $http,
64 private Factory $refinery,
65 private ObjectDataAccessProxy $objectDataCache
66 ) {
67 }
68
69 public static function getInstance(): ilRbacSystem
70 {
71 if (self::$instance !== null) {
72 return self::$instance;
73 }
74 global $DIC;
75 if ($DIC instanceof \ILIAS\DI\Container && isset($DIC['rbacsystem'])) {
76 return self::$instance = $DIC['rbacsystem'];
77 }
78 throw new \LogicException(
79 'ilRbacSystem::getInstance() requires a bootstrapped $DIC[\'rbacsystem\']; '
80 . 'register a mock via $DIC[\'rbacsystem\'] in tests.'
81 );
82 }
83
87 public static function resetCaches(): void
88 {
89 self::$user_role_cache = [];
90 self::$_paCache = [];
91 self::$_checkAccessOfUserCache = [];
92 }
93
111 public function checkAccess(string $a_operations, int $a_ref_id, string $a_type = ""): bool
112 {
113 return $this->checkAccessOfUser($this->user->getId(), $a_operations, $a_ref_id, $a_type);
114 }
115
116 public function checkAccessOfUser(int $a_user_id, string $a_operations, int $a_ref_id, string $a_type = ""): bool
117 {
118 // Create the user cache key
119 $cacheKey = $a_user_id . ':' . $a_operations . ':' . $a_ref_id . ':' . $a_type;
120
121 // Create the cache if it does not yet exist
122 if (!is_array(self::$_checkAccessOfUserCache)) {
123 self::$_checkAccessOfUserCache = [];
124 }
125 // Try to return result from cache
126 if (array_key_exists($cacheKey, self::$_checkAccessOfUserCache)) {
127 return self::$_checkAccessOfUserCache[$cacheKey];
128 }
129
130 // Check For owner
131 // Owners do always have full access to their objects
132 // Excluded are some of the permissions like create, perm, learning progress.
133 // This method call return all operations that are NOT granted by the owner status
134 if (!$a_operations = $this->filterOwnerPermissions($a_user_id, $a_operations, $a_ref_id)) {
135 // Store positive outcome in cache.
136 // Note: we only cache up to 1000 results to avoid memory overflows
137 if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
138 self::$_checkAccessOfUserCache[$cacheKey] = true;
139 }
140 return true;
141 }
142
143 // get roles using role cache
144 $roles = $this->fetchAssignedRoles($a_user_id, $a_ref_id);
145
146 // exclude system role from rbac
147 if (in_array(SYSTEM_ROLE_ID, $roles)) {
148 // Store positive outcome in cache.
149 // Note: we only cache up to 1000 results to avoid memory overflows
150 if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
151 self::$_checkAccessOfUserCache[$cacheKey] = true;
152 }
153 return true;
154 }
155
156 // Create the PA cache if it does not exist yet
157 $paCacheKey = $a_user_id . ':' . $a_ref_id;
158 if (!is_array(self::$_paCache)) {
159 self::$_paCache = [];
160 }
161
162 if (array_key_exists($paCacheKey, self::$_paCache)) {
163 // Return result from PA cache
164 $ops = self::$_paCache[$paCacheKey];
165 } else {
166 // Data is not in PA cache, perform database query
167 $q = "SELECT * FROM rbac_pa " .
168 "WHERE ref_id = " . $this->db->quote($a_ref_id, 'integer');
169
170 $r = $this->db->query($q);
171
172 $ops = [];
173 while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
174 if ($row->ops_id === ':') {
175 continue;
176 }
177 if (in_array((int) $row->rol_id, $roles)) {
178 $ops = array_merge($ops, unserialize(stripslashes($row->ops_id)));
179 }
180 }
181 // Cache up to 1000 entries in the PA cache
182 if (count(self::$_paCache) < self::MAX_CACHE_ENTRIES) {
183 self::$_paCache[$paCacheKey] = $ops;
184 }
185 }
186
187 $operations = explode(",", $a_operations);
188 foreach ($operations as $operation) {
189 if ($operation == "create") {
190 if (empty($a_type)) {
191 throw new DomainException(
192 'checkAccess(): ' . "Expect a type definition for checking a 'create' permission"
193 );
194 }
195 $ops_id = ilRbacReview::_getOperationIdByName($operation . "_" . $a_type);
196 } else {
197 $ops_id = ilRbacReview::_getOperationIdByName($operation);
198 }
199 if (!in_array($ops_id, (array) $ops)) {
200 if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
201 self::$_checkAccessOfUserCache[$cacheKey] = false;
202 }
203 return false;
204 }
205 }
206
207 // Store positive outcome in cache.
208 // Note: we only cache up to 1000 results to avoid memory overflows
209 if (count(self::$_checkAccessOfUserCache) < self::MAX_CACHE_ENTRIES) {
210 //$ilLog->write('PERMISSION: '.$a_ref_id.' -> '.$ops_id.' granted');
211 self::$_checkAccessOfUserCache[$cacheKey] = true;
212 }
213 return true;
214 }
215
216 public function preloadRbacPaCache(array $a_ref_ids, int $a_user_id): void
217 {
218 $ref_ids = [];
219 $roles = $ops = [];
220 foreach ($a_ref_ids as $ref_id) {
221 if (!isset(self::$_paCache[$a_user_id . ":" . $ref_id])) {
222 $roles[$ref_id] = $this->fetchAssignedRoles($a_user_id, $ref_id);
223 $ops[$ref_id] = [];
224 $ref_ids[] = $ref_id;
225 }
226 }
227
228 if ($ref_ids !== []) {
229 // Data is not in PA cache, perform database query
230 $q = "SELECT * FROM rbac_pa " .
231 "WHERE " . $this->db->in("ref_id", $ref_ids, false, "integer");
232
233 $r = $this->db->query($q);
234
235 while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
236 if ($row->ops_id === ':') {
237 continue;
238 }
239 if (in_array($row->rol_id, $roles[(int) $row->ref_id])) {
240 $ops[(int) $row->ref_id] = array_merge(
241 $ops[(int) $row->ref_id],
242 unserialize($row->ops_id)
243 );
244 }
245 }
246 foreach ($a_ref_ids as $ref_id) {
247 // #11313
248 if (!isset(self::$_paCache[$a_user_id . ":" . $ref_id])) {
249 self::$_paCache[$a_user_id . ":" . $ref_id] = $ops[$ref_id];
250 }
251 }
252 }
253 }
254
258 public function checkPermission(int $a_ref_id, int $a_rol_id, string $a_operation): bool
259 {
260 $ops = [];
261 $query_rbac_operations = 'SELECT ops_id FROM rbac_operations ' .
262 'WHERE operation = ' . $this->db->quote($a_operation, 'text');
263 $res_rbac_operations = $this->db->query($query_rbac_operations);
264 $ops_id = 0;
265 while ($row = $this->db->fetchObject($res_rbac_operations)) {
266 $ops_id = (int) $row->ops_id;
267 }
268
269 $query_rbac_pa = "SELECT * FROM rbac_pa " .
270 "WHERE rol_id = " . $this->db->quote($a_rol_id, 'integer') . " " .
271 "AND ref_id = " . $this->db->quote($a_ref_id, 'integer') . " ";
272 $res_rbac_pa = $this->db->query($query_rbac_pa);
273
274 while ($row = $this->db->fetchObject($res_rbac_pa)) {
275 if ($row->ops_id === ':') {
276 continue;
277 }
278 $ops = array_merge($ops, unserialize($row->ops_id));
279 }
280 return in_array($ops_id, $ops);
281 }
282
283 protected function filterOwnerPermissions(int $a_user_id, string $a_operations, int $a_ref_id): string
284 {
285 // member view constraints
286 if (($this->mem_view['active'] ?? null) and $a_user_id == $this->user->getId()) {
287 if (in_array($a_ref_id, $this->mem_view['items'])) {
288 return $a_operations;
289 }
290 }
291
292 if ($a_user_id != $this->objectDataCache->lookupOwner($this->objectDataCache->lookupObjId($a_ref_id))) {
293 return $a_operations;
294 }
295 // Is owner
296 $new_ops = '';
297 foreach (explode(",", $a_operations) as $operation) {
298 if ($operation != 'cat_administrate_users' &&
299 $operation != 'edit_permission' &&
300 $operation != 'edit_learning_progress' &&
301 $operation != 'read_learning_progress' &&
302 !preg_match('/^create/', $operation) &&
303 $operation != 'read_outcomes'
304 ) {
305 continue;
306 }
307 if (!strlen($new_ops)) {
308 $new_ops = $operation;
309 } else {
310 $new_ops .= (',' . $operation);
311 }
312 }
313 return $new_ops;
314 }
315
320 private function fetchAssignedRoles(int $a_usr_id, int $a_ref_id): array
321 {
322 // Member view constraints
323 if (isset($this->mem_view['active']) && $this->mem_view['active'] && $a_usr_id == $this->user->getId()) {
324 // check if current ref_id is subitem of active container
325 if (in_array($a_ref_id, $this->mem_view['items']) && $this->mem_view['role']) {
326 // Return default member role
327 return [$this->mem_view['role']];
328 }
329 }
330
331 if (isset(self::$user_role_cache[$a_usr_id]) and is_array(self::$user_role_cache)) {
332 return self::$user_role_cache[$a_usr_id];
333 }
334 return self::$user_role_cache[$a_usr_id] = $this->review->assignedRoles($a_usr_id);
335 }
336
337 public function initMemberView(): void
338 {
340 $member_view_activation = null;
341 if ($this->http->wrapper()->query()->has('mv')) {
342 $member_view_activation = $this->http->wrapper()->query()->retrieve(
343 'mv',
344 $this->refinery->kindlyTo()->bool()
345 );
346 }
347 $ref_id = 0;
348 if ($this->http->wrapper()->query()->has('ref_id')) {
349 $ref_id = $this->http->wrapper()->query()->retrieve(
350 'ref_id',
351 $this->refinery->kindlyTo()->int()
352 );
353 }
354 if ($member_view_activation === true) {
355 if ($this->checkAccess('write', $ref_id)) {
356 $settings->toggleActivation($ref_id, true);
358 }
359 }
360 if ($member_view_activation === false) {
361 $settings->toggleActivation($ref_id, false);
362 }
363 if (!$settings->isActive()) {
364 $this->mem_view['active'] = false;
365 $this->mem_view['items'] = [];
366 $this->mem_view['role'] = 0;
367 } else {
368 $this->mem_view['active'] = true;
369 $this->mem_view['items'] = $this->tree->getSubTreeIds($settings->getContainer());
370 $this->mem_view['items'] = array_merge($this->mem_view['items'], [$settings->getContainer()]);
371 $this->mem_view['role'] = ilParticipants::getDefaultMemberRole($settings->getContainer());
372 }
373 }
374
375 public function addTemporaryRole(int $a_usr_id, int $a_role_id): void
376 {
377 if (!in_array($a_role_id, self::$user_role_cache[$a_usr_id])) {
378 self::$user_role_cache[$a_usr_id][] = $a_role_id;
379 }
380 }
381
382 public function resetPACache(int $a_usr_id, int $a_ref_id): void
383 {
384 $paCacheKey = $a_usr_id . ':' . $a_ref_id;
385 unset(self::$_paCache[$paCacheKey]);
386 }
387
393 public function hasAnyAdminReadPermission(): bool
394 {
395
396 $roles = $this->fetchAssignedRoles($this->user->getId(), ROLE_FOLDER_ID);
397 if (in_array(SYSTEM_ROLE_ID, $roles)) {
398 return true;
399 }
400
401 $read_id = ilRbacReview::_getOperationIdByName('read');
402
403 $result = $this->db->queryF(
404 "
405 SELECT pa.ops_id
406 FROM tree t
407 JOIN rbac_pa pa ON pa.ref_id = t.child
408 JOIN rbac_ua ua ON ua.rol_id = pa.rol_id AND ua.usr_id = %s
409 WHERE t.parent = %s
410 ",
412 [$this->user->getId(), SYSTEM_FOLDER_ID]
413 );
414
415 while ($row = $result->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
416 if ($row->ops_id !== ':') {
417 $ops = unserialize(stripslashes($row->ops_id));
418 if (in_array($read_id, $ops)) {
419 return true;
420 }
421 }
422 }
423 return false;
424 }
425} // END class.RbacSystem
$settings
Definition: index.php:44
Builds data types.
Definition: Factory.php:36
const FETCHMODE_OBJECT
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...
__construct(private UserIdProviderProxy $user, private Connection $db, private ilRbacReview $review, private RepositoryTreeAccessProxy $tree, private GlobalHttpState $http, private Factory $refinery, private ObjectDataAccessProxy $objectDataCache)
Constructor.
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
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)
static array $_paCache
hasAnyAdminReadPermission()
Check if the current user has read permission to any sub node of the system folder This is used by th...
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
filterOwnerPermissions(int $a_user_id, string $a_operations, int $a_ref_id)
const SYSTEM_ROLE_ID
Definition: constants.php:29
const SYSTEM_FOLDER_ID
Definition: constants.php:35
const ROLE_FOLDER_ID
Definition: constants.php:34
Wrapper for the Setup to work, as they require a namespace.
Definition: Connection.php:27
Interface GlobalHttpState.
Class HTTPServicesTest.
static http()
Fetches the global http state from ILIAS.
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
global $DIC
Definition: shib_login.php:26