Passed
Push — master ( bdcb4c...250cfa )
by Andreas
26:00
created

midcom_services_auth::require_user_do()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

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

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

143
                return new midcom_response_relocate(/** @scrutinizer ignore-type */ $request->get('midcom_services_auth_login_success_url'));
Loading history...
144
            }
145
        }
146
        // No new login detected, so we check if there is a running session.
147 1
        elseif ($user = $this->backend->check_for_active_login_session($request)) {
148
            $this->set_user($user);
149
        }
150
    }
151
152 64
    private function set_user(midcom_core_user $user)
153
    {
154 64
        $this->user = $user;
155 64
        $this->admin = $user->is_admin();
156
    }
157
158
    /**
159
     * Checks whether a user has a certain privilege on the given content object.
160
     * Works on the currently authenticated user by default, but can take another
161
     * user as an optional argument.
162
     *
163
     * @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...
164
     * @param midcom_core_user $user The user against which to check the privilege, defaults to the currently authenticated user.
165
     *     You may specify "EVERYONE" instead of an object to check what an anonymous user can do.
166
     */
167 463
    public function can_do(string $privilege, object $content_object, $user = null) : bool
168
    {
169 463
        if ($this->is_admin($user)) {
170
            // Administrators always have access.
171 2
            return true;
172
        }
173
174 463
        $user_id = $this->acl->get_user_id($user);
175
176
        //if we're handed the correct object type, we use its class right away
177 463
        if (midcom::get()->dbclassloader->is_midcom_db_object($content_object)) {
178 463
            $content_object_class = get_class($content_object);
179
        }
180
        //otherwise, we assume (hope) that it's a midgard object
181
        else {
182
            $content_object_class = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($content_object);
183
        }
184
185 463
        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

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