Passed
Push — master ( 73c86a...9615f0 )
by Andreas
23:18
created

midcom_services_auth_acl::can_do_byguid()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 4
nop 4
dl 0
loc 17
ccs 9
cts 10
cp 0.9
crap 4.016
rs 9.9666
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()
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
     * @var boolean
224
     */
225
    private $_internal_sudo = false;
226
227
    /**
228
     * Internal listing of all default privileges currently registered in the system. This
229
     * is a privilege name/value map.
230
     *
231
     * @var array
232
     */
233
    private static $_default_privileges = [];
234
235
    /**
236
     * Internal listing of all default owner privileges currently registered in the system.
237
     * All privileges not set in this list will be inherited. This is a privilege name/value
238
     * map.
239
     *
240
     * @var array
241
     */
242
    private static $_owner_default_privileges = [];
243
244
    /**
245
     * This listing contains all magic privileges assigned to the existing classes. It is a
246
     * multi-level array, example entry:
247
     *
248
     * <pre>
249
     * 'class_name' => [
250
     *     'EVERYONE' => [],
251
     *     'ANONYMOUS' => [],
252
     *     'USERS' => [
253
     *         'midcom:create' => MIDCOM_PRIVILEGE_ALLOW,
254
     *         'midcom:update' => MIDCOM_PRIVILEGE_ALLOW
255
     *     ],
256
     * ]
257
     * </pre>
258
     *
259
     * @var array
260
     */
261
    private static $_default_magic_class_privileges = [];
262
263
    /**
264
    * Internal cache of the content privileges of users on content objects, this is
265
    * an associative array using a combination of the user identifier and the object's
266
    * guid as index. The privileges for the anonymous user use the magic
267
    * EVERYONE as user identifier.
268
    *
269
    * This must not be merged with the class-wide privileges_cache, because otherwise
270
    * class_default_privileges for child objects might be overridden by parent default
271
    * privileges
272
    *
273
    * @var array
274
    */
275
    private static $_content_privileges_cache = [];
276
277
    /**
278
     * Merges a new set of default privileges into the current set.
279
     * Existing keys will be silently overwritten.
280
     *
281
     * This is usually only called by the framework startup and the
282
     * component loader.
283
     *
284
     * If only a single default value is set (type integer), then this value is taken
285
     * for the default and the owner privilege is unset (meaning INHERIT). If two
286
     * values (type array of integers) is set, the first privilege value is used for
287
     * default, the second one for the owner privilege set.
288
     *
289
     * @param array $privileges An associative privilege_name => default_values listing.
290
     */
291
    public function register_default_privileges(array $privileges)
292
    {
293
        foreach ($privileges as $name => $values) {
294
            if (!is_array($values)) {
295
                $values = [$values, MIDCOM_PRIVILEGE_INHERIT];
296
            }
297
298
            self::$_default_privileges[$name] = $values[0];
299
            if ($values[1] != MIDCOM_PRIVILEGE_INHERIT) {
300
                self::$_owner_default_privileges[$name] = $values[1];
301
            }
302
        }
303
    }
304
305
    /**
306
     * Returns the system-wide basic privilege set.
307
     *
308
     * @return Array Privilege Name / Value map.
309
     */
310 3
    public function get_default_privileges() : array
311
    {
312 3
        return self::$_default_privileges;
313
    }
314
315
    /**
316
     * Returns the system-wide basic owner privilege set.
317
     *
318
     * @return Array Privilege Name / Value map.
319
     */
320 132
    public function get_owner_default_privileges() : array
321
    {
322 132
        return self::$_owner_default_privileges;
323
    }
324
325
    /**
326
     * Load and prepare the list of class magic privileges for usage.
327
     *
328
     * @param mixed $user The user to check
329
     */
330 92
    private function _get_class_magic_privileges(string $class, $user) : array
331
    {
332 92
        if (!array_key_exists($class, self::$_default_magic_class_privileges)) {
333
            $privs = [
334 18
                'EVERYONE' => [],
335
                'ANONYMOUS' => [],
336
                'USERS' => []
337
            ];
338
339 18
            if (method_exists($class, 'get_class_magic_default_privileges')) {
340 15
                $object = new $class();
341 15
                $privs = $object->get_class_magic_default_privileges();
342
            }
343
344 18
            self::$_default_magic_class_privileges[$class] = $privs;
345
        }
346 92
        $dmcp_user = $user === null ? 'ANONYMOUS' : 'USERS';
347 92
        return array_merge(
348 92
            self::$_default_magic_class_privileges[$class]['EVERYONE'],
349 92
            self::$_default_magic_class_privileges[$class][$dmcp_user]
350
        );
351
    }
352
353 14
    private function _get_user_per_class_privileges(string $classname, midcom_core_user $user) : array
354
    {
355 14
        static $cache = [];
356
357 14
        $cache_id = $user->id . '::' . $classname;
358
359 14
        if (!array_key_exists($cache_id, $cache)) {
360 13
            $cache[$cache_id] = [];
361 13
            if (is_subclass_of($classname, midcom_core_dbaobject::class)) {
362
                // in case of DBA classes we also want to match the mgd ones,
363
                // and for that dbafactory's is_a() needs an object
364 11
                $classname = new $classname;
365
            }
366
367 13
            foreach ($user->get_per_class_privileges() as $class => $privileges) {
368 1
                if (midcom::get()->dbfactory->is_a($classname, $class, true)) {
369 1
                    $cache[$cache_id] = array_merge($cache[$cache_id], $privileges);
370
                }
371
            }
372
        }
373
374 14
        return $cache[$cache_id];
375
    }
376
377
    /**
378
     * Determine the user identifier for accessing the privilege cache. This is the passed user's
379
     * identifier with the current user and anonymous as fallback
380
     *
381
     * @param mixed $user The user to check for as string or object.
382
     * @return string The identifier
383
     */
384 499
    public function get_user_id($user = null)
385
    {
386 499
        if ($user === null) {
387 499
            return midcom::get()->auth->user->id ?? 'ANONYMOUS';
388
        }
389 4
        if (is_string($user)) {
390 2
            if (mgd_is_guid($user) || is_numeric($user)) {
391
                return midcom::get()->auth->get_user($user)->id;
392
            }
393
            // Could be a magic assignee (?)
394 2
            return $user;
395
        }
396 2
        if (is_object($user)) {
397 2
            return $user->id;
398
        }
399
        return $user;
400
    }
401
402
    /**
403
     * Validate whether a given privilege exists by its name. Essentially this checks
404
     * if a corresponding default privilege has been registered in the system.
405
     */
406 121
    public function privilege_exists(string $name) : bool
407
    {
408 121
        return array_key_exists($name, self::$_default_privileges);
409
    }
410
411 7
    public function can_do_byclass(string $privilege, $user, $class) : bool
412
    {
413 7
        if ($this->_internal_sudo) {
414
            debug_add('INTERNAL SUDO mode is enabled. Generic Read-Only mode set.');
415
            return $this->_can_do_internal_sudo($privilege);
416
        }
417
418 7
        $default_magic_class_privileges = [];
419 7
        $user_privileges = [];
420 7
        $user_per_class_privileges = [];
421
422 7
        if ($user !== null) {
423 7
            $user_privileges = $user->get_privileges();
424
        }
425
426 7
        if ($class !== null) {
427 6
            if (is_object($class)) {
428
                $class = get_class($class);
429 6
            } elseif (!class_exists($class)) {
430
                debug_add("can_user_do check to undefined class '{$class}'.", MIDCOM_LOG_ERROR);
431
                return false;
432
            }
433
434 6
            $default_magic_class_privileges = $this->_get_class_magic_privileges($class, $user);
435 6
            if ($user !== null) {
436 6
                $user_per_class_privileges = $this->_get_user_per_class_privileges($class, $user);
437
            }
438
        }
439
440 7
        $full_privileges = array_merge(
441 7
            self::$_default_privileges,
442 7
            $default_magic_class_privileges,
443 7
            $user_privileges,
444 7
            $user_per_class_privileges
445
        );
446
447
        // Check for Ownership:
448 7
        if ($full_privileges['midgard:owner'] == MIDCOM_PRIVILEGE_ALLOW) {
449
            $full_privileges = array_merge(
450
                $full_privileges,
451
                $this->get_owner_default_privileges()
452
            );
453
        }
454
455 7
        if (!array_key_exists($privilege, $full_privileges)) {
456
            debug_add("Warning, the privilege {$privilege} is unknown at this point. Assuming not granted privilege.");
457
            return false;
458
        }
459
460 7
        return $full_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
461
    }
462
463
    /**
464
     * Checks whether a user has a certain privilege on the given (via guid and class) content object.
465
     * Works on the currently authenticated user by default, but can take another
466
     * user as an optional argument.
467
     *
468
     * @param string $user_id The user against which to check the privilege, defaults to the currently authenticated user.
469
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
470
     */
471 471
    public function can_do_byguid(string $privilege, string $object_guid, string $object_class, string $user_id) : bool
472
    {
473 471
        if ($this->_internal_sudo) {
474
            return $this->_can_do_internal_sudo($privilege);
475
        }
476
477 471
        if (midcom::get()->auth->is_component_sudo()) {
478 451
            return true;
479
        }
480 141
        static $cache = [];
481
482 141
        $cache_key = "{$user_id}::{$object_guid}::{$privilege}";
483
484 141
        if (!isset($cache[$cache_key])) {
485 132
            $cache[$cache_key] = $this->can_do_byguid_uncached($privilege, $object_guid, $object_class, $user_id);
486
        }
487 141
        return $cache[$cache_key];
488
    }
489
490 132
    private function can_do_byguid_uncached(string $privilege, string $object_guid, string $object_class, string $user_id) : bool
491
    {
492 132
        if ($this->_load_content_privilege($privilege, $object_guid, $object_class, $user_id)) {
493 56
            return self::$_content_privileges_cache["{$user_id}::{$object_guid}"][$privilege];
494
        }
495
496
        // user privileges
497 90
        if ($user = midcom::get()->auth->get_user($user_id)) {
498 11
            $user_per_class_privileges = $this->_get_user_per_class_privileges($object_class, $user);
499 11
            if (array_key_exists($privilege, $user_per_class_privileges)) {
500 1
                return $user_per_class_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
501
            }
502
503 11
            $user_privileges = $user->get_privileges();
504 11
            if (array_key_exists($privilege, $user_privileges)) {
505
                return $user_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
506
            }
507
        }
508
509
        // default magic class privileges user
510 90
        $dmcp = $this->_get_class_magic_privileges($object_class, midcom::get()->auth->user);
511 90
        if (array_key_exists($privilege, $dmcp)) {
512
            return $dmcp[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
513
        }
514
515 90
        if (array_key_exists($privilege, self::$_default_privileges)) {
516 90
            return self::$_default_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
517
        }
518
519
        debug_add("The privilege {$privilege} is unknown at this point. Assuming not granted privilege.", MIDCOM_LOG_WARN);
520
        return false;
521
    }
522
523
    /**
524
     * Look up a specific content privilege and cache the result.
525
     */
526 132
    private function _load_content_privilege(string $privilegename, string $guid, string $class, string $user_id) : bool
527
    {
528 132
        $cache_id = $user_id . '::' . $guid;
529
530 132
        if (!array_key_exists($cache_id, self::$_content_privileges_cache)) {
531 128
            self::$_content_privileges_cache[$cache_id] = [];
532
        }
533 132
        if (array_key_exists($privilegename, self::$_content_privileges_cache[$cache_id])) {
534 10
            return true;
535
        }
536
537 132
        $object_privileges = midcom_core_privilege::get_content_privileges($guid);
538
539 132
        $last_scope = -1;
540 132
        $content_privilege = null;
541
542 132
        foreach ($object_privileges as $privilege) {
543 83
            if ($privilege->privilegename == $privilegename) {
544 80
                $scope = $privilege->get_scope();
545 80
                if ($scope > $last_scope && $privilege->does_privilege_apply($user_id)) {
546 53
                    $last_scope = $scope;
547 53
                    $content_privilege = $privilege;
548
                }
549
            }
550
        }
551
552
        //owner privileges override everything but person privileges, so we have to cross-check those here
553 132
        if (   $privilegename != 'midgard:owner'
554 132
            && $last_scope < MIDCOM_PRIVILEGE_SCOPE_OWNER) {
555 132
            $owner_privileges = $this->get_owner_default_privileges();
556 132
            if (    array_key_exists($privilegename, $owner_privileges)
557 132
                 && $this->_load_content_privilege('midgard:owner', $guid, $class, $user_id)
558 132
                 && self::$_content_privileges_cache[$cache_id]['midgard:owner']) {
559 53
                self::$_content_privileges_cache[$cache_id][$privilegename] = ($owner_privileges[$privilegename] == MIDCOM_PRIVILEGE_ALLOW);
560 53
                return true;
561
            }
562
        }
563
564 131
        if ($content_privilege !== null) {
565 53
            self::$_content_privileges_cache[$cache_id][$privilegename] = ($content_privilege->value == MIDCOM_PRIVILEGE_ALLOW);
566 53
            return true;
567
        }
568
569
        //if nothing was found, we try to recurse to parent
570 90
        [$parent_class, $parent_guid] = $this->get_parent_data($guid, $class);
571
572 90
        if ($parent_guid != $guid && mgd_is_guid($parent_guid)) {
573 37
            $parent_cache_id = $user_id . '::' . $parent_guid;
574 37
            if ($this->_load_content_privilege($privilegename, $parent_guid, $parent_class, $user_id)) {
575 1
                self::$_content_privileges_cache[$cache_id][$privilegename] = self::$_content_privileges_cache[$parent_cache_id][$privilegename];
576 1
                return true;
577
            }
578
        }
579
580 90
        return false;
581
    }
582
583 90
    private function get_parent_data(string $guid, string $class) : array
584
    {
585
        // ==> into SUDO
586 90
        $previous_sudo = $this->_internal_sudo;
587 90
        $this->_internal_sudo = true;
588 90
        $parent_data = midcom::get()->dbfactory->get_parent_data($guid, $class);
589 90
        $this->_internal_sudo = $previous_sudo;
590
        // <== out of SUDO
591 90
        return $parent_data;
592
    }
593
594
    /**
595
     * This internal helper checks if a privilege is available during internal
596
     * sudo mode, as outlined in the corresponding variable.
597
     *
598
     * @see $_internal_sudo
599
     */
600
    private function _can_do_internal_sudo(string $privilege) : bool
601
    {
602
        return !in_array($privilege, ['midgard:create', 'midgard:update', 'midgard:delete', 'midgard:privileges']);
603
    }
604
}
605