Passed
Push — master ( 9a3b52...1b35fc )
by Andreas
20:28
created

midcom_services_auth_acl::get_parent_data()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 1
rs 10
c 1
b 0
f 0
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 114
    public function get_owner_default_privileges() : array
321
    {
322 114
        return self::$_owner_default_privileges;
323
    }
324
325
    /**
326
     * Load and prepare the list of class magic privileges for usage.
327
     *
328
     * @param string $class The class name for which defaults should be loaded.
329
     * @param mixed $user The user to check
330
     */
331 79
    private function _get_class_magic_privileges(string $class, $user) : array
332
    {
333 79
        if (!array_key_exists($class, self::$_default_magic_class_privileges)) {
334
            $privs = [
335 17
                'EVERYONE' => [],
336
                'ANONYMOUS' => [],
337
                'USERS' => []
338
            ];
339
340 17
            if (method_exists($class, 'get_class_magic_default_privileges')) {
341 14
                $object = new $class();
342 14
                $privs = $object->get_class_magic_default_privileges();
343
            }
344
345 17
            self::$_default_magic_class_privileges[$class] = $privs;
346
        }
347 79
        $dmcp_user = $user === null ? 'ANONYMOUS' : 'USERS';
348 79
        return array_merge(
349 79
            self::$_default_magic_class_privileges[$class]['EVERYONE'],
350 79
            self::$_default_magic_class_privileges[$class][$dmcp_user]
351
        );
352
    }
353
354 13
    private function _get_user_per_class_privileges(string $classname, midcom_core_user $user) : array
355
    {
356 13
        static $cache = [];
357
358 13
        $cache_id = $user->id . '::' . $classname;
359
360 13
        if (!array_key_exists($cache_id, $cache)) {
361 12
            $cache[$cache_id] = [];
362 12
            if (is_subclass_of($classname, midcom_core_dbaobject::class)) {
363
                // in case of DBA classes we also want to match the mgd ones,
364
                // and for that dbafactory's is_a() needs an object
365 10
                $classname = new $classname;
366
            }
367
368 12
            foreach ($user->get_per_class_privileges() as $class => $privileges) {
369 1
                if (midcom::get()->dbfactory->is_a($classname, $class, true)) {
370 1
                    $cache[$cache_id] = array_merge($cache[$cache_id], $privileges);
371
                }
372
            }
373
        }
374
375 13
        return $cache[$cache_id];
376
    }
377
378
    /**
379
     * Determine the user identifier for accessing the privilege cache. This is the passed user's
380
     * identifier with the current user and anonymous as fallback
381
     *
382
     * @param mixed $user The user to check for as string or object.
383
     * @return string The identifier
384
     */
385 473
    public function get_user_id($user = null)
386
    {
387 473
        if ($user === null) {
388 473
            return midcom::get()->auth->user->id ?? 'ANONYMOUS';
389
        }
390 4
        if (is_string($user)) {
391 2
            if (mgd_is_guid($user) || is_numeric($user)) {
392
                return midcom::get()->auth->get_user($user)->id;
393
            }
394
            // Could be a magic assignee (?)
395 2
            return $user;
396
        }
397 2
        if (is_object($user)) {
398 2
            return $user->id;
399
        }
400
        return $user;
401
    }
402
403
    /**
404
     * Validate whether a given privilege exists by its name. Essentially this checks
405
     * if a corresponding default privilege has been registered in the system.
406
     */
407 117
    public function privilege_exists(string $name) : bool
408
    {
409 117
        return array_key_exists($name, self::$_default_privileges);
410
    }
411
412 7
    public function can_do_byclass(string $privilege, $user, $class) : bool
413
    {
414 7
        if ($this->_internal_sudo) {
415
            debug_add('INTERNAL SUDO mode is enabled. Generic Read-Only mode set.');
416
            return $this->_can_do_internal_sudo($privilege);
417
        }
418
419 7
        $default_magic_class_privileges = [];
420 7
        $user_privileges = [];
421 7
        $user_per_class_privileges = [];
422
423 7
        if ($user !== null) {
424 7
            $user_privileges = $user->get_privileges();
425
        }
426
427 7
        if ($class !== null) {
428 6
            if (is_object($class)) {
429
                $class = get_class($class);
430 6
            } elseif (!class_exists($class)) {
431
                debug_add("can_user_do check to undefined class '{$class}'.", MIDCOM_LOG_ERROR);
432
                return false;
433
            }
434
435 6
            $default_magic_class_privileges = $this->_get_class_magic_privileges($class, $user);
436 6
            if ($user !== null) {
437 6
                $user_per_class_privileges = $this->_get_user_per_class_privileges($class, $user);
438
            }
439
        }
440
441 7
        $full_privileges = array_merge(
442 7
            self::$_default_privileges,
443 7
            $default_magic_class_privileges,
444 7
            $user_privileges,
445 7
            $user_per_class_privileges
446
        );
447
448
        // Check for Ownership:
449 7
        if ($full_privileges['midgard:owner'] == MIDCOM_PRIVILEGE_ALLOW) {
450
            $full_privileges = array_merge(
451
                $full_privileges,
452
                $this->get_owner_default_privileges()
453
            );
454
        }
455
456 7
        if (!array_key_exists($privilege, $full_privileges)) {
457
            debug_add("Warning, the privilege {$privilege} is unknown at this point. Assuming not granted privilege.");
458
            return false;
459
        }
460
461 7
        return $full_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW;
462
    }
463
464
    /**
465
     * Checks whether a user has a certain privilege on the given (via guid and class) content object.
466
     * Works on the currently authenticated user by default, but can take another
467
     * user as an optional argument.
468
     *
469
     * @param string $user_id The user against which to check the privilege, defaults to the currently authenticated user.
470
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
471
     */
472 448
    public function can_do_byguid(string $privilege, string $object_guid, string $object_class, string $user_id) : bool
473
    {
474 448
        if ($this->_internal_sudo) {
475
            return $this->_can_do_internal_sudo($privilege);
476
        }
477
478 448
        if (midcom::get()->auth->is_component_sudo()) {
479 429
            return true;
480
        }
481 123
        static $cache = [];
482
483 123
        $cache_prefix = "{$user_id}::{$object_guid}";
484 123
        $cache_key = $cache_prefix . "::{$privilege}";
485
486 123
        if (isset($cache[$cache_key])) {
487 42
            return $cache[$cache_key];
488
        }
489
490 114
        if ($this->_load_content_privilege($privilege, $object_guid, $object_class, $user_id)) {
491 48
            $cache[$cache_key] = self::$_content_privileges_cache[$cache_prefix][$privilege];
492 48
            return $cache[$cache_key];
493
        }
494
495
        // user privileges
496 77
        if ($user = midcom::get()->auth->get_user($user_id)) {
497 10
            $user_per_class_privileges = $this->_get_user_per_class_privileges($object_class, $user);
498
499 10
            if (array_key_exists($privilege, $user_per_class_privileges)) {
500 1
                $cache[$cache_key] = ($user_per_class_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW);
501 1
                return $cache[$cache_key];
502
            }
503
504 10
            $user_privileges = $user->get_privileges();
505
506 10
            if (array_key_exists($privilege, $user_privileges)) {
507
                $cache[$cache_key] = ($user_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW);
508
                return $cache[$cache_key];
509
            }
510
        }
511
512
        // default magic class privileges user
513 77
        $dmcp = $this->_get_class_magic_privileges($object_class, midcom::get()->auth->user);
514
515 77
        if (array_key_exists($privilege, $dmcp)) {
516
            $cache[$cache_key] = ($dmcp[$privilege] == MIDCOM_PRIVILEGE_ALLOW);
517
            return $cache[$cache_key];
518
        }
519
520 77
        if (array_key_exists($privilege, self::$_default_privileges)) {
521 77
            $cache[$cache_key] = (self::$_default_privileges[$privilege] == MIDCOM_PRIVILEGE_ALLOW);
522 77
            return $cache[$cache_key];
523
        }
524
525
        debug_add("The privilege {$privilege} is unknown at this point. Assuming not granted privilege.", MIDCOM_LOG_WARN);
526
        return false;
527
    }
528
529
    /**
530
     * Look up a specific content privilege and cache the result.
531
     */
532 114
    private function _load_content_privilege(string $privilegename, string $guid, string $class, string $user_id) : bool
533
    {
534 114
        $cache_id = $user_id . '::' . $guid;
535
536 114
        if (!array_key_exists($cache_id, self::$_content_privileges_cache)) {
537 110
            self::$_content_privileges_cache[$cache_id] = [];
538
        }
539 114
        if (array_key_exists($privilegename, self::$_content_privileges_cache[$cache_id])) {
540 8
            return true;
541
        }
542
543 114
        $object_privileges = midcom_core_privilege::get_content_privileges($guid);
544
545 114
        $last_scope = -1;
546 114
        $content_privilege = null;
547
548 114
        foreach ($object_privileges as $privilege) {
549 76
            if ($privilege->privilegename == $privilegename) {
550 73
                $scope = $privilege->get_scope();
551 73
                if ($scope > $last_scope && $privilege->does_privilege_apply($user_id)) {
552 45
                    $last_scope = $scope;
553 45
                    $content_privilege = $privilege;
554
                }
555
            }
556
        }
557
558
        //owner privileges override everything but person privileges, so we have to cross-check those here
559 114
        if (   $privilegename != 'midgard:owner'
560 114
            && $last_scope < MIDCOM_PRIVILEGE_SCOPE_OWNER) {
561 114
            $owner_privileges = $this->get_owner_default_privileges();
562 114
            if (    array_key_exists($privilegename, $owner_privileges)
563 114
                 && $this->_load_content_privilege('midgard:owner', $guid, $class, $user_id)
564 114
                 && self::$_content_privileges_cache[$cache_id]['midgard:owner']) {
565 45
                self::$_content_privileges_cache[$cache_id][$privilegename] = ($owner_privileges[$privilegename] == MIDCOM_PRIVILEGE_ALLOW);
566 45
                return true;
567
            }
568
        }
569
570 113
        if ($content_privilege !== null) {
571 45
            self::$_content_privileges_cache[$cache_id][$privilegename] = ($content_privilege->value == MIDCOM_PRIVILEGE_ALLOW);
572 45
            return true;
573
        }
574
575
        //if nothing was found, we try to recurse to parent
576 77
        [$parent_class, $parent_guid] = $this->get_parent_data($guid, $class);
577
578 77
        if ($parent_guid != $guid && mgd_is_guid($parent_guid)) {
579 25
            $parent_cache_id = $user_id . '::' . $parent_guid;
580 25
            if ($this->_load_content_privilege($privilegename, $parent_guid, $parent_class, $user_id)) {
581 1
                self::$_content_privileges_cache[$cache_id][$privilegename] = self::$_content_privileges_cache[$parent_cache_id][$privilegename];
582 1
                return true;
583
            }
584
        }
585
586 77
        return false;
587
    }
588
589 77
    private function get_parent_data(string $guid, string $class) : array
590
    {
591
        // ==> into SUDO
592 77
        $previous_sudo = $this->_internal_sudo;
593 77
        $this->_internal_sudo = true;
594 77
        $parent_data = midcom::get()->dbfactory->get_parent_data($guid, $class);
595 77
        $this->_internal_sudo = $previous_sudo;
596
        // <== out of SUDO
597 77
        return $parent_data;
598
    }
599
600
    /**
601
     * This internal helper checks if a privilege is available during internal
602
     * sudo mode, as outlined in the corresponding variable.
603
     *
604
     * @param string $privilege The privilege to check for
605
     * @see $_internal_sudo
606
     */
607
    private function _can_do_internal_sudo(string $privilege) : bool
608
    {
609
        return !in_array($privilege, ['midgard:create', 'midgard:update', 'midgard:delete', 'midgard:privileges']);
610
    }
611
}
612