Passed
Branch master (05c266)
by Andreas
09:55
created

midcom_services_auth::drop_sudo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
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
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpFoundation\Response;
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
     * @var midcom_core_user
45
     */
46
    public $user;
47
48
    /**
49
     * Admin user level state. This is true if the currently authenticated user is an
50
     * Administrator, false otherwise.
51
     *
52
     * @var boolean
53
     */
54
    public $admin = false;
55
56
    /**
57
     * @var midcom_services_auth_acl
58
     */
59
    public $acl;
60
61
    /**
62
     * Internal cache of all loaded groups, indexed by their identifiers.
63
     *
64
     * @var array
65
     */
66
    private $_group_cache = [];
67
68
    /**
69
     * This flag indicates if sudo mode is active during execution. This will only be the
70
     * case if the sudo system actually grants this privileges, and only until components
71
     * release the rights again. This does override the full access control system at this time
72
     * and essentially give you full admin privileges (though this might change in the future).
73
     *
74
     * Note, that this is no boolean but an int, otherwise it would be impossible to trace nested
75
     * sudo invocations, which are quite possible with multiple components calling each others
76
     * callback. A value of 0 indicates that sudo is inactive. A value greater than zero indicates
77
     * sudo mode is active, with the count being equal to the depth of the sudo callers.
78
     *
79
     * It is thus still safely possible to evaluate this member in a boolean context to check
80
     * for an enabled sudo mode.
81
     *
82
     * @var int
83
     * @see request_sudo()
84
     * @see drop_sudo()
85
     */
86
    private $_component_sudo = 0;
87
88
    /**
89
     * @var midcom_services_auth_backend
90
     */
91
    private $backend;
92
93
    /**
94
     * @var midcom_services_auth_frontend
95
     */
96
    private $frontend;
97
98
    /**
99
     * Loads all configured authentication drivers.
100
     */
101 1
    public function __construct(midcom_services_auth_acl $acl, midcom_services_auth_backend $backend, midcom_services_auth_frontend $frontend)
102
    {
103 1
        $this->acl = $acl;
104 1
        $this->backend = $backend;
105 1
        $this->frontend = $frontend;
106
    }
107
108
    /**
109
     * Checks if the current authentication fronted has new credentials
110
     * ready. If yes, it processes the login accordingly. Otherwise look for existing session
111
     */
112 2
    public function check_for_login_session(Request $request)
113
    {
114
        // Try to start up a new session, this will authenticate as well.
115 2
        if ($credentials = $this->frontend->read_login_data($request)) {
116 1
            if (!$this->login($credentials['username'], $credentials['password'], $request->getClientIp())) {
117
                if (is_callable(midcom::get()->config->get('auth_failure_callback'))) {
118
                    debug_print_r('Calling auth failure callback: ', midcom::get()->config->get('auth_failure_callback'));
119
                    // Calling the failure function with the username as a parameter. No password sent to the user function for security reasons
120
                    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

120
                    call_user_func(/** @scrutinizer ignore-type */ midcom::get()->config->get('auth_failure_callback'), $credentials['username']);
Loading history...
121
                }
122
                return;
123
            }
124 1
            debug_add('Authentication was successful, we have a new login session now. Updating timestamps');
125
126 1
            $person_class = midcom::get()->config->get('person_class');
127 1
            $person = new $person_class($this->user->guid);
128
129 1
            if (!$person->get_parameter('midcom', 'first_login')) {
130 1
                $person->set_parameter('midcom', 'first_login', time());
131
            } elseif (midcom::get()->config->get('auth_save_prev_login')) {
132
                $person->set_parameter('midcom', 'prev_login', $person->get_parameter('midcom', 'last_login'));
133
            }
134 1
            $person->set_parameter('midcom', 'last_login', time());
135
136 1
            if (is_callable(midcom::get()->config->get('auth_success_callback'))) {
137
                debug_print_r('Calling auth success callback:', midcom::get()->config->get('auth_success_callback'));
138
                // Calling the success function. No parameters, because authenticated user is stored in midcom_connection
139
                call_user_func(midcom::get()->config->get('auth_success_callback'));
140
            }
141
142
            // Now we check whether there is a success-relocate URL given somewhere.
143 1
            if ($request->get('midcom_services_auth_login_success_url')) {
144 1
                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

144
                return new midcom_response_relocate(/** @scrutinizer ignore-type */ $request->get('midcom_services_auth_login_success_url'));
Loading history...
145
            }
146
        }
147
        // No new login detected, so we check if there is a running session.
148 1
        elseif ($user = $this->backend->check_for_active_login_session($request)) {
149
            $this->set_user($user);
150
        }
151
    }
152
153 65
    private function set_user(midcom_core_user $user)
154
    {
155 65
        $this->user = $user;
156 65
        $this->admin = $user->is_admin();
157
    }
158
159
    /**
160
     * Checks whether a user has a certain privilege on the given content object.
161
     * Works on the currently authenticated user by default, but can take another
162
     * user as an optional argument.
163
     *
164
     * @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...
165
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user.
166
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
167
     */
168 464
    public function can_do(string $privilege, object $content_object, $user = null) : bool
169
    {
170 464
        if ($this->is_admin($user)) {
171
            // Administrators always have access.
172 2
            return true;
173
        }
174
175 464
        $user_id = $this->acl->get_user_id($user);
176
177
        //if we're handed the correct object type, we use its class right away
178 464
        if (midcom::get()->dbclassloader->is_midcom_db_object($content_object)) {
179 464
            $content_object_class = get_class($content_object);
180
        }
181
        //otherwise, we assume (hope) that it's a midgard object
182
        else {
183
            $content_object_class = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($content_object);
184
        }
185
186 464
        return $this->acl->can_do_byguid($privilege, $content_object->guid, $content_object_class, $user_id);
0 ignored issues
show
Bug introduced by
It seems like $content_object_class can also be of type null; however, parameter $object_class of midcom_services_auth_acl::can_do_byguid() 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
        return $this->acl->can_do_byguid($privilege, $content_object->guid, /** @scrutinizer ignore-type */ $content_object_class, $user_id);
Loading history...
187
    }
188
189 474
    private function is_admin($user) : bool
190
    {
191 474
        if ($user === null) {
192 474
            return $this->user && $this->admin;
193
        }
194 5
        if ($user instanceof midcom_core_user) {
195 2
            return $user->is_admin();
196
        }
197 3
        return false;
198
    }
199
200
    /**
201
     * Checks, whether the given user have the privilege assigned to him in general.
202
     * Be aware, that this does not take any permissions overridden by content objects
203
     * into account. Whenever possible, you should user the can_do() variant of this
204
     * call therefore. can_user_do is only of interest in cases where you do not have
205
     * any content object available, for example when creating root topics.
206
     *
207
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user,
208
     *     you may specify 'EVERYONE' here to check what an anonymous user can do.
209
     * @param string $class Optional parameter to set if the check should take type specific permissions into account. The class must be default constructible.
210
     */
211 340
    public function can_user_do(string $privilege, $user = null, $class = null) : bool
212
    {
213 340
        if ($this->is_admin($user)) {
214
            // Administrators always have access.
215 2
            return true;
216
        }
217 340
        if ($this->_component_sudo) {
218 336
            return true;
219
        }
220 7
        if ($user === null) {
221 7
            $user =& $this->user;
222
        }
223
224 7
        if ($user == 'EVERYONE') {
0 ignored issues
show
introduced by
The condition $user == 'EVERYONE' is always false.
Loading history...
225
            $user = null;
226
        }
227
228 7
        return $this->acl->can_do_byclass($privilege, $user, $class);
229
    }
230
231
    /**
232
     * Request superuser privileges for the domain passed.
233
     *
234
     * STUB IMPLEMENTATION ONLY, WILL ALWAYS GRANT SUDO.
235
     *
236
     * You have to call midcom_services_auth::drop_sudo() as soon as you no longer
237
     * need the elevated privileges, which will reset the authentication data to the
238
     * initial credentials.
239
     *
240
     * @param string $domain The domain to request sudo for. This is a component name.
241
     */
242 686
    public function request_sudo(string $domain = null) : bool
243
    {
244 686
        if (!midcom::get()->config->get('auth_allow_sudo')) {
245 1
            debug_add("SUDO is not allowed on this website.", MIDCOM_LOG_ERROR);
246 1
            return false;
247
        }
248
249 686
        if ($domain === null) {
250 1
            $domain = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_COMPONENT);
251 1
            debug_add("Domain was not supplied, falling back to '{$domain}' which we got from the current component context.");
252
        }
253
254 686
        if ($domain == '') {
255 1
            debug_add("SUDO request for an empty domain, this should not happen. Denying sudo.", MIDCOM_LOG_INFO);
256 1
            return false;
257
        }
258
259 686
        $this->_component_sudo++;
260
261 686
        debug_add("Entered SUDO mode for domain {$domain}.", MIDCOM_LOG_INFO);
262
263 686
        return true;
264
    }
265
266
    /**
267
     * Drops previously acquired superuser privileges.
268
     *
269
     * @see request_sudo()
270
     */
271 686
    public function drop_sudo()
272
    {
273 686
        if ($this->_component_sudo > 0) {
274 686
            debug_add('Leaving SUDO mode.');
275 686
            $this->_component_sudo--;
276
        } else {
277 1
            debug_add('Requested to leave SUDO mode, but sudo was already disabled. Ignoring request.', MIDCOM_LOG_INFO);
278
        }
279
    }
280
281 663
    public function is_component_sudo() : bool
282
    {
283 663
        return $this->_component_sudo > 0;
284
    }
285
286
    /**
287
     * Check, whether a user is member of a given group. By default, the query is run
288
     * against the currently authenticated user.
289
     *
290
     * It always returns true for administrative users.
291
     *
292
     * @param mixed $group Group to check against, this can be either a midcom_core_group object or a group string identifier.
293
     * @param midcom_core_user $user The user which should be checked, defaults to the current user.
294
     */
295
    public function is_group_member($group, $user = null) : bool
296
    {
297
        if ($this->is_admin($user)) {
298
            // Administrators always have access.
299
            return true;
300
        }
301
        // Default parameter
302
        if ($user === null) {
303
            if ($this->user === null) {
304
                // not authenticated
305
                return false;
306
            }
307
            $user = $this->user;
308
        }
309
310
        return $user->is_in_group($group);
311
    }
312
313
    /**
314
     * Returns true if there is an authenticated user, false otherwise.
315
     */
316 155
    public function is_valid_user() : bool
317
    {
318 155
        return $this->user !== null;
319
    }
320
321
    /**
322
     * Validates that the current user has the given privilege granted on the
323
     * content object passed to the function.
324
     *
325
     * If this is not the case, an Access Denied error is generated, the message
326
     * defaulting to the string 'access denied: privilege %s not granted' of the
327
     * MidCOM main L10n table.
328
     *
329
     * The check is always done against the currently authenticated user. If the
330
     * check is successful, the function returns silently.
331
     *
332
     * @param MidgardObject $content_object A Midgard Content Object
333
     */
334 194
    public function require_do(string $privilege, object $content_object, string $message = null)
335
    {
336 194
        if (!$this->can_do($privilege, $content_object)) {
337
            throw $this->access_denied($message, 'privilege %s not granted', $privilege);
338
        }
339
    }
340
341
    /**
342
     * Validates, whether the given user have the privilege assigned to him in general.
343
     * Be aware, that this does not take any permissions overridden by content objects
344
     * into account. Whenever possible, you should user the require_do() variant of this
345
     * call therefore. require_user_do is only of interest in cases where you do not have
346
     * any content object available, for example when creating root topics.
347
     *
348
     * If this is not the case, an Access Denied error is generated, the message
349
     * defaulting to the string 'access denied: privilege %s not granted' of the
350
     * MidCOM main L10n table.
351
     *
352
     * The check is always done against the currently authenticated user. If the
353
     * check is successful, the function returns silently.
354
     *
355
     * @param string $class Optional parameter to set if the check should take type specific permissions into account. The class must be default constructible.
356
     */
357 77
    public function require_user_do(string $privilege, string $message = null, string $class = null)
358
    {
359 77
        if (!$this->can_user_do($privilege, null, $class)) {
360
            throw $this->access_denied($message, 'privilege %s not granted', $privilege);
361
        }
362
    }
363
364
    /**
365
     * Validates that the current user is a member of the given group.
366
     *
367
     * If this is not the case, an Access Denied error is generated, the message
368
     * defaulting to the string 'access denied: user is not member of the group %s' of the
369
     * MidCOM main L10n table.
370
     *
371
     * The check is always done against the currently authenticated user. If the
372
     * check is successful, the function returns silently.
373
     *
374
     * @param mixed $group Group to check against, this can be either a midcom_core_group object or a group string identifier.
375
     * @param string $message The message to show if the user is not member of the given group.
376
     */
377
    function require_group_member($group, $message = null)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
378
    {
379
        if (!$this->is_group_member($group)) {
380
            if (is_object($group)) {
381
                $group = $group->name;
382
            }
383
            throw $this->access_denied($message, 'user is not member of the group %s', $group);
384
        }
385
    }
386
387
    /**
388
     * Validates that we currently have admin level privileges, which can either
389
     * come from the current user, or from the sudo service.
390
     *
391
     * If the check is successful, the function returns silently.
392
     */
393 4
    public function require_admin_user(string $message = null)
394
    {
395 4
        if (!$this->admin && !$this->_component_sudo) {
396
            throw $this->access_denied($message, 'admin level privileges required');
397
        }
398
    }
399
400
    private function access_denied(?string $message, string $fallback, string $data = null) : midcom_error_forbidden
401
    {
402
        if ($message === null) {
403
            $message = midcom::get()->i18n->get_string('access denied: ' . $fallback, 'midcom');
404
            if ($data !== null) {
405
                $message = sprintf($message, $data);
406
            }
407
        }
408
        debug_print_function_stack("access_denied was called from here:");
409
        return new midcom_error_forbidden($message);
410
    }
411
412
    /**
413
     * Require either a configured IP address or admin credentials
414
     */
415
    public function require_admin_or_ip(string $domain) : bool
416
    {
417
        $ips = midcom::get()->config->get_array('indexer_reindex_allowed_ips');
418
        if (in_array($_SERVER['REMOTE_ADDR'], $ips)) {
419
            if (!$this->request_sudo($domain)) {
420
                throw new midcom_error('Failed to acquire SUDO rights. Aborting.');
421
            }
422
            return true;
423
        }
424
425
        // Require user to Basic-authenticate for security reasons
426
        $this->require_valid_user('basic');
427
        $this->require_admin_user();
428
        return false;
429
    }
430
431
    /**
432
     * Validates that there is an authenticated user.
433
     *
434
     * If this is not the case, midcom_error_forbidden is thrown, or a
435
     * basic auth challenge is triggered
436
     *
437
     * If the check is successful, the function returns silently.
438
     *
439
     * @param string $method Preferred authentication method: form or basic
440
     */
441 153
    public function require_valid_user(string $method = 'form')
442
    {
443 153
        if ($method === 'basic') {
444 4
            $this->_http_basic_auth();
445
        }
446 153
        if (!$this->is_valid_user()) {
447
            throw new midcom_error_forbidden(null, Response::HTTP_UNAUTHORIZED, $method);
448
        }
449
    }
450
451
    /**
452
     * Handles HTTP Basic authentication
453
     */
454 4
    private function _http_basic_auth()
455
    {
456 4
        if (isset($_SERVER['PHP_AUTH_USER'])) {
457
            if ($user = $this->backend->authenticate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
458
                $this->set_user($user);
459
            } else {
460
                // Wrong password
461
                unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
462
            }
463
        }
464
    }
465
466
    /**
467
     * Resolve any assignee identifier known by the system into an appropriate user/group object.
468
     *
469
     * @param string $id A valid user or group identifier usable as assignee (e.g. the $id member
470
     *     of any midcom_core_user or midcom_core_group object).
471
     * @return object|null corresponding object or false on failure.
472
     */
473 132
    public function get_assignee(string $id) : ?object
474
    {
475 132
        $parts = explode(':', $id);
476
477 132
        if ($parts[0] == 'user') {
478 131
            return $this->get_user($id);
479
        }
480 4
        if ($parts[0] == 'group') {
481 3
            return $this->get_group($id);
482
        }
483 1
        debug_add("The identifier {$id} cannot be resolved into an assignee, it cannot be mapped to a type.", MIDCOM_LOG_WARN);
484
485 1
        return null;
486
    }
487
488
    /**
489
     * This is a wrapper for get_user, which allows user retrieval by its name.
490
     * If the username is unknown, false is returned.
491
     */
492 3
    public function get_user_by_name(string $name) : ?midcom_core_user
493
    {
494 3
        $mc = new midgard_collector('midgard_user', 'login', $name);
495 3
        $mc->set_key_property('person');
496 3
        $mc->add_constraint('authtype', '=', midcom::get()->config->get('auth_type'));
497 3
        $mc->execute();
498 3
        $keys = $mc->list_keys();
499 3
        if (count($keys) != 1) {
500
            return null;
501
        }
502
503 3
        return $this->get_user(key($keys));
504
    }
505
506
    /**
507
     * This is a wrapper for get_group, which allows Midgard Group retrieval by its name.
508
     * If the group name is unknown, false is returned.
509
     *
510
     * In the case that more than one group matches the given name, the first one is returned.
511
     * Note, that this should not happen as midgard group names should be unique according to the specs.
512
     */
513
    public function get_midgard_group_by_name(string $name) : ?midcom_core_group
514
    {
515
        $qb = new midgard_query_builder('midgard_group');
516
        $qb->add_constraint('name', '=', $name);
517
518
        if ($result = $qb->execute()) {
519
            return $this->get_group($result[0]);
520
        }
521
        return null;
522
    }
523
524
    /**
525
     * Load a user from the database and returns an object instance.
526
     *
527
     * @param mixed $id A valid identifier for a MidgardPerson: An existing midgard_person class
528
     *     or subclass thereof, a Person ID or GUID or a midcom_core_user identifier.
529
     */
530 190
    public function get_user($id) : ?midcom_core_user
531
    {
532 190
        return $this->backend->get_user($id);
533
    }
534
535
    /**
536
     * Returns a midcom_core_group instance. Valid arguments are either a valid group identifier
537
     * (group:...), any valid identifier for the midcom_core_group
538
     * constructor or a valid object of that type.
539
     *
540
     * @param mixed $id The identifier of the group as outlined above.
541
     */
542 3
    public function get_group($id) : ?midcom_core_group
543
    {
544 3
        $param = $id;
545
546 3
        if (isset($param->id)) {
547
            $id = $param->id;
548 3
        } elseif (!is_string($id) && !is_int($id)) {
549
            debug_add('The group identifier is of an unsupported type: ' . gettype($param), MIDCOM_LOG_WARN);
550
            debug_print_r('Complete dump:', $param);
551
            return null;
552
        }
553
554 3
        if (!array_key_exists($id, $this->_group_cache)) {
555
            try {
556 3
                if ($param instanceof midcom_core_dbaobject) {
557
                    $param = $param->__object;
558
                }
559 3
                $this->_group_cache[$id] = new midcom_core_group($param);
560
            } catch (midcom_error $e) {
561
                debug_add("Group with identifier {$id} could not be loaded: " . $e->getMessage(), MIDCOM_LOG_WARN);
562
                $this->_group_cache[$id] = null;
563
            }
564
        }
565 3
        return $this->_group_cache[$id];
566
    }
567
568
    /**
569
     * This call tells the backend to log in.
570
     */
571 65
    public function login(string $username, string $password, string $clientip = null) : bool
572
    {
573 65
        if ($user = $this->backend->login($username, $password, $clientip)) {
574 65
            $this->set_user($user);
575 65
            return true;
576
        }
577
        debug_add('The login information for ' . $username . ' was invalid.', MIDCOM_LOG_WARN);
578
        return false;
579
    }
580
581
    public function trusted_login(string $username) : bool
582
    {
583
        if (midcom::get()->config->get('auth_allow_trusted') !== true) {
584
            debug_add("Trusted logins are prohibited", MIDCOM_LOG_ERROR);
585
            return false;
586
        }
587
588
        if ($user = $this->backend->login($username, '', null, true)) {
589
            $this->set_user($user);
590
            return true;
591
        }
592
        return false;
593
    }
594
595
    /**
596
     * This call clears any authentication state
597
     */
598 1
    public function logout()
599
    {
600 1
        if ($this->user === null) {
601
            debug_add('The backend has no authenticated user set, so we should be fine');
602
        } else {
603 1
            $this->backend->logout($this->user);
604 1
            $this->user = null;
605
        }
606 1
        $this->admin = false;
607
    }
608
609
    /**
610
     * Render the main login form.
611
     * This only includes the form, no heading or whatsoever.
612
     *
613
     * It is recommended to call this function only as long as the headers are not yet sent (which
614
     * is usually given thanks to MidCOMs output buffering).
615
     *
616
     * What gets rendered depends on the authentication frontend, but will usually be some kind
617
     * of form.
618
     */
619
    public function show_login_form()
620
    {
621
        $this->frontend->show_login_form();
622
    }
623
624
    public function has_login_data() : bool
625
    {
626
        return $this->frontend->has_login_data();
627
    }
628
}
629