Completed
Push — master ( 6bb916...1b69f8 )
by Andreas
14:18
created

midcom_services_auth_acl::get_user_id()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6.288

Importance

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