midcom_services_auth   F
last analyzed

Complexity

Total Complexity 79

Size/Duplication

Total Lines 565
Duplicated Lines 0 %

Test Coverage

Coverage 60.54%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 169
dl 0
loc 565
ccs 112
cts 185
cp 0.6054
rs 2.08
c 2
b 0
f 0
wmc 79

29 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A can_do() 0 19 3
A drop_sudo() 0 7 2
A set_user() 0 4 1
A is_component_sudo() 0 3 1
A request_sudo() 0 22 4
A is_admin() 0 9 4
A can_user_do() 0 18 5
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 require_user_do() 0 4 2
A get_group() 0 20 5
A require_do() 0 4 2
A require_admin_or_ip() 0 14 3
A login() 0 8 2
A require_group_member() 0 7 3
A show_login_form() 0 3 1
A get_midgard_group_by_name() 0 9 2
A trusted_login() 0 12 3
A access_denied() 0 10 3
A get_user_by_name() 0 12 2
A get_user() 0 3 1
A is_valid_user() 0 3 1
B check_for_login_session() 0 39 9
A logout() 0 9 2

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\Response;
10
use Symfony\Component\HttpKernel\Event\RequestEvent;
11
12
/**
13
 * Main Authentication/Authorization service class, it provides means to authenticate
14
 * users and to check for permissions.
15
 *
16
 * <b>Authentication</b>
17
 *
18
 * Whenever the system successfully creates a new login session (during auth service startup),
19
 * it checks whether the key <i>midcom_services_auth_login_success_url</i> is present in the HTTP
20
 * Request data. If this is the case, it relocates to the URL given in it. This member isn't set
21
 * by default in the MidCOM core, it is intended for custom authentication forms. The MidCOM
22
 * relocate function is used to for relocation, thus you can take full advantage of the
23
 * convenience functions in there. See midcom_application::relocate() for details.
24
 *
25
 * <b>Checking Privileges</b>
26
 *
27
 * This class offers various methods to verify the privilege state of a user, all of them prefixed
28
 * with can_* for privileges and is_* for membership checks.
29
 *
30
 * Each function is available in a simple check version, which returns true or false, and a
31
 * require_* prefixed variant, which has no return value. The require variants of these calls
32
 * instead check if the given condition is met, if yes, they return silently, otherwise they
33
 * throw an access denied error.
34
 *
35
 * @todo Fully document authentication.
36
 * @package midcom.services
37
 */
38
class midcom_services_auth
39
{
40
    /**
41
     * The currently authenticated user or null in case of anonymous access.
42
     * It is to be considered read-only.
43
     */
44
    public ?midcom_core_user $user = null;
45
46
    /**
47
     * Admin user level state. This is true if the currently authenticated user is an
48
     * Administrator, false otherwise.
49
     */
50
    public bool $admin = false;
51
52
    public midcom_services_auth_acl $acl;
53
54
    /**
55
     * Internal cache of all loaded groups, indexed by their identifiers.
56
     */
57
    private array $_group_cache = [];
58
59
    /**
60
     * This flag indicates if sudo mode is active during execution. This will only be the
61
     * case if the sudo system actually grants this privileges, and only until components
62
     * release the rights again. This does override the full access control system at this time
63
     * and essentially give you full admin privileges (though this might change in the future).
64
     *
65
     * Note, that this is no boolean but an int, otherwise it would be impossible to trace nested
66
     * sudo invocations, which are quite possible with multiple components calling each others
67
     * callback. A value of 0 indicates that sudo is inactive. A value greater than zero indicates
68
     * sudo mode is active, with the count being equal to the depth of the sudo callers.
69
     *
70
     * It is thus still safely possible to evaluate this member in a boolean context to check
71
     * for an enabled sudo mode.
72
     *
73
     * @see request_sudo()
74
     * @see drop_sudo()
75
     */
76
    private int $_component_sudo = 0;
77
78
    private midcom_services_auth_backend $backend;
79
80
    private midcom_services_auth_frontend $frontend;
81
82
    /**
83
     * Loads all configured authentication drivers.
84
     */
85 1
    public function __construct(midcom_services_auth_acl $acl, midcom_services_auth_backend $backend, midcom_services_auth_frontend $frontend)
86
    {
87 1
        $this->acl = $acl;
88 1
        $this->backend = $backend;
89 1
        $this->frontend = $frontend;
90
    }
91
92
    /**
93
     * Checks if the current authentication fronted has new credentials
94
     * ready. If yes, it processes the login accordingly. Otherwise look for existing session
95
     */
96 353
    public function check_for_login_session(RequestEvent $event)
97
    {
98 353
        $request = $event->getRequest();
99
        // Try to start up a new session, this will authenticate as well.
100 353
        if ($credentials = $this->frontend->read_login_data($request)) {
101 1
            if (!$this->login($credentials['username'], $credentials['password'], $request->getClientIp())) {
102
                if (is_callable(midcom::get()->config->get('auth_failure_callback'))) {
103
                    debug_print_r('Calling auth failure callback: ', midcom::get()->config->get('auth_failure_callback'));
104
                    // Calling the failure function with the username as a parameter. No password sent to the user function for security reasons
105
                    call_user_func(midcom::get()->config->get('auth_failure_callback'), $credentials['username']);
0 ignored issues
show
Bug introduced by
It seems like midcom::get()->config->g...auth_failure_callback') can also be of type null; however, parameter $callback of call_user_func() does only seem to accept callable, 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

105
                    call_user_func(/** @scrutinizer ignore-type */ midcom::get()->config->get('auth_failure_callback'), $credentials['username']);
Loading history...
106
                }
107
                return;
108
            }
109 1
            debug_add('Authentication was successful, we have a new login session now. Updating timestamps');
110
111 1
            $person_class = midcom::get()->config->get('person_class');
112 1
            $person = new $person_class($this->user->guid);
113
114 1
            if (!$person->get_parameter('midcom', 'first_login')) {
115 1
                $person->set_parameter('midcom', 'first_login', time());
116
            } elseif (midcom::get()->config->get('auth_save_prev_login')) {
117
                $person->set_parameter('midcom', 'prev_login', $person->get_parameter('midcom', 'last_login'));
118
            }
119 1
            $person->set_parameter('midcom', 'last_login', time());
120
121 1
            if (is_callable(midcom::get()->config->get('auth_success_callback'))) {
122
                debug_print_r('Calling auth success callback:', midcom::get()->config->get('auth_success_callback'));
123
                // Calling the success function. No parameters, because authenticated user is stored in midcom_connection
124
                call_user_func(midcom::get()->config->get('auth_success_callback'));
125
            }
126
127
            // Now we check whether there is a success-relocate URL given somewhere.
128 1
            if ($request->get('midcom_services_auth_login_success_url')) {
129 1
                $event->setResponse(new midcom_response_relocate($request->get('midcom_services_auth_login_success_url')));
130
            }
131
        }
132
        // No new login detected, so we check if there is a running session.
133 352
        elseif ($user = $this->backend->check_for_active_login_session($request)) {
134
            $this->set_user($user);
135
        }
136
    }
137
138 65
    private function set_user(midcom_core_user $user)
139
    {
140 65
        $this->user = $user;
141 65
        $this->admin = $user->is_admin();
142
    }
143
144
    /**
145
     * Checks whether a user has a certain privilege on the given content object.
146
     * Works on the currently authenticated user by default, but can take another
147
     * user as an optional argument.
148
     *
149
     * @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...
150
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user.
151
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
152
     */
153 471
    public function can_do(string $privilege, object $content_object, $user = null) : bool
154
    {
155 471
        if ($this->is_admin($user)) {
156
            // Administrators always have access.
157 2
            return true;
158
        }
159
160 471
        $user_id = $this->acl->get_user_id($user);
161
162
        //if we're handed the correct object type, we use its class right away
163 471
        if (midcom::get()->dbclassloader->is_midcom_db_object($content_object)) {
164 471
            $content_object_class = $content_object::class;
165
        }
166
        //otherwise, we assume (hope) that it's a midgard object
167
        else {
168
            $content_object_class = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($content_object);
169
        }
170
171 471
        return $this->acl->can_do_byguid($privilege, $content_object->guid, $content_object_class, $user_id);
172
    }
173
174 484
    private function is_admin($user) : bool
175
    {
176 484
        if ($user === null) {
177 484
            return $this->user && $this->admin;
178
        }
179 5
        if ($user instanceof midcom_core_user) {
180 2
            return $user->is_admin();
181
        }
182 3
        return false;
183
    }
184
185
    /**
186
     * Checks, whether the given user have the privilege assigned to him in general.
187
     * Be aware, that this does not take any permissions overridden by content objects
188
     * into account. Whenever possible, you should user the can_do() variant of this
189
     * call therefore. can_user_do is only of interest in cases where you do not have
190
     * any content object available, for example when creating root topics.
191
     *
192
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user,
193
     *     you may specify 'EVERYONE' here to check what an anonymous user can do.
194
     * @param string $class Optional parameter to set if the check should take type specific permissions into account. The class must be default constructible.
195
     */
196 352
    public function can_user_do(string $privilege, $user = null, $class = null) : bool
197
    {
198 352
        if ($this->is_admin($user)) {
199
            // Administrators always have access.
200 2
            return true;
201
        }
202 352
        if ($this->_component_sudo) {
203 346
            return true;
204
        }
205 9
        if ($user === null) {
206 9
            $user =& $this->user;
207
        }
208
209 9
        if ($user == 'EVERYONE') {
210
            $user = null;
211
        }
212
213 9
        return $this->acl->can_do_byclass($privilege, $user, $class);
214
    }
215
216
    /**
217
     * Request superuser privileges for the domain passed.
218
     *
219
     * STUB IMPLEMENTATION ONLY, WILL ALWAYS GRANT SUDO.
220
     *
221
     * You have to call midcom_services_auth::drop_sudo() as soon as you no longer
222
     * need the elevated privileges, which will reset the authentication data to the
223
     * initial credentials.
224
     *
225
     * @param string $domain The domain to request sudo for. This is a component name.
226
     */
227 695
    public function request_sudo(?string $domain = null) : bool
228
    {
229 695
        if (!midcom::get()->config->get('auth_allow_sudo')) {
230 1
            debug_add("SUDO is not allowed on this website.", MIDCOM_LOG_ERROR);
231 1
            return false;
232
        }
233
234 695
        if ($domain === null) {
235 1
            $domain = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_COMPONENT);
236 1
            debug_add("Domain was not supplied, falling back to '{$domain}' which we got from the current component context.");
237
        }
238
239 695
        if ($domain == '') {
240 1
            debug_add("SUDO request for an empty domain, this should not happen. Denying sudo.", MIDCOM_LOG_INFO);
241 1
            return false;
242
        }
243
244 695
        $this->_component_sudo++;
245
246 695
        debug_add("Entered SUDO mode for domain {$domain}.", MIDCOM_LOG_INFO);
247
248 695
        return true;
249
    }
250
251
    /**
252
     * Drops previously acquired superuser privileges.
253
     *
254
     * @see request_sudo()
255
     */
256 695
    public function drop_sudo()
257
    {
258 695
        if ($this->_component_sudo > 0) {
259 695
            debug_add('Leaving SUDO mode.');
260 695
            $this->_component_sudo--;
261
        } else {
262 1
            debug_add('Requested to leave SUDO mode, but sudo was already disabled. Ignoring request.', MIDCOM_LOG_INFO);
263
        }
264
    }
265
266 672
    public function is_component_sudo() : bool
267
    {
268 672
        return $this->_component_sudo > 0;
269
    }
270
271
    /**
272
     * Check, whether a user is member of a given group. By default, the query is run
273
     * against the currently authenticated user.
274
     *
275
     * It always returns true for administrative users.
276
     */
277
    public function is_group_member(midcom_core_group|string $group, ?midcom_core_user $user = null) : bool
278
    {
279
        if ($this->is_admin($user)) {
280
            // Administrators always have access.
281
            return true;
282
        }
283
        // Default parameter
284
        if ($user === null) {
285
            if ($this->user === null) {
286
                // not authenticated
287
                return false;
288
            }
289
            $user = $this->user;
290
        }
291
292
        return $user->is_in_group($group);
293
    }
294
295
    /**
296
     * Returns true if there is an authenticated user, false otherwise.
297
     */
298 158
    public function is_valid_user() : bool
299
    {
300 158
        return $this->user !== null;
301
    }
302
303
    /**
304
     * Validates that the current user has the given privilege granted on the
305
     * content object passed to the function.
306
     *
307
     * If this is not the case, an Access Denied error is generated, the message
308
     * defaulting to the string 'access denied: privilege %s not granted' of the
309
     * MidCOM main L10n table.
310
     *
311
     * The check is always done against the currently authenticated user. If the
312
     * check is successful, the function returns silently.
313
     *
314
     * @param MidgardObject $content_object A Midgard Content Object
315
     */
316 194
    public function require_do(string $privilege, object $content_object, ?string $message = null)
317
    {
318 194
        if (!$this->can_do($privilege, $content_object)) {
319
            throw $this->access_denied($message, 'privilege %s not granted', $privilege);
320
        }
321
    }
322
323
    /**
324
     * Validates, whether the given user have the privilege assigned to him in general.
325
     * Be aware, that this does not take any permissions overridden by content objects
326
     * into account. Whenever possible, you should user the require_do() variant of this
327
     * call therefore. require_user_do is only of interest in cases where you do not have
328
     * any content object available, for example when creating root topics.
329
     *
330
     * If this is not the case, an Access Denied error is generated, the message
331
     * defaulting to the string 'access denied: privilege %s not granted' of the
332
     * MidCOM main L10n table.
333
     *
334
     * The check is always done against the currently authenticated user. If the
335
     * check is successful, the function returns silently.
336
     *
337
     * @param string $class Optional parameter to set if the check should take type specific permissions into account. The class must be default constructible.
338
     */
339 77
    public function require_user_do(string $privilege, ?string $message = null, ?string $class = null)
340
    {
341 77
        if (!$this->can_user_do($privilege, class: $class)) {
342
            throw $this->access_denied($message, 'privilege %s not granted', $privilege);
343
        }
344
    }
345
346
    /**
347
     * Validates that the current user is a member of the given group.
348
     *
349
     * If this is not the case, an Access Denied error is generated, the message
350
     * defaulting to the string 'access denied: user is not member of the group %s' of the
351
     * MidCOM main L10n table.
352
     *
353
     * The check is always done against the currently authenticated user. If the
354
     * check is successful, the function returns silently.
355
     *
356
     * @param mixed $group Group to check against, this can be either a midcom_core_group object or a group string identifier.
357
     * @param string $message The message to show if the user is not member of the given group.
358
     */
359
    function require_group_member($group, $message = null)
360
    {
361
        if (!$this->is_group_member($group)) {
362
            if (is_object($group)) {
363
                $group = $group->name;
364
            }
365
            throw $this->access_denied($message, 'user is not member of the group %s', $group);
366
        }
367
    }
368
369
    /**
370
     * Validates that we currently have admin level privileges, which can either
371
     * come from the current user, or from the sudo service.
372
     *
373
     * If the check is successful, the function returns silently.
374
     */
375 4
    public function require_admin_user(?string $message = null)
376
    {
377 4
        if (!$this->admin && !$this->_component_sudo) {
378
            throw $this->access_denied($message, 'admin level privileges required');
379
        }
380
    }
381
382
    private function access_denied(?string $message, string $fallback, ?string $data = null) : midcom_error_forbidden
383
    {
384
        if ($message === null) {
385
            $message = midcom::get()->i18n->get_string('access denied: ' . $fallback, 'midcom');
386
            if ($data !== null) {
387
                $message = sprintf($message, $data);
388
            }
389
        }
390
        debug_print_function_stack("access_denied was called from here:");
391
        return new midcom_error_forbidden($message);
392
    }
393
394
    /**
395
     * Require either a configured IP address or admin credentials
396
     */
397
    public function require_admin_or_ip(string $domain) : bool
398
    {
399
        $ips = midcom::get()->config->get_array('indexer_reindex_allowed_ips');
400
        if (in_array($_SERVER['REMOTE_ADDR'], $ips)) {
401
            if (!$this->request_sudo($domain)) {
402
                throw new midcom_error('Failed to acquire SUDO rights. Aborting.');
403
            }
404
            return true;
405
        }
406
407
        // Require user to Basic-authenticate for security reasons
408
        $this->require_valid_user('basic');
409
        $this->require_admin_user();
410
        return false;
411
    }
412
413
    /**
414
     * Validates that there is an authenticated user.
415
     *
416
     * If this is not the case, midcom_error_forbidden is thrown, or a
417
     * basic auth challenge is triggered
418
     *
419
     * If the check is successful, the function returns silently.
420
     *
421
     * @param string $method Preferred authentication method: form or basic
422
     */
423 156
    public function require_valid_user(string $method = 'form')
424
    {
425 156
        if ($method === 'basic') {
426 5
            $this->_http_basic_auth();
427
        }
428 156
        if (!$this->is_valid_user()) {
429
            throw new midcom_error_forbidden(code: Response::HTTP_UNAUTHORIZED, method: $method);
430
        }
431
    }
432
433
    /**
434
     * Handles HTTP Basic authentication
435
     */
436 5
    private function _http_basic_auth()
437
    {
438 5
        if (isset($_SERVER['PHP_AUTH_USER'])) {
439
            if ($user = $this->backend->authenticate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
440
                $this->set_user($user);
441
            } else {
442
                // Wrong password
443
                unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
444
            }
445
        }
446
    }
447
448
    /**
449
     * Resolve any assignee identifier known by the system into an appropriate user/group object.
450
     *
451
     * @param string $id A valid user or group identifier usable as assignee (e.g. the $id member
452
     *     of any midcom_core_user or midcom_core_group object).
453
     * @return object|null corresponding object or false on failure.
454
     */
455 133
    public function get_assignee(string $id) : ?object
456
    {
457 133
        $parts = explode(':', $id);
458
459 133
        if ($parts[0] == 'user') {
460 132
            return $this->get_user($id);
461
        }
462 5
        if ($parts[0] == 'group') {
463 4
            return $this->get_group($id);
464
        }
465 1
        debug_add("The identifier {$id} cannot be resolved into an assignee, it cannot be mapped to a type.", MIDCOM_LOG_WARN);
466
467 1
        return null;
468
    }
469
470
    /**
471
     * This is a wrapper for get_user, which allows user retrieval by its name.
472
     * If the username is unknown, false is returned.
473
     */
474 3
    public function get_user_by_name(string $name) : ?midcom_core_user
475
    {
476 3
        $mc = new midgard_collector('midgard_user', 'login', $name);
477 3
        $mc->set_key_property('person');
478 3
        $mc->add_constraint('authtype', '=', midcom::get()->config->get('auth_type'));
479 3
        $mc->execute();
480 3
        $keys = $mc->list_keys();
481 3
        if (count($keys) != 1) {
482
            return null;
483
        }
484
485 3
        return $this->get_user(key($keys));
486
    }
487
488
    /**
489
     * This is a wrapper for get_group, which allows Midgard Group retrieval by its name.
490
     * If the group name is unknown, false is returned.
491
     *
492
     * In the case that more than one group matches the given name, the first one is returned.
493
     * Note, that this should not happen as midgard group names should be unique according to the specs.
494
     */
495
    public function get_midgard_group_by_name(string $name) : ?midcom_core_group
496
    {
497
        $qb = new midgard_query_builder('midgard_group');
498
        $qb->add_constraint('name', '=', $name);
499
500
        if ($result = $qb->execute()) {
501
            return $this->get_group($result[0]);
502
        }
503
        return null;
504
    }
505
506
    /**
507
     * Load a user from the database and returns an object instance.
508
     *
509
     * @param mixed $id A valid identifier for a MidgardPerson: An existing midgard_person class
510
     *     or subclass thereof, a Person ID or GUID or a midcom_core_user identifier.
511
     */
512 191
    public function get_user($id) : ?midcom_core_user
513
    {
514 191
        return $this->backend->get_user($id);
515
    }
516
517
    /**
518
     * Returns a midcom_core_group instance. Valid arguments are either a valid group identifier
519
     * (group:...), any valid identifier for the midcom_core_group
520
     * constructor or a valid object of that type.
521
     */
522 4
    public function get_group(string|int|midcom_db_group|midgard_group $id) : ?midcom_core_group
523
    {
524 4
        $param = $id;
525
526 4
        if (is_object($param)) {
527
            $id = $param->id;
528
        }
529
530 4
        if (!array_key_exists($id, $this->_group_cache)) {
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type midcom_db_group and midgard_group; however, parameter $key of array_key_exists() does only seem to accept integer|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

530
        if (!array_key_exists(/** @scrutinizer ignore-type */ $id, $this->_group_cache)) {
Loading history...
531
            try {
532 4
                if ($param instanceof midcom_core_dbaobject) {
533
                    $param = $param->__object;
534
                }
535 4
                $this->_group_cache[$id] = new midcom_core_group($param);
536
            } catch (midcom_error $e) {
537
                debug_add("Group with identifier {$id} could not be loaded: " . $e->getMessage(), MIDCOM_LOG_WARN);
538
                $this->_group_cache[$id] = null;
539
            }
540
        }
541 4
        return $this->_group_cache[$id];
542
    }
543
544
    /**
545
     * This call tells the backend to log in.
546
     */
547 65
    public function login(string $username, string $password, ?string $clientip = null) : bool
548
    {
549 65
        if ($user = $this->backend->login($username, $password, $clientip)) {
550 65
            $this->set_user($user);
551 65
            return true;
552
        }
553
        debug_add('The login information for ' . $username . ' was invalid.', MIDCOM_LOG_WARN);
554
        return false;
555
    }
556
557
    public function trusted_login(string $username) : bool
558
    {
559
        if (midcom::get()->config->get('auth_allow_trusted') !== true) {
560
            debug_add("Trusted logins are prohibited", MIDCOM_LOG_ERROR);
561
            return false;
562
        }
563
564
        if ($user = $this->backend->login($username, '', trusted: true)) {
565
            $this->set_user($user);
566
            return true;
567
        }
568
        return false;
569
    }
570
571
    /**
572
     * This call clears any authentication state
573
     */
574 2
    public function logout()
575
    {
576 2
        if ($this->user === null) {
577 1
            debug_add('The backend has no authenticated user set, so we should be fine');
578
        } else {
579 1
            $this->backend->logout($this->user);
580 1
            $this->user = null;
581
        }
582 2
        $this->admin = false;
583
    }
584
585
    /**
586
     * Render the main login form.
587
     * This only includes the form, no heading or whatsoever.
588
     *
589
     * It is recommended to call this function only as long as the headers are not yet sent (which
590
     * is usually given thanks to MidCOMs output buffering).
591
     *
592
     * What gets rendered depends on the authentication frontend, but will usually be some kind
593
     * of form.
594
     */
595
    public function show_login_form()
596
    {
597
        $this->frontend->show_login_form();
598
    }
599
600
    public function has_login_data() : bool
601
    {
602
        return $this->frontend->has_login_data();
603
    }
604
}
605