Completed
Push — master ( 60f4f4...b821a3 )
by Andreas
17:28
created

midcom_services_auth   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 675
Duplicated Lines 0 %

Test Coverage

Coverage 54.22%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 197
c 2
b 0
f 0
dl 0
loc 675
ccs 122
cts 225
cp 0.5422
rs 2
wmc 92

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 3
A require_valid_user() 0 7 3
A _http_basic_auth() 0 8 3
A get_assignee() 0 13 3
A has_login_data() 0 3 1
A require_admin_user() 0 4 3
A is_group_member() 0 16 4
A logout() 0 9 2
A require_user_do() 0 4 2
A can_do() 0 23 4
B get_group() 0 24 7
A require_do() 0 4 2
A drop_sudo() 0 7 2
A require_admin_or_ip() 0 15 4
A login() 0 8 2
B check_for_login_session() 0 27 7
A _prepare_authentication_drivers() 0 13 3
A set_user() 0 4 1
A require_group_member() 0 7 3
A show_login_form() 0 3 1
A is_component_sudo() 0 3 1
A get_midgard_group_by_name() 0 10 2
A trusted_login() 0 12 3
A request_sudo() 0 22 4
A is_admin() 0 9 4
A access_denied() 0 10 3
A can_user_do() 0 18 5
A get_user_by_name() 0 15 2
B get_user() 0 24 7
A is_valid_user() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like midcom_services_auth often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use midcom_services_auth, and based on these observations, apply Extract Interface, too.

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
use Symfony\Component\HttpFoundation\Request;
10
11
/**
12
 * Main Authentication/Authorization service class, it provides means to authenticate
13
 * users and to check for permissions.
14
 *
15
 * <b>Authentication</b>
16
 *
17
 * Whenever the system successfully creates a new login session (during auth service startup),
18
 * it checks whether the key <i>midcom_services_auth_login_success_url</i> is present in the HTTP
19
 * Request data. If this is the case, it relocates to the URL given in it. This member isn't set
20
 * by default in the MidCOM core, it is intended for custom authentication forms. The MidCOM
21
 * relocate function is used to for relocation, thus you can take full advantage of the
22
 * convenience functions in there. See midcom_application::relocate() for details.
23
 *
24
 * <b>Checking Privileges</b>
25
 *
26
 * This class offers various methods to verify the privilege state of a user, all of them prefixed
27
 * with can_* for privileges and is_* for membership checks.
28
 *
29
 * Each function is available in a simple check version, which returns true or false, and a
30
 * require_* prefixed variant, which has no return value. The require variants of these calls
31
 * instead check if the given condition is met, if yes, they return silently, otherwise they
32
 * throw an access denied error.
33
 *
34
 * @todo Fully document authentication.
35
 * @package midcom.services
36
 */
37
class midcom_services_auth
38
{
39
    /**
40
     * The currently authenticated user or null in case of anonymous access.
41
     * It is to be considered read-only.
42
     *
43
     * @var midcom_core_user
44
     */
45
    public $user;
46
47
    /**
48
     * Admin user level state. This is true if the currently authenticated user is an
49
     * Midgard Administrator, false otherwise.
50
     *
51
     * This effectively maps to midcom_connection::is_admin(); but it is suggested to use the auth class
52
     * for consistency reasons nevertheless.
53
     *
54
     * @var boolean
55
     */
56
    public $admin = false;
57
58
    /**
59
     * The ACL management system.
60
     *
61
     * @var midcom_services_auth_acl
62
     */
63
    public $acl;
64
65
    /**
66
     * Internal cache of all loaded groups, indexed by their identifiers.
67
     *
68
     * @var Array
69
     */
70
    private $_group_cache = [];
71
72
    /**
73
     * Internal cache of all loaded users, indexed by their identifiers.
74
     *
75
     * @var Array
76
     */
77
    private $_user_cache = [];
78
79
    /**
80
     * This flag indicates if sudo mode is active during execution. This will only be the
81
     * case if the sudo system actually grants this privileges, and only until components
82
     * release the rights again. This does override the full access control system at this time
83
     * and essentially give you full admin privileges (though this might change in the future).
84
     *
85
     * Note, that this is no boolean but an int, otherwise it would be impossible to trace nested
86
     * sudo invocations, which are quite possible with multiple components calling each others
87
     * callback. A value of 0 indicates that sudo is inactive. A value greater than zero indicates
88
     * sudo mode is active, with the count being equal to the depth of the sudo callers.
89
     *
90
     * It is thus still safely possible to evaluate this member in a boolean context to check
91
     * for an enabled sudo mode.
92
     *
93
     * @var int
94
     * @see request_sudo()
95
     * @see drop_sudo()
96
     */
97
    private $_component_sudo = 0;
98
99
    /**
100
     * The authentication backend we should use by default.
101
     *
102
     * @var midcom_services_auth_backend
103
     */
104
    private $backend;
105
106
    /**
107
     * The authentication frontend we should use by default.
108
     *
109
     * @var midcom_services_auth_frontend
110
     */
111
    private $frontend;
112
113
    /**
114
     * Initialize the service:
115
     *
116
     * - Start up the login session service
117
     * - Load the core privileges.
118
     * - Initialize to the Midgard Authentication, then synchronize with the auth
119
     *   drivers' currently authenticated user overriding the Midgard Auth if
120
     *   necessary.
121
     */
122
    public function __construct(midcom_services_auth_acl $acl)
123
    {
124
        $this->acl = $acl;
125
126
        // Initialize from midgard
127
        if (   midcom_connection::get_user()
128
            && $user = $this->get_user(midcom_connection::get_user())) {
129
            $this->set_user($user);
130
        }
131
132
        $this->_prepare_authentication_drivers();
133
    }
134
135
    /**
136
     * Checks if the current authentication fronted has new credentials
137
     * ready. If yes, it processes the login accordingly. Otherwise look for existing session
138
     *
139
     * @param Request $request The request object
140
     */
141 1
    public function check_for_login_session(Request $request)
142
    {
143
        // Try to start up a new session, this will authenticate as well.
144 1
        if ($credentials = $this->frontend->read_login_data($request)) {
145
            if (!$this->login($credentials['username'], $credentials['password'], $request->getClientIp())) {
146
                return;
147
            }
148
            debug_add('Authentication was successful, we have a new login session now. Updating timestamps');
149
150
            $person_class = midcom::get()->config->get('person_class');
151
            $person = new $person_class($this->user->guid);
152
153
            if (!$person->get_parameter('midcom', 'first_login')) {
154
                $person->set_parameter('midcom', 'first_login', time());
155
            } elseif (midcom::get()->config->get('auth_save_prev_login')) {
156
                $person->set_parameter('midcom', 'prev_login', $person->get_parameter('midcom', 'last_login'));
157
            }
158
            $person->set_parameter('midcom', 'last_login', time());
159
160
            // Now we check whether there is a success-relocate URL given somewhere.
161
            if ($request->get('midcom_services_auth_login_success_url')) {
162
                return new midcom_response_relocate($request->get('midcom_services_auth_login_success_url'));
0 ignored issues
show
Bug introduced by
It seems like $request->get('midcom_se...uth_login_success_url') can also be of type null; however, parameter $url of midcom_response_relocate::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
                return new midcom_response_relocate(/** @scrutinizer ignore-type */ $request->get('midcom_services_auth_login_success_url'));
Loading history...
163
            }
164
        }
165
        // No new login detected, so we check if there is a running session.
166 1
        elseif ($user = $this->backend->check_for_active_login_session($request)) {
167
            $this->set_user($user);
168
        }
169 1
    }
170
171
    /**
172
     * @param midcom_core_user $user
173
     */
174 59
    private function set_user(midcom_core_user $user)
175
    {
176 59
        $this->user = $user;
177 59
        $this->admin = $user->is_admin();
178 59
    }
179
180
    /**
181
     * Internal startup helper, loads all configured authentication drivers.
182
     */
183
    private function _prepare_authentication_drivers()
184
    {
185
        $classname = midcom::get()->config->get('auth_backend');
186
        if (!str_contains($classname, "_")) {
0 ignored issues
show
Bug introduced by
It seems like $classname can also be of type null; however, parameter $haystack of str_contains() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

186
        if (!str_contains(/** @scrutinizer ignore-type */ $classname, "_")) {
Loading history...
187
            $classname = 'midcom_services_auth_backend_' . $classname;
188
        }
189
        $this->backend = new $classname($this);
190
191
        $classname = midcom::get()->config->get('auth_frontend');
192
        if (!str_contains($classname, "_")) {
193
            $classname = 'midcom_services_auth_frontend_' . $classname;
194
        }
195
        $this->frontend = new $classname();
196
    }
197
198
    /**
199
     * Checks whether a user has a certain privilege on the given content object.
200
     * Works on the currently authenticated user by default, but can take another
201
     * user as an optional argument.
202
     *
203
     * @param string $privilege The privilege to check for
204
     * @param MidgardObject $content_object A Midgard Content Object
0 ignored issues
show
Bug introduced by
The type MidgardObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
205
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user.
206
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
207
     */
208 436
    public function can_do($privilege, $content_object, $user = null) : bool
209
    {
210 436
        if (!is_object($content_object)) {
211 1
            return false;
212
        }
213
214 436
        if ($this->is_admin($user)) {
215
            // Administrators always have access.
216 2
            return true;
217
        }
218
219 436
        $user_id = $this->acl->get_user_id($user);
220
221
        //if we're handed the correct object type, we use its class right away
222 436
        if (midcom::get()->dbclassloader->is_midcom_db_object($content_object)) {
223 436
            $content_object_class = get_class($content_object);
224
        }
225
        //otherwise, we assume (hope) that it's a midgard object
226
        else {
227
            $content_object_class = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($content_object);
228
        }
229
230 436
        return $this->acl->can_do_byguid($privilege, $content_object->guid, $content_object_class, $user_id);
231
    }
232
233 446
    private function is_admin($user) : bool
234
    {
235 446
        if ($user === null) {
236 446
            return $this->user && $this->admin;
237
        }
238 5
        if (is_a($user, midcom_core_user::class)) {
239 2
            return $user->is_admin();
240
        }
241 3
        return false;
242
    }
243
244
    /**
245
     * Checks, whether the given user have the privilege assigned to him in general.
246
     * Be aware, that this does not take any permissions overridden by content objects
247
     * into account. Whenever possible, you should user the can_do() variant of this
248
     * call therefore. can_user_do is only of interest in cases where you do not have
249
     * any content object available, for example when creating root topics.
250
     *
251
     * @param string $privilege The privilege to check for
252
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user,
253
     *     you may specify 'EVERYONE' here to check what an anonymous user can do.
254
     * @param string $class Optional parameter to set if the check should take type specific permissions into account. The class must be default constructible.
255
     */
256 335
    public function can_user_do($privilege, $user = null, $class = null) : bool
257
    {
258 335
        if ($this->is_admin($user)) {
259
            // Administrators always have access.
260 2
            return true;
261
        }
262 335
        if ($this->_component_sudo) {
263 332
            return true;
264
        }
265 7
        if ($user === null) {
266 7
            $user =& $this->user;
267
        }
268
269 7
        if ($user == 'EVERYONE') {
0 ignored issues
show
introduced by
The condition $user == 'EVERYONE' is always false.
Loading history...
270
            $user = null;
271
        }
272
273 7
        return $this->acl->can_do_byclass($privilege, $user, $class);
274
    }
275
276
    /**
277
     * Request superuser privileges for the domain passed.
278
     *
279
     * STUB IMPLEMENTATION ONLY, WILL ALWAYS GRANT SUDO.
280
     *
281
     * You have to call midcom_services_auth::drop_sudo() as soon as you no longer
282
     * need the elevated privileges, which will reset the authentication data to the
283
     * initial credentials.
284
     *
285
     * @param string $domain The domain to request sudo for. This is a component name.
286
     */
287 596
    public function request_sudo($domain = null) : bool
288
    {
289 596
        if (!midcom::get()->config->get('auth_allow_sudo')) {
290 1
            debug_add("SUDO is not allowed on this website.", MIDCOM_LOG_ERROR);
291 1
            return false;
292
        }
293
294 596
        if ($domain === null) {
295 1
            $domain = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_COMPONENT);
296 1
            debug_add("Domain was not supplied, falling back to '{$domain}' which we got from the current component context.");
297
        }
298
299 596
        if ($domain == '') {
300 1
            debug_add("SUDO request for an empty domain, this should not happen. Denying sudo.", MIDCOM_LOG_INFO);
301 1
            return false;
302
        }
303
304 596
        $this->_component_sudo++;
305
306 596
        debug_add("Entered SUDO mode for domain {$domain}.", MIDCOM_LOG_INFO);
307
308 596
        return true;
309
    }
310
311
    /**
312
     * Drops previously acquired superuser privileges.
313
     *
314
     * @see request_sudo()
315
     */
316 596
    public function drop_sudo()
317
    {
318 596
        if ($this->_component_sudo > 0) {
319 596
            debug_add('Leaving SUDO mode.');
320 596
            $this->_component_sudo--;
321
        } else {
322 1
            debug_add('Requested to leave SUDO mode, but sudo was already disabled. Ignoring request.', MIDCOM_LOG_INFO);
323
        }
324 596
    }
325
326 573
    public function is_component_sudo() : bool
327
    {
328 573
        return $this->_component_sudo > 0;
329
    }
330
331
    /**
332
     * Check, whether a user is member of a given group. By default, the query is run
333
     * against the currently authenticated user.
334
     *
335
     * It always returns true for administrative users.
336
     *
337
     * @param mixed $group Group to check against, this can be either a midcom_core_group object or a group string identifier.
338
     * @param midcom_core_user $user The user which should be checked, defaults to the current user.
339
     */
340
    public function is_group_member($group, $user = null) : bool
341
    {
342
        if ($this->is_admin($user)) {
343
            // Administrators always have access.
344
            return true;
345
        }
346
        // Default parameter
347
        if ($user === null) {
348
            if ($this->user === null) {
349
                // not authenticated
350
                return false;
351
            }
352
            $user = $this->user;
353
        }
354
355
        return $user->is_in_group($group);
356
    }
357
358
    /**
359
     * Returns true if there is an authenticated user, false otherwise.
360
     */
361 146
    public function is_valid_user() : bool
362
    {
363 146
        return $this->user !== null;
364
    }
365
366
    /**
367
     * Validates that the current user has the given privilege granted on the
368
     * content object passed to the function.
369
     *
370
     * If this is not the case, an Access Denied error is generated, the message
371
     * defaulting to the string 'access denied: privilege %s not granted' of the
372
     * MidCOM main L10n table.
373
     *
374
     * The check is always done against the currently authenticated user. If the
375
     * check is successful, the function returns silently.
376
     *
377
     * @param string $privilege The privilege to check for
378
     * @param MidgardObject $content_object A Midgard Content Object
379
     * @param string $message The message to show if the privilege has been denied.
380
     */
381 182
    public function require_do($privilege, $content_object, $message = null)
382
    {
383 182
        if (!$this->can_do($privilege, $content_object)) {
384
            throw $this->access_denied($message, 'privilege %s not granted', $privilege);
385
        }
386 182
    }
387
388
    /**
389
     * Validates, whether the given user have the privilege assigned to him in general.
390
     * Be aware, that this does not take any permissions overridden by content objects
391
     * into account. Whenever possible, you should user the require_do() variant of this
392
     * call therefore. require_user_do is only of interest in cases where you do not have
393
     * any content object available, for example when creating root topics.
394
     *
395
     * If this is not the case, an Access Denied error is generated, the message
396
     * defaulting to the string 'access denied: privilege %s not granted' of the
397
     * MidCOM main L10n table.
398
     *
399
     * The check is always done against the currently authenticated user. If the
400
     * check is successful, the function returns silently.
401
     *
402
     * @param string $privilege The privilege to check for
403
     * @param string $message The message to show if the privilege has been denied.
404
     * @param string $class Optional parameter to set if the check should take type specific permissions into account. The class must be default constructible.
405
     */
406 77
    public function require_user_do($privilege, $message = null, $class = null)
407
    {
408 77
        if (!$this->can_user_do($privilege, null, $class)) {
409
            throw $this->access_denied($message, 'privilege %s not granted', $privilege);
410
        }
411 77
    }
412
413
    /**
414
     * Validates that the current user is a member of the given group.
415
     *
416
     * If this is not the case, an Access Denied error is generated, the message
417
     * defaulting to the string 'access denied: user is not member of the group %s' of the
418
     * MidCOM main L10n table.
419
     *
420
     * The check is always done against the currently authenticated user. If the
421
     * check is successful, the function returns silently.
422
     *
423
     * @param mixed $group Group to check against, this can be either a midcom_core_group object or a group string identifier.
424
     * @param string $message The message to show if the user is not member of the given group.
425
     */
426
    function require_group_member($group, $message = null)
427
    {
428
        if (!$this->is_group_member($group)) {
429
            if (is_object($group)) {
430
                $group = $group->name;
431
            }
432
            throw $this->access_denied($message, 'user is not member of the group %s', $group);
433
        }
434
    }
435
436
    /**
437
     * Validates that we currently have admin level privileges, which can either
438
     * come from the current user, or from the sudo service.
439
     *
440
     * If the check is successful, the function returns silently.
441
     *
442
     * @param string $message The message to show if the admin level privileges are missing.
443
     */
444 4
    public function require_admin_user($message = null)
445
    {
446 4
        if (!$this->admin && !$this->_component_sudo) {
447
            throw $this->access_denied($message, 'admin level privileges required');
448
        }
449 4
    }
450
451
    private function access_denied($message, $fallback, $data = null) : midcom_error_forbidden
452
    {
453
        if ($message === null) {
454
            $message = midcom::get()->i18n->get_string('access denied: ' . $fallback, 'midcom');
455
            if ($data !== null) {
456
                $message = sprintf($message, $data);
457
            }
458
        }
459
        debug_print_function_stack("access_denied was called from here:");
460
        return new midcom_error_forbidden($message);
461
    }
462
463
    /**
464
     * Require either a configured IP address or admin credentials
465
     *
466
     * @param string $domain Domain for IP sudo
467
     * @throws midcom_error In case request_sudo fails
468
     * @return boolean True if IP sudo is active, false otherwise
469
     */
470
    public function require_admin_or_ip($domain) : bool
471
    {
472
        $ips = midcom::get()->config->get('indexer_reindex_allowed_ips');
473
        if (   $ips
474
            && in_array($_SERVER['REMOTE_ADDR'], $ips)) {
475
            if (!$this->request_sudo($domain)) {
476
                throw new midcom_error('Failed to acquire SUDO rights. Aborting.');
477
            }
478
            return true;
479
        }
480
481
        // Require user to Basic-authenticate for security reasons
482
        $this->require_valid_user('basic');
483
        $this->require_admin_user();
484
        return false;
485
    }
486
487
    /**
488
     * Validates that there is an authenticated user.
489
     *
490
     * If this is not the case, midcom_error_forbidden is thrown, or a
491
     * basic auth challenge is triggered
492
     *
493
     * If the check is successful, the function returns silently.
494
     *
495
     * @param string $method Preferred authentication method: form or basic
496
     */
497 144
    public function require_valid_user($method = 'form')
498
    {
499 144
        if ($method === 'basic') {
500 3
            $this->_http_basic_auth();
501
        }
502 144
        if (!$this->is_valid_user()) {
503
            throw new midcom_error_forbidden(null, MIDCOM_ERRAUTH, $method);
504
        }
505 144
    }
506
507
    /**
508
     * Handles HTTP Basic authentication
509
     */
510 3
    private function _http_basic_auth()
511
    {
512 3
        if (isset($_SERVER['PHP_AUTH_USER'])) {
513
            if ($user = $this->backend->authenticate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
514
                $this->set_user($user);
515
            } else {
516
                // Wrong password
517
                unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
518
            }
519
        }
520 3
    }
521
522
    /**
523
     * Resolve any assignee identifier known by the system into an appropriate user/group object.
524
     *
525
     * @param string $id A valid user or group identifier usable as assignee (e.g. the $id member
526
     *     of any midcom_core_user or midcom_core_group object).
527
     * @return object|null corresponding object or false on failure.
528
     */
529 122
    public function get_assignee($id)
530
    {
531 122
        $parts = explode(':', $id);
532
533 122
        if ($parts[0] == 'user') {
534 120
            return $this->get_user($id);
535
        }
536 5
        if ($parts[0] == 'group') {
537 3
            return $this->get_group($id);
538
        }
539 2
        debug_add("The identifier {$id} cannot be resolved into an assignee, it cannot be mapped to a type.", MIDCOM_LOG_WARN);
540
541 2
        return null;
542
    }
543
544
    /**
545
     * This is a wrapper for get_user, which allows user retrieval by its name.
546
     * If the username is unknown, false is returned.
547
     *
548
     * @param string $name The name of the user to look up.
549
     */
550 3
    public function get_user_by_name($name) : ?midcom_core_user
551
    {
552 3
        $mc = new midgard_collector('midgard_user', 'login', $name);
553 3
        $mc->set_key_property('person');
554 3
        $mc->add_constraint('authtype', '=', midcom::get()->config->get('auth_type'));
555 3
        $mc->execute();
556 3
        $keys = $mc->list_keys();
557 3
        if (count($keys) != 1) {
558
            return null;
559
        }
560
561 3
        $person_class = midcom::get()->config->get('person_class');
562 3
        $person = new $person_class(key($keys));
563
564 3
        return $this->get_user($person);
565
    }
566
567
    /**
568
     * This is a wrapper for get_group, which allows Midgard Group retrieval by its name.
569
     * If the group name is unknown, false is returned.
570
     *
571
     * In the case that more than one group matches the given name, the first one is returned.
572
     * Note, that this should not happen as midgard group names should be unique according to the specs.
573
     *
574
     * @param string $name The name of the group to look up.
575
     */
576
    public function get_midgard_group_by_name($name) : ?midcom_core_group
577
    {
578
        $qb = new midgard_query_builder('midgard_group');
579
        $qb->add_constraint('name', '=', $name);
580
581
        $result = $qb->execute();
582
        if (empty($result)) {
583
            return null;
584
        }
585
        return $this->get_group($result[0]);
586
    }
587
588
    /**
589
     * Load a user from the database and returns an object instance.
590
     *
591
     * @param mixed $id A valid identifier for a MidgardPerson: An existing midgard_person class
592
     *     or subclass thereof, a Person ID or GUID or a midcom_core_user identifier.
593
     */
594 168
    public function get_user($id) : ?midcom_core_user
595
    {
596 168
        $param = $id;
597
598 168
        if (isset($param->id)) {
599 3
            $id = $param->id;
600 168
        } elseif (!is_string($id) && !is_int($id)) {
601 4
            debug_add('The passed argument was an object of an unsupported type: ' . gettype($param), MIDCOM_LOG_WARN);
602 4
            debug_print_r('Complete object dump:', $param);
603 4
            return null;
604
        }
605 168
        if (!array_key_exists($id, $this->_user_cache)) {
606
            try {
607 108
                if (is_a($param, midcom_db_person::class)) {
608
                    $param = $param->__object;
609
                }
610 108
                $this->_user_cache[$id] = new midcom_core_user($param);
611 3
            } catch (midcom_error $e) {
612
                // Keep it silent while missing user object can mess here
613 3
                $this->_user_cache[$id] = null;
614
            }
615
        }
616
617 168
        return $this->_user_cache[$id];
618
    }
619
620
    /**
621
     * Returns a midcom_core_group instance. Valid arguments are either a valid group identifier
622
     * (group:...), any valid identifier for the midcom_core_group
623
     * constructor or a valid object of that type.
624
     *
625
     * @param mixed $id The identifier of the group as outlined above.
626
     */
627 3
    public function get_group($id) : ?midcom_core_group
628
    {
629 3
        $param = $id;
630
631 3
        if (isset($param->id)) {
632
            $id = $param->id;
633 3
        } elseif (!is_string($id) && !is_int($id)) {
634
            debug_add('The group identifier is of an unsupported type: ' . gettype($param), MIDCOM_LOG_WARN);
635
            debug_print_r('Complete dump:', $param);
636
            return null;
637
        }
638
639 3
        if (!array_key_exists($id, $this->_group_cache)) {
640
            try {
641 3
                if (is_a($param, midcom_core_dbaobject::class)) {
642
                    $param = $param->__object;
643
                }
644 3
                $this->_group_cache[$id] = new midcom_core_group($param);
645
            } catch (midcom_error $e) {
646
                debug_add("Group with identifier {$id} could not be loaded: " . $e->getMessage(), MIDCOM_LOG_WARN);
647
                $this->_group_cache[$id] = null;
648
            }
649
        }
650 3
        return $this->_group_cache[$id];
651
    }
652
653
    /**
654
     * This call tells the backend to log in.
655
     */
656 59
    public function login($username, $password, $clientip = null) : bool
657
    {
658 59
        if ($user = $this->backend->login($username, $password, $clientip)) {
659 59
            $this->set_user($user);
660 59
            return true;
661
        }
662
        debug_add('The login information for ' . $username . ' was invalid.', MIDCOM_LOG_WARN);
663
        return false;
664
    }
665
666
    public function trusted_login($username) : bool
667
    {
668
        if (midcom::get()->config->get('auth_allow_trusted') !== true) {
669
            debug_add("Trusted logins are prohibited", MIDCOM_LOG_ERROR);
670
            return false;
671
        }
672
673
        if ($user = $this->backend->login($username, '', null, true)) {
674
            $this->set_user($user);
675
            return true;
676
        }
677
        return false;
678
    }
679
680
    /**
681
     * This call clears any authentication state
682
     */
683 1
    public function logout()
684
    {
685 1
        if ($this->user === null) {
686
            debug_add('The backend has no authenticated user set, so we should be fine');
687
        } else {
688 1
            $this->backend->logout($this->user);
689 1
            $this->user = null;
690
        }
691 1
        $this->admin = false;
692 1
    }
693
694
    /**
695
     * Render the main login form.
696
     * This only includes the form, no heading or whatsoever.
697
     *
698
     * It is recommended to call this function only as long as the headers are not yet sent (which
699
     * is usually given thanks to MidCOMs output buffering).
700
     *
701
     * What gets rendered depends on the authentication frontend, but will usually be some kind
702
     * of form.
703
     */
704
    public function show_login_form()
705
    {
706
        $this->frontend->show_login_form();
707
    }
708
709
    public function has_login_data() : bool
710
    {
711
        return $this->frontend->has_login_data();
712
    }
713
}
714