_load_content_privilege()   C
last analyzed

Complexity

Conditions 16
Paths 74

Size

Total Lines 55
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 16

Importance

Changes 0
Metric Value
cc 16
eloc 32
nc 74
nop 4
dl 0
loc 55
ccs 33
cts 33
cp 1
crap 16
rs 5.5666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package midcom.services
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * This class is responsible for ACL checks against classes and content objects.
11
 *
12
 * <b>Privilege definition</b>
13
 *
14
 * Privileges are represented by the class midcom_core_privilege and basically consist
15
 * of three parts: Name, Assignee and Value:
16
 *
17
 * The privilege name is a unique identifier for the privilege. The mRFC 15 defines the
18
 * syntax to be $component:$name, where $component is either the name of the component
19
 * or one of 'midgard' or 'midcom' for core privileges. Valid privilege names are for
20
 * example 'net.nehmer.static:do_something' or 'midgard:update'.
21
 *
22
 * The assignee is the entity to which the privilege applies, this can be one of several
23
 * things, depending on where the privilege is taken into effect, I'll explain this below
24
 * in more detail:
25
 *
26
 * On content objects (generally every object in the system used during 'normal operation'):
27
 *
28
 * - A Midgard User encapsulated by a midcom_core_user object.
29
 * - A Midgard Group encapsulated by a midcom_core_group object or subtype thereof.
30
 * - The magic assignee 'EVERYONE', which applies the privilege to every user unconditionally,
31
 *   even to unauthenticated users.
32
 * - The magic assignee 'USERS', which applies to all authenticated users.
33
 * - The magic assignee 'ANONYMOUS, which applies to all unauthenticated users.
34
 * - The magic assignee 'OWNER', which applies for all object owners.
35
 *
36
 * On users and groups during authentication (when building the basic privilege set for the user,
37
 * which applies generally):
38
 *
39
 * - The magic string 'SELF', which denotes that the privilege is set for the user in general for
40
 *   every content object. SELF privileges may be restricted to a class by using the classname
41
 *   property available at both midcom_core_privilege and various DBA interface functions.
42
 *
43
 * The value is one of MIDCOM_PRIVILEGE_ALLOW or MIDCOM_PRIVILEGE_DENY, which either grants or
44
 * revokes a privilege. Be aware, that unsetting a privilege does not set it to MIDCOM_PRIVILEGE_DENY,
45
 * but clears the entry completely, which means that the privilege value inherited from the parents
46
 * is now in effect.
47
 *
48
 * <b>How are privileges read and merged</b>
49
 *
50
 * First, you have to understand, that there are actually three distinct sources where a privilege
51
 * comes from: The systemwide defaults, the currently authenticated user and the content object
52
 * which is being operated on. We'll look into this distinction first, before we get on to the order
53
 * in which they are merged.
54
 *
55
 * <i>Systemwide default privileges</i>
56
 *
57
 * This is analogous to the MidCOM default configuration, they are taken into account globally to each
58
 * and every check whether a privilege is granted. Whenever a privilege is defined, there is also a
59
 * default value (either ALLOW or DENY) assigned to it. They serve as a basis for all privilege sets
60
 * and ensure that there is a value set for all privileges.
61
 *
62
 * These defaults are defined by the MidCOM core and the components respectively and are very restrictive,
63
 * basically granting read-only access to all non sensitive information.
64
 *
65
 * Currently, there is no way to influence these privileges unless you are a developer and writing new
66
 * components.
67
 *
68
 * <i>Class specific, systemwide default privileges (for magic assignees only)</i>
69
 *
70
 * Often you want to have a number of default privileges for certain classes in general. For regular
71
 * users/groups you can easily assign them to the corresponding users/groups, there is one special
72
 * case which cannot be covered there at this time: You cannot set defaults applicable for the magic
73
 * assignees EVERYONE, USERS and ANONYMOUS. This is normally only of interest for component authors,
74
 * which want to have some special privileges assigned for their objects, where the global defaults
75
 * do no longer suffice.
76
 *
77
 * These privileges are queried using a static callback of the DBA classes in question, see the following
78
 * example:
79
 *
80
 * <code>
81
 * public function get_class_magic_default_privileges() : array
82
 * {
83
 *     return [
84
 *         'EVERYONE' => [],
85
 *         'ANONYMOUS' => [],
86
 *         'USERS' => ['midcom:create' => MIDCOM_PRIVILEGE_ALLOW]
87
 *     ];
88
 * }
89
 * </code>
90
 *
91
 * See also the documentation of the $_default_magic_class_privileges member for further details.
92
 *
93
 * <i>User / Group specific privileges</i>
94
 *
95
 * This kind of privileges are rights, assigned directly to a user. Similar to the systemwide defaults,
96
 * they too apply to any operation done by the user / group respectively throughout the system. The magic
97
 * assignee SELF is used to denote such privileges, which can obviously only be assigned to users or
98
 * groups. These privileges are loaded at the time of user authentication only.
99
 *
100
 * You should use these privileges carefully, due to their global nature. If you assign the privilege
101
 * midgard:delete to a user, this means that the user can now delete all objects he can read, unless
102
 * there are again restricting privileges set to content objects.
103
 *
104
 * To be more flexible in the control over the top level objects, you may add a classname which restricts
105
 * the validity of the privilege to a class and all of its descendants.
106
 *
107
 * <i>Content object privileges</i>
108
 *
109
 * This is the kind of privilege that will be used most often. They are associated with any content
110
 * object in the system, and are read on every access to a content object. As you can see in the
111
 * introduction, you have the most flexibility here.
112
 *
113
 * The basic idea is that you can assign privileges based on the combination of users/groups and
114
 * content objects. In other words, you can say the user x has the privilege midgard:update for
115
 * this object (and its descendants) only. This works with groups as well.
116
 *
117
 * The possible assignees here are either a user, a group or one of the magic assignees EVERYONE,
118
 * USERS or ANONYMOUS, as outlined above.
119
 *
120
 * Be aware, that persons and groups are treted as content objects when loaded from the database
121
 * in a tool like org.openpsa.user, as the groups are not used for authentication but for
122
 * regular site operation there. Therefore, the SELF privileges mentioned above are not taken into
123
 * account when determining the content object privileges!
124
 *
125
 * <i>Privilege merging</i>
126
 *
127
 * This is where we get to the guts of privilege system, as this is not trivial (but nevertheless
128
 * straight-forward I hope). The general idea is based on the scope of object a privilege applies:
129
 *
130
 * System default privileges obviously have the largest scope, they apply to everyone. The next
131
 * smaller scope are privileges which are assigned to groups in general, followed by privileges
132
 * assigned directly to a user.
133
 *
134
 * From this point on, the privileges of the content objects are next in line, starting at the
135
 * top-level objects again (for example a root topic). The smallest scope finally then has the
136
 * object that is being accessed itself.
137
 *
138
 * Let us visualize this a bit:
139
 *
140
 * <pre>
141
 * ^ larger scope     System default privileges
142
 * |                  Class specific magic assignee default privileges
143
 * |                  Root Midgard group
144
 * |                  ... more parent Midgard groups ...
145
 * |                  Direct Midgard group membership
146
 * |                  User
147
 * |                  SELF privileges limited to a class
148
 * |                  Root content object
149
 * |                  ... more parent objects ...
150
 * v smaller scope    Accessed content object
151
 * </pre>
152
 *
153
 * Privileges assigned to a specific user always override owner privileges; owner privileges are
154
 * calculated on a per-content-object bases, and are merged just before the final user privileges are
155
 * merged into the privilege set. It is of no importance from where you get ownership at that point.
156
 *
157
 * Implementation notes: Internally, MidCOM separates the "user privilege set" which is everything
158
 * down to the line User above, and the content object privileges, which constitutes the rest.
159
 * This separation has been done for performance reasons, as the user's privileges are loaded
160
 * immediately upon authentication of the user, and the privileges of the actual content objects
161
 * are merged into this set then. Normally, this should be of no importance for ACL users, but it
162
 * explains the more complex graph in the original mRFC.
163
 *
164
 * <b>Predefined Privileges</b>
165
 *
166
 * The MidCOM core defines a set of core privileges, which fall in two categories:
167
 *
168
 * <i>Midgard Core Privileges</i>
169
 *
170
 * These privileges are part of the MidCOM Database Abstraction layer (MidCOM DBA) and have been
171
 * originally proposed by me in a mail to the Midgard developers list. Unless otherwise noted,
172
 * all privileges are denied by default and no difference between owner and normal default privileges
173
 * is made.
174
 *
175
 * - <i>midgard:read</i> controls read access to the object, if denied, you cannot load the object
176
 *   from the database. This privilege is granted by default.
177
 * - <i>midgard:update</i> controls updating of objects. Be aware that you need to be able to read
178
 *   the object before updating it, it is granted by default only for owners.
179
 * - <i>midgard:delete</i> controls deletion of objects. Be aware that you need to be able to read
180
 *   the object before updating it, it is granted by default only for owners.
181
 * - <i>midgard:create</i> allows you to create new content objects as children on whatever content
182
 *   object that you have the create privilege for. This means that you can create an article if and only
183
 *   if you have create permission for either the parent article (if you create a so-called 'reply
184
 *   article') or the parent topic, it is granted by default only for owners.
185
 * - <i>midgard:parameters</i> allows the manipulation of parameters on the current object if and
186
 *   only if the user also has the midgard:update privilege on the object. This privileges is granted
187
 *   by default and covers the full set of parameter operations (create, update and delete).
188
 * - <i>midgard:attachments</i> is analogous to midgard:parameters but covers attachments instead
189
 *   and is also granted by default.
190
 * - <i>midgard:autoserve_attachment</i> controls whether an attachment may be autoserved using
191
 *   the midcom-serveattachmentguid handler. This is granted by default, allowing every attachment
192
 *   to be served using the default URL methods. Denying this right allows component authors to
193
 *   build more sophisticated access control restrictions to attachments.
194
 * - <i>midgard:privileges</i> allows the user to change the permissions on the objects they are
195
 *   granted for. You also need midgard:update and midgard:parameters to properly execute these
196
 *   operations.
197
 * - <i>midgard:owner</i> indicates that the user who has this privilege set is an owner of the
198
 *   given content object.
199
 *
200
 * <i>MidCOM Core Privileges</i>
201
 *
202
 * - <i>midcom:approve</i> grants the user the right to approve or unapprove objects.
203
 * - <i>midcom:component_config</i> grants the user access to configuration management system,
204
 *   it is granted by default only for owners.
205
 * - <i>midcom:isonline</i> is needed to see the online state of another user. It is not granted
206
 *   by default.
207
 *
208
 * <b>Assigning Privileges</b>
209
 *
210
 * See the documentation of the DBA layer for more information.
211
 *
212
 * @package midcom.services
213
 */
214
class midcom_services_auth_acl
215
{
216
    /**
217
     * This is an internal flag used to override all regular permission checks with a sort-of
218
     * read-only privilege set. While internal_sudo is enabled, the system automatically
219
     * grants all privileges except midgard:create, midgard:update, midgard:delete and
220
     * midgard:privileges, which will always be denied. These checks go after the basic checks
221
     * for not authenticated users or admin level users.
222
     */
223
    private bool $_internal_sudo = false;
224
225
    /**
226
     * Internal listing of all default privileges currently registered in the system. This
227
     * is a privilege name/value map.
228
     */
229
    private static array $_default_privileges = [];
230
231
    /**
232
     * Internal listing of all default owner privileges currently registered in the system.
233
     * All privileges not set in this list will be inherited. This is a privilege name/value
234
     * map.
235
     */
236
    private static array $_owner_default_privileges = [];
237
238
    /**
239
     * This listing contains all magic privileges assigned to the existing classes. It is a
240
     * multi-level array, example entry:
241
     *
242
     * <pre>
243
     * 'class_name' => [
244
     *     'EVERYONE' => [],
245
     *     'ANONYMOUS' => [],
246
     *     'USERS' => [
247
     *         'midcom:create' => MIDCOM_PRIVILEGE_ALLOW,
248
     *         'midcom:update' => MIDCOM_PRIVILEGE_ALLOW
249
     *     ],
250
     * ]
251
     * </pre>
252
     */
253
    private static array $_default_magic_class_privileges = [];
254
255
    /**
256
    * Internal cache of the content privileges of users on content objects, this is
257
    * an associative array using a combination of the user identifier and the object's
258
    * guid as index. The privileges for the anonymous user use the magic
259
    * EVERYONE as user identifier.
260
    *
261
    * This must not be merged with the class-wide privileges_cache, because otherwise
262
    * class_default_privileges for child objects might be overridden by parent default
263
    * privileges
264
    */
265
    private static array $_content_privileges_cache = [];
266
267
    /**
268
     * Merges a new set of default privileges into the current set.
269
     * Existing keys will be silently overwritten.
270
     *
271
     * This is usually only called by the framework startup and the
272
     * component loader.
273
     *
274
     * If only a single default value is set (type integer), then this value is taken
275
     * for the default and the owner privilege is unset (meaning INHERIT). If two
276
     * values (type array of integers) is set, the first privilege value is used for
277
     * default, the second one for the owner privilege set.
278
     *
279
     * @param array $privileges An associative privilege_name => default_values listing.
280
     */
281
    public function register_default_privileges(array $privileges)
282
    {
283
        foreach ($privileges as $name => $values) {
284
            if (!is_array($values)) {
285
                $values = [$values, MIDCOM_PRIVILEGE_INHERIT];
286
            }
287
288
            self::$_default_privileges[$name] = $values[0];
289
            if ($values[1] != MIDCOM_PRIVILEGE_INHERIT) {
290
                self::$_owner_default_privileges[$name] = $values[1];
291
            }
292
        }
293
    }
294
295
    /**
296
     * Returns the system-wide basic privilege set.
297
     *
298
     * @return Array Privilege Name / Value map.
299
     */
300 3
    public function get_default_privileges() : array
301
    {
302 3
        return self::$_default_privileges;
303
    }
304
305
    /**
306
     * Returns the system-wide basic owner privilege set.
307
     *
308
     * @return Array Privilege Name / Value map.
309
     */
310 133
    public function get_owner_default_privileges() : array
311
    {
312 133
        return self::$_owner_default_privileges;
313
    }
314
315
    /**
316
     * Load and prepare the list of class magic privileges for usage.
317
     */
318 95
    private function _get_class_magic_privileges(string $class, ?midcom_core_user $user) : array
319
    {
320 95
        if (!array_key_exists($class, self::$_default_magic_class_privileges)) {
321 18
            $privs = [
322 18
                'EVERYONE' => [],
323 18
                'ANONYMOUS' => [],
324 18
                'USERS' => []
325 18
            ];
326
327 18
            if (method_exists($class, 'get_class_magic_default_privileges')) {
328 15
                $privs = (new $class)->get_class_magic_default_privileges();
329
            }
330
331 18
            self::$_default_magic_class_privileges[$class] = $privs;
332
        }
333
334 95
        return array_merge(
335 95
            self::$_default_magic_class_privileges[$class]['EVERYONE'],
336 95
            self::$_default_magic_class_privileges[$class][$user ? 'USERS' : 'ANONYMOUS']
337 95
        );
338
    }
339
340 14
    private function _get_user_per_class_privileges(string $classname, midcom_core_user $user) : array
341
    {
342 14
        static $cache = [];
343
344 14
        $cache_id = $user->id . '::' . $classname;
345
346 14
        if (!array_key_exists($cache_id, $cache)) {
347 13
            $cache[$cache_id] = [];
348 13
            if (is_subclass_of($classname, midcom_core_dbaobject::class)) {
349
                // in case of DBA classes we also want to match the mgd ones,
350
                // and for that dbafactory's is_a() needs an object
351 11
                $classname = new $classname;
352
            }
353
354 13
            foreach ($user->get_per_class_privileges() as $class => $privileges) {
355 1
                if (midcom::get()->dbfactory->is_a($classname, $class, true)) {
356 1
                    $cache[$cache_id] = array_merge($cache[$cache_id], $privileges);
357
                }
358
            }
359
        }
360
361 14
        return $cache[$cache_id];
362
    }
363
364
    /**
365
     * Determine the user identifier for accessing the privilege cache. This is the passed user's
366
     * identifier with the current user and anonymous as fallback
367
     *
368
     * @param mixed $user The user to check for as string or object.
369
     */
370 510
    public function get_user_id($user = null) : string
371
    {
372 510
        if ($user === null) {
373 510
            return midcom::get()->auth->user->id ?? 'ANONYMOUS';
374
        }
375 4
        if (is_string($user)) {
376 2
            if (mgd_is_guid($user) || is_numeric($user)) {
377
                return midcom::get()->auth->get_user($user)->id;
378
            }
379
            // Could be a magic assignee (?)
380 2
            return $user;
381
        }
382 2
        return $user->id;
383
    }
384
385
    /**
386
     * Validate whether a given privilege exists by its name. Essentially this checks
387
     * if a corresponding default privilege has been registered in the system.
388
     */
389 125
    public function privilege_exists(string $name) : bool
390
    {
391 125
        return array_key_exists($name, self::$_default_privileges);
392
    }
393
394 9
    public function can_do_byclass(string $privilege, ?midcom_core_user $user, $class) : bool
395
    {
396 9
        if ($this->_internal_sudo) {
397
            debug_add('INTERNAL SUDO mode is enabled. Generic Read-Only mode set.');
398
            return $this->_can_do_internal_sudo($privilege);
399
        }
400
401 9
        $default_magic_class_privileges = [];
402 9
        $user_privileges = [];
403 9
        $user_per_class_privileges = [];
404
405 9
        if ($user !== null) {
406 7
            $user_privileges = $user->get_privileges();
407
        }
408
409 9
        if ($class !== null) {
410 8
            if (is_object($class)) {
411
                $class = $class::class;
412 8
            } elseif (!class_exists($class)) {
413
                debug_add("can_user_do check to undefined class '{$class}'.", MIDCOM_LOG_ERROR);
414
                return false;
415
            }
416
417 8
            $default_magic_class_privileges = $this->_get_class_magic_privileges($class, $user);
418 8
            if ($user !== null) {
419 6
                $user_per_class_privileges = $this->_get_user_per_class_privileges($class, $user);
420
            }
421
        }
422
423 9
        $full_privileges = array_merge(
424 9
            self::$_default_privileges,
425 9
            $default_magic_class_privileges,
426 9
            $user_privileges,
427 9
            $user_per_class_privileges
428 9
        );
429
430
        // Check for Ownership:
431 9
        if ($full_privileges['midgard:owner'] == MIDCOM_PRIVILEGE_ALLOW) {
432
            $full_privileges = array_merge(
433
                $full_privileges,
434
                $this->get_owner_default_privileges()
435
            );
436
        }
437
438 9
        if (!array_key_exists($privilege, $full_privileges)) {
439
            debug_add("Warning, the privilege {$privilege} is unknown at this point. Assuming not granted privilege.");
440
            return false;
441
        }
442
443 9
        return $full_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
444
    }
445
446
    /**
447
     * Checks whether a user has a certain privilege on the given (via guid and class) content object.
448
     * Works on the currently authenticated user by default, but can take another
449
     * user as an optional argument.
450
     *
451
     * @param string $user_id The user against which to check the privilege, defaults to the currently authenticated user.
452
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
453
     */
454 483
    public function can_do_byguid(string $privilege, string $object_guid, string $object_class, string $user_id) : bool
455
    {
456 483
        if ($this->_internal_sudo) {
457
            return $this->_can_do_internal_sudo($privilege);
458
        }
459
460 483
        if (midcom::get()->auth->is_component_sudo()) {
461 463
            return true;
462
        }
463 142
        static $cache = [];
464
465 142
        $cache_key = "{$user_id}::{$object_guid}::{$privilege}";
466
467 142
        return $cache[$cache_key] ??= $this->can_do_byguid_uncached($privilege, $object_guid, $object_class, $user_id);
468
    }
469
470 133
    private function can_do_byguid_uncached(string $privilege, string $object_guid, string $object_class, string $user_id) : bool
471
    {
472 133
        if ($this->_load_content_privilege($privilege, $object_guid, $object_class, $user_id)) {
473 55
            return self::$_content_privileges_cache["{$user_id}::{$object_guid}"][$privilege];
474
        }
475
476
        // user privileges
477 91
        if ($user = midcom::get()->auth->get_user($user_id)) {
478 11
            $user_per_class_privileges = $this->_get_user_per_class_privileges($object_class, $user);
479 11
            if (array_key_exists($privilege, $user_per_class_privileges)) {
480 1
                return $user_per_class_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
481
            }
482
483 11
            $user_privileges = $user->get_privileges();
484 11
            if (array_key_exists($privilege, $user_privileges)) {
485
                return $user_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
486
            }
487
        }
488
489
        // default magic class privileges user
490 91
        $dmcp = $this->_get_class_magic_privileges($object_class, midcom::get()->auth->user);
491 91
        if (array_key_exists($privilege, $dmcp)) {
492
            return $dmcp[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
493
        }
494
495 91
        if (array_key_exists($privilege, self::$_default_privileges)) {
496 91
            return self::$_default_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
497
        }
498
499
        debug_add("The privilege {$privilege} is unknown at this point. Assuming not granted privilege.", MIDCOM_LOG_WARN);
500
        return false;
501
    }
502
503
    /**
504
     * Look up a specific content privilege and cache the result.
505
     */
506 133
    private function _load_content_privilege(string $privilegename, string $guid, string $class, string $user_id) : bool
507
    {
508 133
        $cache_id = $user_id . '::' . $guid;
509
510 133
        if (!array_key_exists($cache_id, self::$_content_privileges_cache)) {
511 129
            self::$_content_privileges_cache[$cache_id] = [];
512
        }
513 133
        if (array_key_exists($privilegename, self::$_content_privileges_cache[$cache_id])) {
514 10
            return true;
515
        }
516
517 133
        $object_privileges = midcom_core_privilege::get_content_privileges($guid);
518
519 133
        $last_scope = -1;
520 133
        $content_privilege = null;
521
522 133
        foreach ($object_privileges as $privilege) {
523 84
            if ($privilege->privilegename == $privilegename) {
524 81
                $scope = $privilege->get_scope();
525 81
                if ($scope > $last_scope && $privilege->does_privilege_apply($user_id)) {
526 52
                    $last_scope = $scope;
527 52
                    $content_privilege = $privilege;
528
                }
529
            }
530
        }
531
532
        //owner privileges override everything but person privileges, so we have to cross-check those here
533 133
        if (   $privilegename != 'midgard:owner'
534 133
            && $last_scope < MIDCOM_PRIVILEGE_SCOPE_OWNER) {
535 133
            $owner_privileges = $this->get_owner_default_privileges();
536 133
            if (    array_key_exists($privilegename, $owner_privileges)
537 133
                 && $this->_load_content_privilege('midgard:owner', $guid, $class, $user_id)
538 133
                 && self::$_content_privileges_cache[$cache_id]['midgard:owner']) {
539 52
                self::$_content_privileges_cache[$cache_id][$privilegename] = ($owner_privileges[$privilegename] == MIDCOM_PRIVILEGE_ALLOW);
540 52
                return true;
541
            }
542
        }
543
544 132
        if ($content_privilege !== null) {
545 52
            self::$_content_privileges_cache[$cache_id][$privilegename] = ($content_privilege->value == MIDCOM_PRIVILEGE_ALLOW);
546 52
            return true;
547
        }
548
549
        //if nothing was found, we try to recurse to parent
550 91
        [$parent_class, $parent_guid] = $this->get_parent_data($guid, $class);
551
552 91
        if ($parent_guid != $guid && mgd_is_guid($parent_guid)) {
553 37
            $parent_cache_id = $user_id . '::' . $parent_guid;
554 37
            if ($this->_load_content_privilege($privilegename, $parent_guid, $parent_class, $user_id)) {
555 1
                self::$_content_privileges_cache[$cache_id][$privilegename] = self::$_content_privileges_cache[$parent_cache_id][$privilegename];
556 1
                return true;
557
            }
558
        }
559
560 91
        return false;
561
    }
562
563 91
    private function get_parent_data(string $guid, string $class) : array
564
    {
565
        // ==> into SUDO
566 91
        $previous_sudo = $this->_internal_sudo;
567 91
        $this->_internal_sudo = true;
568 91
        $parent_data = midcom::get()->dbfactory->get_parent_data($guid, $class);
569 91
        $this->_internal_sudo = $previous_sudo;
570
        // <== out of SUDO
571 91
        return $parent_data;
572
    }
573
574
    /**
575
     * This internal helper checks if a privilege is available during internal
576
     * sudo mode, as outlined in the corresponding variable.
577
     *
578
     * @see $_internal_sudo
579
     */
580
    private function _can_do_internal_sudo(string $privilege) : bool
581
    {
582
        return !in_array($privilege, ['midgard:create', 'midgard:update', 'midgard:delete', 'midgard:privileges']);
583
    }
584
}
585