Passed
Push — master ( 85a2c6...18cd31 )
by Nils
07:03 queued 14s
created

handleAuthAttempts()   B

Complexity

Conditions 10
Paths 7

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 39
nc 7
nop 2
dl 0
loc 62
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 * @file      identify.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use voku\helper\AntiXSS;
28
use EZimuel\PHPSecureSession;
29
use TeampassClasses\SessionManager\SessionManager;
30
use Symfony\Component\HttpFoundation\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
31
use TeampassClasses\Language\Language;
32
use TeampassClasses\PerformChecks\PerformChecks;
33
use LdapRecord\Connection;
34
use LdapRecord\Container;
35
use LdapRecord\Auth\Events\Failed;
36
use TeampassClasses\NestedTree\NestedTree;
37
use PasswordLib\PasswordLib;
38
use Duo\DuoUniversal\Client;
39
use Duo\DuoUniversal\DuoException;
40
use RobThree\Auth\TwoFactorAuth;
41
use TeampassClasses\LdapExtra\LdapExtra;
42
use TeampassClasses\LdapExtra\OpenLdapExtra;
43
use TeampassClasses\LdapExtra\ActiveDirectoryExtra;
44
45
// Load functions
46
require_once 'main.functions.php';
47
48
// init
49
loadClasses('DB');
50
$session = SessionManager::getSession();
51
$request = Request::createFromGlobals();
52
$lang = new Language(); 
53
54
55
// Load config if $SETTINGS not defined
56
try {
57
    include_once __DIR__.'/../includes/config/tp.config.php';
58
} catch (Exception $e) {
59
    throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
60
}
61
62
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true || $SETTINGS['cpassman_dir'] === '.') {
63
    $SETTINGS = [];
64
    $SETTINGS['cpassman_dir'] = '..';
65
}
66
//error_log('Identify.php: '.print_r($_POST, true));
67
// Do checks
68
// Instantiate the class with posted data
69
$checkUserAccess = new PerformChecks(
70
    dataSanitizer(
71
        [
72
            'type' => $request->request->get('type', '') !== '' ? htmlspecialchars($request->request->get('type')) : '',
73
        ],
74
        [
75
            'type' => 'trim|escape',
76
        ],
77
    ),
78
    [
79
        'user_id' => returnIfSet($session->get('user-id'), null),
80
        'user_key' => returnIfSet($session->get('key'), null),
81
        'login' => isset($_POST['login']) === false ? null : $_POST['login'],
82
    ]
83
);
84
85
// Handle the case
86
echo $checkUserAccess->caseHandler();
87
if ($checkUserAccess->checkSession() === false) {
88
    // Not allowed page
89
    $session->set('system-error_code', ERR_NOT_ALLOWED);
90
    include $SETTINGS['cpassman_dir'] . '/error.php';
91
    exit;
92
}
93
94
// Define Timezone
95
date_default_timezone_set(isset($SETTINGS['timezone']) === true ? $SETTINGS['timezone'] : 'UTC');
96
97
// Set header properties
98
header('Content-type: text/html; charset=utf-8');
99
header('Cache-Control: no-cache, no-store, must-revalidate');
100
error_reporting(E_ERROR);
101
102
// --------------------------------- //
103
104
// Prepare POST variables
105
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
106
$post_login = filter_input(INPUT_POST, 'login', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
107
$post_data = filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
108
109
110
if ($post_type === 'identify_user') {
111
    //--------
112
    // NORMAL IDENTICATION STEP
113
    //--------
114
115
    // Ensure Complexity levels are translated
116
    defineComplexity();
117
118
    /**
119
     * Permits to handle login attempts
120
     *
121
     * @param string $post_data
122
     * @param array $SETTINGS
123
     * @return bool|string
124
     */
125
    function handleAuthAttempts($post_data, $SETTINGS): bool|string
126
    {
127
        $session = SessionManager::getSession();
128
        $lang = new Language();
129
        $sessionPwdAttempts = $session->get('pwd_attempts');
130
        $nextPossibleAttempts = (int) $session->get('next_possible_pwd_attempts');
131
132
        // Check if the user is currently within the waiting period
133
        if ($nextPossibleAttempts > 0 && time() < $nextPossibleAttempts) {
134
            // Brute force wait
135
            $remainingSeconds = $nextPossibleAttempts - time();
136
            $errorResponse = prepareExchangedData([
137
                'value' => 'bruteforce_wait',
138
                'user_admin' => null !== $session->get('user-admin') ? (int) $session->get('user-admin') : 0,
139
                'initial_url' => null !== $session->get('user-initial_url') ? $session->get('user-initial_url') : '',
140
                'pwd_attempts' => 0,
141
                'error' => true,
142
                'message' => $lang->get('error_bad_credentials_more_than_3_times'),
143
                'remaining_seconds' => $remainingSeconds,
144
            ], 'encode');
145
146
            echo $errorResponse;
147
            return false;
148
        }
149
150
        // Increment the counter of login attempts
151
        $sessionPwdAttempts = ($sessionPwdAttempts === '') ? 1 : ++$sessionPwdAttempts;
152
        $session->set('pwd_attempts', $sessionPwdAttempts);
153
154
        // Check for brute force attempts
155
        if ($sessionPwdAttempts <= 3) {
156
            // Identify the user through Teampass process
157
            identifyUser($post_data, $SETTINGS);
158
        } else {
159
            // Reset attempts and set waiting period on the fourth consecutive attempt
160
            $session->set('pwd_attempts', 0);
161
162
            if ($sessionPwdAttempts === 4) {
163
                // On the fourth consecutive attempt, trigger the waiting period
164
                $nextPossibleAttempts = time() + 10;
165
                $session->set('next_possible_pwd_attempts', $nextPossibleAttempts);
166
167
                // Send an error response indicating the waiting period
168
                $errorResponse = prepareExchangedData([
169
                    'value' => 'bruteforce_wait',
170
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionAdmin does not exist. Did you maybe mean $session?
Loading history...
171
                    'initial_url' => isset($sessionUrl) ? $sessionUrl : '',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionUrl does not exist. Did you maybe mean $session?
Loading history...
172
                    'pwd_attempts' => 0,
173
                    'error' => true,
174
                    'message' => $lang->get('error_bad_credentials_more_than_3_times'),
175
                    'remaining_seconds' => 10,
176
                ], 'encode');
177
178
                echo $errorResponse;
179
                return false;
180
            }
181
            
182
            // Identify the user through Teampass process
183
            identifyUser($post_data, $SETTINGS);
184
        }
185
186
        return true;
187
    }
188
189
    handleAuthAttempts($post_data, $SETTINGS);
190
191
    // ---
192
    // ---
193
    // ---
194
} elseif ($post_type === 'get2FAMethods') {
195
    //--------
196
    // Get MFA methods
197
    //--------
198
    //
199
200
    // Encrypt data to return
201
    echo json_encode([
202
        'ret' => prepareExchangedData(
203
            [
204
                'agses' => isKeyExistingAndEqual('agses_authentication_enabled', 1, $SETTINGS) === true ? true : false,
205
                'google' => isKeyExistingAndEqual('google_authentication', 1, $SETTINGS) === true ? true : false,
206
                'yubico' => isKeyExistingAndEqual('yubico_authentication', 1, $SETTINGS) === true ? true : false,
207
                'duo' => isKeyExistingAndEqual('duo', 1, $SETTINGS) === true ? true : false,
208
            ],
209
            'encode'
210
        ),
211
        'key' => $session->get('key'),
212
    ]);
213
    return false;
214
}
215
216
/**
217
 * Complete authentication of user through Teampass
218
 *
219
 * @param string $sentData Credentials
220
 * @param array $SETTINGS Teampass settings
221
 *
222
 * @return bool
223
 */
224
function identifyUser(string $sentData, array $SETTINGS): bool
225
{
226
    $antiXss = new AntiXSS();
227
    
228
    $request = Request::createFromGlobals();
229
    $lang = new Language();
230
    $session = SessionManager::getSession();
231
232
    // Prepare GET variables
233
    $sessionAdmin = $session->get('user-admin');
234
    $sessionPwdAttempts = $session->get('pwd_attempts');
235
    $sessionUrl = $session->get('user-initial_url');
236
    $server = [];
237
    $server['PHP_AUTH_USER'] =  $request->server->get('PHP_AUTH_USER');
238
    $server['PHP_AUTH_PW'] = $request->server->get('PHP_AUTH_PW');
239
    
240
    // decrypt and retreive data in JSON format
241
    if ($session->get('key') === null) {
242
        $dataReceived = $sentData;
243
    } else {
244
        $dataReceived = prepareExchangedData(
245
            $sentData,
246
            'decode',
247
            $session->get('key')
248
        );
249
    }
250
251
    // Check if Duo auth is in progress and pass the pw and login back to the standard login process
252
    if(
253
        isKeyExistingAndEqual('duo', 1, $SETTINGS) === true
254
        && $dataReceived['user_2fa_selection'] === 'duo'
255
        && $session->get('user-duo_status') === 'IN_PROGRESS'
256
        && !empty($dataReceived['duo_state'])
257
    ){
258
        $key = hash('sha256', $dataReceived['duo_state']);
259
        $iv = substr(hash('sha256', $dataReceived['duo_state']), 0, 16);
260
        $duo_data_dec = openssl_decrypt(base64_decode($session->get('user-duo_status')), 'AES-256-CBC', $key, 0, $iv);
261
        // Clear the data from the Duo process to continue clean with the standard login process
262
        $session->set('user-duo_data','');
263
        if($duo_data_dec === false){
264
            echo prepareExchangedData(
265
                [
266
                    'error' => true,
267
                    'message' => $lang->get('duo_error_decrypt'),
268
                ],
269
                'encode'
270
            );
271
            return false;
272
        }
273
        $duo_data = unserialize($duo_data_dec);
274
        $dataReceived['pw'] = $duo_data['duo_pwd'];
275
        $dataReceived['login'] = $duo_data['duo_login'];
276
    }
277
278
    if(isset($dataReceived['pw']) === false || isset($dataReceived['login']) === false) {
279
        echo json_encode([
280
            'data' => prepareExchangedData(
281
                [
282
                    'error' => true,
283
                    'message' => $lang->get('ga_enter_credentials'),
284
                ],
285
                'encode'
286
            ),
287
            'key' => $session->get('key')
288
        ]);
289
        return false;
290
    }
291
292
    // prepare variables    
293
    $userCredentials = identifyGetUserCredentials(
294
        $SETTINGS,
295
        (string) $server['PHP_AUTH_USER'],
296
        (string) $server['PHP_AUTH_PW'],
297
        (string) filter_var($dataReceived['pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
298
        (string) filter_var($dataReceived['login'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
299
    );
300
    $username = $userCredentials['username'];
301
    $passwordClear = $userCredentials['passwordClear'];
302
    
303
    // DO initial checks
304
    $userInitialData = identifyDoInitialChecks(
305
        $SETTINGS,
306
        (int) $sessionPwdAttempts,
307
        (string) $username,
308
        (int) $sessionAdmin,
309
        (string) $sessionUrl,
310
        (string) filter_var($dataReceived['user_2fa_selection'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
311
    );
312
    // if user doesn't exist in Teampass then return error
313
    if ($userInitialData['error'] === true) {
314
        echo prepareExchangedData(
315
            $userInitialData['array'],
316
            'encode'
317
        );
318
        return false;
319
    }
320
321
    $userInfo = $userInitialData['userInfo'];
322
    $return = '';
323
    $userLdap = identifyDoLDAPChecks(
324
        $SETTINGS,
325
        $userInfo,
326
        (string) $username,
327
        (string) $passwordClear,
328
        (int) $sessionAdmin,
329
        (string) $sessionUrl,
330
        (int) $sessionPwdAttempts
331
    );
332
    if ($userLdap['error'] === true) {
333
        echo prepareExchangedData(
334
            $userLdap['array'],
335
            'encode'
336
        );
337
        return false;
338
    }
339
    if (isset($userLdap['user_info']) === true && (int) $userLdap['user_info']['has_been_created'] === 1) {
340
        /*$userInfo = DB::queryfirstrow(
341
            'SELECT *
342
            FROM ' . prefixTable('users') . '
343
            WHERE login = %s',
344
            $username
345
        );*/
346
        //$userInfo = $userLdap['user_info'];
347
        echo json_encode([
348
            'data' => prepareExchangedData(
349
                [
350
                    'error' => true,
351
                    'message' => '',
352
                    'extra' => 'ad_user_created',
353
                ],
354
                'encode'
355
            ),
356
            'key' => $session->get('key')
357
        ]);
358
        return false;
359
    }
360
361
    // Check user and password
362
    if ($userLdap['userPasswordVerified'] === false && (int) checkCredentials($passwordClear, $userInfo, $dataReceived, $username, $SETTINGS) !== 1) {
363
        echo prepareExchangedData(
364
            [
365
                'value' => '',
366
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
367
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
368
                'pwd_attempts' => (int) $sessionPwdAttempts,
369
                'error' => true,
370
                'message' => $lang->get('error_bad_credentials'),
371
            ],
372
            'encode'
373
        );
374
        return false;
375
    }
376
377
    // Check if MFA is required
378
    if ((isOneVarOfArrayEqualToValue(
379
                [
380
                    (int) $SETTINGS['yubico_authentication'],
381
                    (int) $SETTINGS['google_authentication'],
382
                    (int) $SETTINGS['duo']
383
                ],
384
                1
385
            ) === true)
386
        && (((int) $userInfo['admin'] !== 1 && (int) $userInfo['mfa_enabled'] === 1) || ((int) $SETTINGS['admin_2fa_required'] === 1 && (int) $userInfo['admin'] === 1))
387
        && $userInfo['mfa_auth_requested_roles'] === true
388
    ) {
389
        // Check user against MFA method if selected
390
        $userMfa = identifyDoMFAChecks(
391
            $SETTINGS,
392
            $userInfo,
393
            $dataReceived,
394
            $userInitialData,
395
            (string) $username
396
        );
397
        if ($userMfa['error'] === true) {
398
            echo prepareExchangedData(
399
                [
400
                    'error' => true,
401
                    'message' => $userMfa['mfaData']['message'],
402
                    'mfaStatus' => $userMfa['mfaData']['mfaStatus'],
403
                ],
404
                'encode'
405
            );
406
            return false;
407
        } elseif ($userMfa['mfaQRCodeInfos'] === true) {
408
            // Case where user has initiated Google Auth
409
            // Return QR code
410
            echo prepareExchangedData(
411
                [
412
                    'value' => $userMfa['mfaData']['value'],
413
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
414
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
415
                    'pwd_attempts' => (int) $sessionPwdAttempts,
416
                    'error' => false,
417
                    'message' => $userMfa['mfaData']['message'],
418
                    'mfaStatus' => $userMfa['mfaData']['mfaStatus'],
419
                ],
420
                'encode'
421
            );
422
            return false;
423
        } elseif ($userMfa['duo_url_ready'] === true) {
424
            // Case where user has initiated Duo Auth
425
            // Return the DUO redirect URL
426
            echo prepareExchangedData(
427
                [
428
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
429
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
430
                    'pwd_attempts' => (int) $sessionPwdAttempts,
431
                    'error' => false,
432
                    'message' => $userMfa['mfaData']['message'],
433
                    'duo_url_ready' => $userMfa['mfaData']['duo_url_ready'],
434
                    'duo_redirect_url' => $userMfa['mfaData']['duo_redirect_url'],
435
                    'mfaStatus' => $userMfa['mfaData']['mfaStatus'],
436
                ],
437
                'encode'
438
            );
439
            return false;
440
        }
441
    }
442
    
443
    // Can connect if
444
    // 1- no LDAP mode + user enabled + pw ok
445
    // 2- LDAP mode + user enabled + ldap connection ok + user is not admin
446
    // 3- LDAP mode + user enabled + pw ok + usre is admin
447
    // This in order to allow admin by default to connect even if LDAP is activated
448
    if (canUserGetLog(
449
            $SETTINGS,
450
            (int) $userInfo['disabled'],
451
            $username,
452
            $userLdap['ldapConnection']
453
        ) === true
454
    ) {
455
        $session->set('pwd_attempts', 0);
456
457
        // Check if any unsuccessfull login tries exist
458
        $attemptsInfos = handleLoginAttempts(
459
            $userInfo['id'],
460
            $userInfo['login'],
461
            $userInfo['last_connexion'],
462
            $username,
463
            $SETTINGS,
464
        );
465
        
466
        // Save account in SESSION
467
        $session->set('user-unsuccessfull_login_attempts_list', $attemptsInfos['attemptsList'] === 0 ? true : false);
468
        $session->set('user-unsuccessfull_login_attempts_shown', $attemptsInfos['attemptsCount'] === 0 ? true : false);
469
        $session->set('user-unsuccessfull_login_attempts_nb', DB::count());
470
        $session->set('user-login', stripslashes($username));
471
        $session->set('user-name', empty($userInfo['name']) === false ? stripslashes($userInfo['name']) : '');
472
        $session->set('user-lastname', empty($userInfo['lastname']) === false ? stripslashes($userInfo['lastname']) : '');
473
        $session->set('user-id', (int) $userInfo['id']);
474
        $session->set('user-password', $passwordClear);
475
        $session->set('user-admin', (int) $userInfo['admin']);
476
        $session->set('user-manager', (int) $userInfo['gestionnaire']);
477
        $session->set('user-can_manage_all_users', $userInfo['can_manage_all_users']);
478
        $session->set('user-read_only', $userInfo['read_only']);
479
        $session->set('user-last_pw_change', $userInfo['last_pw_change']);
480
        $session->set('user-last_pw', $userInfo['last_pw']);
481
        $session->set('user-force_relog', $userInfo['force-relog']);
482
        $session->set('user-can_create_root_folder', $userInfo['can_create_root_folder']);
483
        $session->set('user-email', $userInfo['email']);
484
        //$session->set('user-ga', $userInfo['ga']);
485
        $session->set('user-avatar', $userInfo['avatar']);
486
        $session->set('user-avatar_thumb', $userInfo['avatar_thumb']);
487
        $session->set('user-upgrade_needed', $userInfo['upgrade_needed']);
488
        $session->set('user-is_ready_for_usage', $userInfo['is_ready_for_usage']);
489
        $session->set('user-personal_folder_enabled', $userInfo['personal_folder']);
490
        $session->set(
491
            'user-tree_load_strategy',
492
            (isset($userInfo['treeloadstrategy']) === false || empty($userInfo['treeloadstrategy']) === true) ? 'full' : $userInfo['treeloadstrategy']
493
        );
494
        $session->set('user-language', $userInfo['user_language']);
495
        $session->set('user-timezone', $userInfo['usertimezone']);
496
        $session->set('user-keys_recovery_time', $userInfo['keys_recovery_time']);
497
498
        // User signature keys
499
        $returnKeys = prepareUserEncryptionKeys($userInfo, $passwordClear);  
500
        $session->set('user-private_key', $returnKeys['private_key_clear']);
501
        $session->set('user-public_key', $returnKeys['public_key']);      
502
503
        // API key
504
        $session->set(
505
            'user-api_key',
506
            empty($userInfo['api_key']) === false ? base64_decode(decryptUserObjectKey($userInfo['api_key'], $returnKeys['private_key_clear'])) : '',
507
        );
508
        
509
        $session->set('user-special', $userInfo['special']);
510
        $session->set('user-auth_type', $userInfo['auth_type']);
511
        // manage session expiration
512
        $session->set('user-session_duration', (int) (time() + ($dataReceived['duree_session'] * 60)));
513
514
        // check feedback regarding user password validity
515
        $return = checkUserPasswordValidity(
516
            $userInfo,
517
            $session->get('user-num_days_before_exp'),
518
            $session->get('user-last_pw_change'),
519
            $SETTINGS
520
        );
521
        $session->set('user-validite_pw', $return['validite_pw']);
522
        $session->set('user-last_pw_change', $return['last_pw_change']);
523
        $session->set('user-num_days_before_exp', $return['numDaysBeforePwExpiration']);
524
        $session->set('user-force_relog', $return['user_force_relog']);
525
        
526
        $session->set('user-last_connection', empty($userInfo['last_connexion']) === false ? (int) $userInfo['last_connexion'] : (int) time());
527
        $session->set('user-latest_items', empty($userInfo['latest_items']) === false ? explode(';', $userInfo['latest_items']) : []);
528
        $session->set('user-favorites', empty($userInfo['favourites']) === false ? explode(';', $userInfo['favourites']) : []);
529
        $session->set('user-accessible_folders', empty($userInfo['groupes_visibles']) === false ? explode(';', $userInfo['groupes_visibles']) : []);
530
        $session->set('user-no_access_folders', empty($userInfo['groupes_interdits']) === false ? explode(';', $userInfo['groupes_interdits']) : []);
531
        
532
        // User's roles
533
        if (strpos($userInfo['fonction_id'] !== NULL ? (string) $userInfo['fonction_id'] : '', ',') !== -1) {
534
            // Convert , to ;
535
            $userInfo['fonction_id'] = str_replace(',', ';', (string) $userInfo['fonction_id']);
536
            DB::update(
537
                prefixTable('users'),
538
                [
539
                    'fonction_id' => $userInfo['fonction_id'],
540
                ],
541
                'id = %i',
542
                $session->get('user-id')
543
            );
544
        }
545
        // Append with roles from AD groups
546
        if (is_null($userInfo['roles_from_ad_groups']) === false) {
547
            $userInfo['fonction_id'] = empty($userInfo['fonction_id'])  === true ? $userInfo['roles_from_ad_groups'] : $userInfo['fonction_id']. ';' . $userInfo['roles_from_ad_groups'];
548
        }
549
        // store
550
        $session->set('user-roles', $userInfo['fonction_id']);
551
        $session->set('user-roles_array', array_unique(array_filter(explode(';', $userInfo['fonction_id']))));
552
        
553
        // build array of roles
554
        $session->set('user-pw_complexity', 0);
555
        $session->set('system-array_roles', []);
556
        if (count($session->get('user-roles_array')) > 0) {
557
            $rolesList = DB::query(
558
                'SELECT id, title, complexity
559
                FROM ' . prefixTable('roles_title') . '
560
                WHERE id IN %li',
561
                $session->get('user-roles_array')
562
            );
563
            $excludeUser = isset($SETTINGS['exclude_user']) ? str_contains($session->get('user-login'), $SETTINGS['exclude_user']) : false;
564
            $adjustPermissions = ($session->get('user-id') >= 1000000 && !$excludeUser && (isset($SETTINGS['admin_needle']) || isset($SETTINGS['manager_needle']) || isset($SETTINGS['tp_manager_needle']) || isset($SETTINGS['read_only_needle'])));
565
            if ($adjustPermissions) {
566
                $userInfo['admin'] = $userInfo['gestionnaire'] = $userInfo['can_manage_all_users'] = $userInfo['read_only'] = 0;
567
            }
568
            foreach ($rolesList as $role) {
569
                SessionManager::addRemoveFromSessionAssociativeArray(
570
                    'system-array_roles',
571
                    [
572
                        'id' => $role['id'],
573
                        'title' => $role['title'],
574
                    ],
575
                    'add'
576
                );
577
                
578
                if ($adjustPermissions) {
579
                    if (isset($SETTINGS['admin_needle']) && str_contains($role['title'], $SETTINGS['admin_needle'])) {
580
                        $userInfo['gestionnaire'] = $userInfo['can_manage_all_users'] = $userInfo['read_only'] = 0;
581
                        $userInfo['admin'] = 1;
582
                    }    
583
                    if (isset($SETTINGS['manager_needle']) && str_contains($role['title'], $SETTINGS['manager_needle'])) {
584
                        $userInfo['admin'] = $userInfo['can_manage_all_users'] = $userInfo['read_only'] = 0;
585
                        $userInfo['gestionnaire'] = 1;
586
                    }
587
                    if (isset($SETTINGS['tp_manager_needle']) && str_contains($role['title'], $SETTINGS['tp_manager_needle'])) {
588
                        $userInfo['admin'] = $userInfo['gestionnaire'] = $userInfo['read_only'] = 0;
589
                        $userInfo['can_manage_all_users'] = 1;
590
                    }
591
                    if (isset($SETTINGS['read_only_needle']) && str_contains($role['title'], $SETTINGS['read_only_needle'])) {
592
                        $userInfo['admin'] = $userInfo['gestionnaire'] = $userInfo['can_manage_all_users'] = 0;
593
                        $userInfo['read_only'] = 1;
594
                    }
595
                }
596
597
                // get highest complexity
598
                if ($session->get('user-pw_complexity') < (int) $role['complexity']) {
599
                    $session->set('user-pw_complexity', (int) $role['complexity']);
600
                }
601
            }
602
            if ($adjustPermissions) {
603
                $session->set('user-admin', (int) $userInfo['admin']);
604
                $session->set('user-manager', (int) $userInfo['gestionnaire']);
605
                $session->set('user-can_manage_all_users',(int)  $userInfo['can_manage_all_users']);
606
                $session->set('user-read_only', (int) $userInfo['read_only']);
607
                DB::update(
608
                    prefixTable('users'),
609
                    [
610
                        'admin' => $userInfo['admin'],
611
                        'gestionnaire' => $userInfo['gestionnaire'],
612
                        'can_manage_all_users' => $userInfo['can_manage_all_users'],
613
                        'read_only' => $userInfo['read_only'],
614
                    ],
615
                    'id = %i',
616
                    $session->get('user-id')
617
                );
618
            }
619
        }
620
621
        // Set some settings
622
        $SETTINGS['update_needed'] = '';
623
624
        // Update table
625
        DB::update(
626
            prefixTable('users'),
627
            array_merge(
628
                [
629
                    'key_tempo' => $session->get('key'),
630
                    'last_connexion' => time(),
631
                    'timestamp' => time(),
632
                    'disabled' => 0,
633
                    'no_bad_attempts' => 0,
634
                    'session_end' => $session->get('user-session_duration'),
635
                    'user_ip' => $dataReceived['client'],
636
                ],
637
                $returnKeys['update_keys_in_db']
638
            ),
639
            'id=%i',
640
            $userInfo['id']
641
        );
642
        
643
        // Get user's rights
644
        if ($userLdap['user_initial_creation_through_ldap'] === true) {
645
            // is new LDAP user. Show only his personal folder
646
            if ($SETTINGS['enable_pf_feature'] === '1') {
647
                $session->set('user-personal_visible_folders', [$userInfo['id']]);
648
                $session->set('user-personal_folders', [$userInfo['id']]);
649
            } else {
650
                $session->set('user-personal_visible_folders', []);
651
                $session->set('user-personal_folders', []);
652
            }
653
            $session->set('user-all_non_personal_folders', []);
654
            $session->set('user-roles_array', []);
655
            $session->set('user-read_only_folders', []);
656
            $session->set('user-list_folders_limited', []);
657
            $session->set('system-list_folders_editable_by_role', []);
658
            $session->set('system-list_restricted_folders_for_items', []);
659
            $session->set('user-nb_folders', 1);
660
            $session->set('user-nb_roles', 1);
661
        } else {
662
            identifyUserRights(
663
                $userInfo['groupes_visibles'],
664
                $session->get('user-no_access_folders'),
665
                $userInfo['admin'],
666
                $userInfo['fonction_id'],
667
                $SETTINGS
668
            );
669
        }
670
        // Get some more elements
671
        $session->set('system-screen_height', $dataReceived['screenHeight']);
672
673
        // Get last seen items
674
        $session->set('user-latest_items_tab', []);
675
        $session->set('user-nb_roles', 0);
676
        foreach ($session->get('user-latest_items') as $item) {
677
            if (! empty($item)) {
678
                $dataLastItems = DB::queryFirstRow(
679
                    'SELECT id,label,id_tree
680
                    FROM ' . prefixTable('items') . '
681
                    WHERE id=%i',
682
                    $item
683
                );
684
                SessionManager::addRemoveFromSessionAssociativeArray(
685
                    'user-latest_items_tab',
686
                    [
687
                        'id' => $item,
688
                        'label' => $dataLastItems['label'],
689
                        'url' => 'index.php?page=items&amp;group=' . $dataLastItems['id_tree'] . '&amp;id=' . $item,
690
                    ],
691
                    'add'
692
                );
693
            }
694
        }
695
696
        // Get cahce tree info
697
        $cacheTreeData = DB::queryFirstRow(
698
            'SELECT visible_folders
699
            FROM ' . prefixTable('cache_tree') . '
700
            WHERE user_id=%i',
701
            (int) $session->get('user-id')
702
        );
703
        if (DB::count() > 0 && empty($cacheTreeData['visible_folders']) === true) {
704
            $session->set('user-cache_tree', '');
705
            // Prepare new task
706
            DB::insert(
707
                prefixTable('processes'),
708
                array(
709
                    'created_at' => time(),
710
                    'process_type' => 'user_build_cache_tree',
711
                    'arguments' => json_encode([
712
                        'user_id' => (int) $session->get('user-id'),
713
                    ], JSON_HEX_QUOT | JSON_HEX_TAG),
714
                    'updated_at' => '',
715
                    'finished_at' => '',
716
                    'output' => '',
717
                )
718
            );
719
        } else {
720
            $session->set('user-cache_tree', $cacheTreeData['visible_folders']);
721
        }
722
723
        // send back the random key
724
        $return = $dataReceived['randomstring'];
725
        // Send email
726
        if (
727
            isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true
728
            && (int) $sessionAdmin !== 1
729
        ) {
730
            // get all Admin users
731
            $val = DB::queryfirstrow('SELECT email FROM ' . prefixTable('users') . " WHERE admin = %i and email != ''", 1);
732
            if (DB::count() > 0) {
733
                // Add email to table
734
                prepareSendingEmail(
735
                    $lang->get('email_subject_on_user_login'),
736
                    str_replace(
737
                        [
738
                            '#tp_user#',
739
                            '#tp_date#',
740
                            '#tp_time#',
741
                        ],
742
                        [
743
                            ' ' . $session->get('user-login') . ' (IP: ' . getClientIpServer() . ')',
744
                            date($SETTINGS['date_format'], (int) $session->get('user-last_connection')),
745
                            date($SETTINGS['time_format'], (int) $session->get('user-last_connection')),
746
                        ],
747
                        $lang->get('email_body_on_user_login')
748
                    ),
749
                    $val['email'],
750
                    $lang->get('administrator')
751
                );
752
            }
753
        }
754
755
        // Ensure Complexity levels are translated
756
        defineComplexity();
757
        echo prepareExchangedData(
758
            [
759
                'value' => $return,
760
                'user_id' => $session->get('user-id') !== null ? $session->get('user-id') : '',
761
                'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
762
                'initial_url' => $antiXss->xss_clean($sessionUrl),
763
                'pwd_attempts' => 0,
764
                'error' => false,
765
                'message' => $session->has('user-upgrade_needed') && (int) $session->get('user-upgrade_needed') && (int) $session->get('user-upgrade_needed') === 1 ? 'ask_for_otc' : '',
766
                'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
767
                'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
768
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
769
                'private_key_conform' => $session->get('user-id') !== null
770
                    && empty($session->get('user-private_key')) === false
771
                    && $session->get('user-private_key') !== 'none' ? true : false,
772
                'session_key' => $session->get('key'),
773
                'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
774
                'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
775
                'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
776
                'upgrade_needed' => isset($userInfo['upgrade_needed']) === true ? (int) $userInfo['upgrade_needed'] : 0,
777
                'special' => isset($userInfo['special']) === true ? (int) $userInfo['special'] : 0,
778
            ],
779
            'encode'
780
        );
781
    
782
        return true;
783
784
    } elseif ((int) $userInfo['disabled'] === 1) {
785
        // User and password is okay but account is locked
786
        echo prepareExchangedData(
787
            [
788
                'value' => $return,
789
                'user_id' => $session->get('user-id') !== null ? (int) $session->get('user-id') : '',
790
                'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
791
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
792
                'pwd_attempts' => 0,
793
                'error' => 'user_is_locked',
794
                'message' => $lang->get('account_is_locked'),
795
                'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
796
                'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
797
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
798
                'private_key_conform' => $session->has('user-private_key') && null !== $session->get('user-private_key')
799
                    && empty($session->get('user-private_key')) === false
800
                    && $session->get('user-private_key') !== 'none' ? true : false,
801
                'session_key' => $session->get('key'),
802
                'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
803
                'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
804
                'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
805
            ],
806
            'encode'
807
        );
808
        return false;
809
    }
810
811
    // DEFAULT CASE
812
    // User exists in the DB but Password is false
813
    // check if user is locked
814
    if (isUserLocked(
815
            (int) $userInfo['no_bad_attempts'],
816
            $userInfo['id'],
817
            $username,
818
            $SETTINGS
819
        ) === true
820
    ) {
821
        echo prepareExchangedData(
822
            [
823
                'value' => $return,
824
                'user_id' => $session->get('user-id') !== null ? (int) $session->get('user-id') : '',
825
                'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
826
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
827
                'pwd_attempts' => 0,
828
                'error' => 'user_is_locked',
829
                'message' => $lang->get('account_is_locked'),
830
                'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
831
                'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
832
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
833
                'private_key_conform' => $session->get('user-id') !== null
834
                    && empty($session->get('user-private_key')) === false
835
                    && $session->get('user-private_key') !== 'none' ? true : false,
836
                'session_key' => $session->get('key'),
837
                'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
838
                'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
839
                'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
840
            ],
841
            'encode'
842
        );
843
        return false;
844
    }
845
    echo prepareExchangedData(
846
        [
847
            'value' => $return,
848
            'user_id' => $session->get('user-id') !== null ? (int) $session->get('user-id') : '',
849
            'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
850
            'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
851
            'pwd_attempts' => (int) $sessionPwdAttempts,
852
            'error' => true,
853
            'message' => $lang->get('error_not_allowed_to_authenticate'),
854
            'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
855
            'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
856
            'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
857
            'private_key_conform' => $session->get('user-id') !== null
858
                    && empty($session->get('user-private_key')) === false
859
                    && $session->get('user-private_key') !== 'none' ? true : false,
860
            'session_key' => $session->get('key'),
861
            'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
862
            'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
863
            'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
864
        ],
865
        'encode'
866
    );
867
    return false;
868
}
869
870
/**
871
 * Check if any unsuccessfull login tries exist
872
 *
873
 * @param int       $userInfoId
874
 * @param string    $userInfoLogin
875
 * @param string    $userInfoLastConnection
876
 * @param string    $username
877
 * @param array     $SETTINGS
878
 * @return array
879
 */
880
function handleLoginAttempts(
881
    $userInfoId,
882
    $userInfoLogin,
883
    $userInfoLastConnection,
884
    $username,
885
    $SETTINGS
886
) : array
887
{
888
    $rows = DB::query(
889
        'SELECT date
890
        FROM ' . prefixTable('log_system') . "
891
        WHERE field_1 = %s
892
        AND type = 'failed_auth'
893
        AND label = 'password_is_not_correct'
894
        AND date >= %s AND date < %s",
895
        $userInfoLogin,
896
        $userInfoLastConnection,
897
        time()
898
    );
899
    $arrAttempts = [];
900
    if (DB::count() > 0) {
901
        foreach ($rows as $record) {
902
            array_push(
903
                $arrAttempts,
904
                date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['date'])
905
            );
906
        }
907
    }
908
    
909
910
    // Log into DB the user's connection
911
    if (isKeyExistingAndEqual('log_connections', 1, $SETTINGS) === true) {
912
        logEvents($SETTINGS, 'user_connection', 'connection', (string) $userInfoId, stripslashes($username));
913
    }
914
915
    return [
916
        'attemptsList' => $arrAttempts,
917
        'attemptsCount' => count($rows),
918
    ];
919
}
920
921
/**
922
 * Permits to load config file
923
 *
924
 * @return boolean
925
 */
926
function findTpConfigFile() : bool
927
{
928
    if (file_exists('../includes/config/tp.config.php')) {
929
        include_once '../includes/config/tp.config.php';
930
        return true;
931
    } elseif (file_exists('./includes/config/tp.config.php')) {
932
        include_once './includes/config/tp.config.php';
933
    } elseif (file_exists('../../includes/config/tp.config.php')) {
934
        include_once '../../includes/config/tp.config.php';
935
    } elseif (file_exists('../../../includes/config/tp.config.php')) {
936
        include_once '../../../includes/config/tp.config.php';
937
    }
938
    return false;
939
}
940
941
/**
942
 * Can you user get logged into main page
943
 *
944
 * @param array     $SETTINGS
945
 * @param int       $userInfoDisabled
946
 * @param string    $username
947
 * @param bool      $ldapConnection
948
 *
949
 * @return boolean
950
 */
951
function canUserGetLog(
952
    $SETTINGS,
953
    $userInfoDisabled,
954
    $username,
955
    $ldapConnection
956
) : bool
957
{
958
    include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
959
960
    if ((int) $userInfoDisabled === 1) {
961
        return false;
962
    }
963
964
    if (isKeyExistingAndEqual('ldap_mode', 0, $SETTINGS) === true) {
965
        return true;
966
    }
967
    
968
    if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true 
969
        && (
970
            ($ldapConnection === true && $username !== 'admin')
971
            || $username === 'admin'
972
        )
973
    ) {
974
        return true;
975
    }
976
977
    if (isKeyExistingAndEqual('ldap_and_local_authentication', 1, $SETTINGS) === true
978
        && isset($SETTINGS['ldap_mode']) === true && in_array($SETTINGS['ldap_mode'], ['1', '2']) === true
979
    ) {
980
        return true;
981
    }
982
983
    return false;
984
}
985
986
/**
987
 * Manages if user is locked or not
988
 *
989
 * @param int       $nbAttempts
990
 * @param int       $userId
991
 * @param string    $username
992
 * @param string    $key
993
 * @param array     $SETTINGS
994
 *
995
 * @return boolean
996
 */
997
function isUserLocked(
998
    $nbAttempts,
999
    $userId,
1000
    $username,
1001
    $SETTINGS
1002
) : bool 
1003
{
1004
    $userIsLocked = false;
1005
    $nbAttempts++;
1006
    if (
1007
        (int) $SETTINGS['nb_bad_authentication'] > 0
1008
        && (int) $SETTINGS['nb_bad_authentication'] < $nbAttempts
1009
    ) {
1010
        // User is now locked as too many attempts
1011
        $userIsLocked = true;
1012
1013
        // log it
1014
        if (isKeyExistingAndEqual('log_connections', 1, $SETTINGS) === true) {
1015
            logEvents($SETTINGS, 'user_locked', 'connection', (string) $userId, stripslashes($username));
1016
        }
1017
    }
1018
    
1019
    DB::update(
1020
        prefixTable('users'),
1021
        [
1022
            'disabled' => $userIsLocked,
1023
            'no_bad_attempts' => $nbAttempts,
1024
        ],
1025
        'id=%i',
1026
        $userId
1027
    );
1028
1029
    return $userIsLocked;
1030
}
1031
1032
1033
/**
1034
 * 
1035
 * Prepare user keys
1036
 * 
1037
 * @param array $userInfo   User account information
1038
 * @param string $passwordClear
1039
 *
1040
 * @return array
1041
 */
1042
function prepareUserEncryptionKeys($userInfo, $passwordClear) : array
1043
{
1044
    if (is_null($userInfo['private_key']) === true || empty($userInfo['private_key']) === true || $userInfo['private_key'] === 'none') {
1045
        // No keys have been generated yet
1046
        // Create them
1047
        $userKeys = generateUserKeys($passwordClear);
1048
1049
        return [
1050
            'public_key' => $userKeys['public_key'],
1051
            'private_key_clear' => $userKeys['private_key_clear'],
1052
            'update_keys_in_db' => [
1053
                'public_key' => $userKeys['public_key'],
1054
                'private_key' => $userKeys['private_key'],
1055
            ],
1056
        ];
1057
    } 
1058
    
1059
    if ($userInfo['special'] === 'generate-keys') {
1060
        return [
1061
            'public_key' => $userInfo['public_key'],
1062
            'private_key_clear' => '',
1063
            'update_keys_in_db' => [],
1064
        ];
1065
    }
1066
    
1067
    // Don't perform this in case of special login action
1068
    if ($userInfo['special'] === 'otc_is_required_on_next_login' || $userInfo['special'] === 'user_added_from_ldap') {
1069
        return [
1070
            'public_key' => $userInfo['public_key'],
1071
            'private_key_clear' => '',
1072
            'update_keys_in_db' => [],
1073
        ];
1074
    }
1075
    
1076
    // Uncrypt private key
1077
    return [
1078
        'public_key' => $userInfo['public_key'],
1079
        'private_key_clear' => decryptPrivateKey($passwordClear, $userInfo['private_key']),
1080
        'update_keys_in_db' => [],
1081
    ];
1082
}
1083
1084
1085
/**
1086
 * CHECK PASSWORD VALIDITY
1087
 * Don't take into consideration if LDAP in use
1088
 * 
1089
 * @param array $userInfo                       User account information
1090
 * @param int $numDaysBeforePwExpiration
1091
 * @param int $lastPwChange
1092
 * @param array $SETTINGS                       Teampass settings
1093
 *
1094
 * @return array
1095
 */
1096
function checkUserPasswordValidity($userInfo, $numDaysBeforePwExpiration, $lastPwChange, $SETTINGS)
1097
{
1098
    if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true) {
1099
        return [
1100
            'validite_pw' => true,
1101
            'last_pw_change' => $userInfo['last_pw_change'],
1102
            'user_force_relog' => '',
1103
            'numDaysBeforePwExpiration' => '',
1104
        ];
1105
    }
1106
1107
    if (isset($userInfo['last_pw_change']) === true) {
1108
        if ((int) $SETTINGS['pw_life_duration'] === 0) {
1109
            return [
1110
                'validite_pw' => true,
1111
                'last_pw_change' => '',
1112
                'user_force_relog' => 'infinite',
1113
                'numDaysBeforePwExpiration' => '',
1114
            ];
1115
        }
1116
        
1117
        return [
1118
            'validite_pw' => $numDaysBeforePwExpiration <= 0 ? false : true,
1119
            'last_pw_change' => $userInfo['last_pw_change'],
1120
            'user_force_relog' => 'infinite',
1121
            'numDaysBeforePwExpiration' => $SETTINGS['pw_life_duration'] - round(
1122
                (mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')) - $lastPwChange) / (24 * 60 * 60)),
1123
        ];
1124
    } else {
1125
        return [
1126
            'validite_pw' => false,
1127
            'last_pw_change' => '',
1128
            'user_force_relog' => '',
1129
            'numDaysBeforePwExpiration' => '',
1130
        ];
1131
    }
1132
}
1133
1134
1135
/**
1136
 * Authenticate a user through AD.
1137
 *
1138
 * @param string $username      Username
1139
 * @param array $userInfo       User account information
1140
 * @param string $passwordClear Password
1141
 * @param array $SETTINGS       Teampass settings
1142
 *
1143
 * @return array
1144
 */
1145
function authenticateThroughAD(string $username, array $userInfo, string $passwordClear, array $SETTINGS): array
1146
{
1147
    $lang = new Language(); 
1148
1149
    // 1- Connect to LDAP
1150
    try {
1151
        switch ($SETTINGS['ldap_type']) {
1152
            case 'ActiveDirectory':
1153
                $ldapExtra = new LdapExtra($SETTINGS);
1154
                $ldapConnection = $ldapExtra->establishLdapConnection();
1155
                $activeDirectoryExtra = new ActiveDirectoryExtra();
1156
                break;
1157
            case 'OpenLDAP':
1158
                // Establish connection for OpenLDAP
1159
                $ldapExtra = new LdapExtra($SETTINGS);
1160
                $ldapConnection = $ldapExtra->establishLdapConnection();
1161
1162
                // Create an instance of OpenLdapExtra and configure it
1163
                $openLdapExtra = new OpenLdapExtra();
1164
                break;
1165
            default:
1166
                throw new Exception("Unsupported LDAP type: " . $SETTINGS['ldap_type']);
1167
        }
1168
    } catch (Exception $e) {
1169
        return [
1170
            'error' => true,
1171
            'message' => "Error:".$e->getMessage(),
1172
        ];
1173
    }
1174
    
1175
    try {
1176
        // 2- Get user info from AD
1177
        // We want to isolate attribute ldap_user_attribute or mostly samAccountName
1178
        $userADInfos = $ldapConnection->query()
1179
            ->where((isset($SETTINGS['ldap_user_attribute']) ===true && empty($SETTINGS['ldap_user_attribute']) === false) ? $SETTINGS['ldap_user_attribute'] : 'samaccountname', '=', $username)
1180
            ->firstOrFail();
1181
1182
        // Is user enabled? Only ActiveDirectory
1183
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory' && isset($activeDirectoryExtra) === true && $activeDirectoryExtra instanceof ActiveDirectoryExtra) {
1184
            //require_once 'ldap.activedirectory.php';
1185
            if ($activeDirectoryExtra->userIsEnabled((string) $userADInfos['dn'], $ldapConnection) === false) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $activeDirectoryExtra does not seem to be defined for all execution paths leading up to this point.
Loading history...
1186
                return [
1187
                    'error' => true,
1188
                    'message' => "Error : User is not enabled",
1189
                ];
1190
            }
1191
        }
1192
1193
        // 3- User auth attempt
1194
        // For AD, we use attribute userPrincipalName
1195
        // For OpenLDAP and others, we use attribute dn
1196
        $userAuthAttempt = $ldapConnection->auth()->attempt(
1197
            $SETTINGS['ldap_type'] === 'ActiveDirectory' ?
1198
                $userADInfos['userprincipalname'][0] :  // refering to https://ldaprecord.com/docs/core/v2/authentication#basic-authentication
1199
                $userADInfos['dn'],
1200
            $passwordClear
1201
        );
1202
1203
        // User is not auth then return error
1204
        if ($userAuthAttempt === false) {
1205
            return [
1206
                'error' => true,
1207
                'message' => "Error: User is not authenticated",
1208
            ];
1209
        }
1210
1211
    } catch (\LdapRecord\Query\ObjectNotFoundException $e) {
1212
        $error = $e->getDetailedError();
1213
        return [
1214
            'error' => true,
1215
            'message' => $lang->get('error')." - ".(isset($error) === true ? $error->getErrorCode()." - ".$error->getErrorMessage(). "<br>".$error->getDiagnosticMessage() : $e),
1216
1217
        ];
1218
    }
1219
1220
    // 4- Check shadowexpire attribute
1221
    // if === 1 then user disabled
1222
    if (
1223
        (isset($userADInfos['shadowexpire'][0]) === true && (int) $userADInfos['shadowexpire'][0] === 1)
1224
        ||
1225
        (isset($userADInfos['accountexpires'][0]) === true && (int) $userADInfos['accountexpires'][0] < time() && (int) $userADInfos['accountexpires'][0] != 0)
1226
    ) {
1227
        return [
1228
            'error' => true,
1229
            'message' => $lang->get('error_ad_user_expired'),
1230
        ];
1231
    }
1232
1233
    // Create LDAP user if not exists and tasks enabled
1234
    if ($userInfo['ldap_user_to_be_created'] === true) {   
1235
        $userInfo = ldapCreateUser(
1236
            $username,
1237
            $passwordClear,
1238
            $userADInfos['mail'][0],
1239
            $userADInfos['givenname'][0],
1240
            $userADInfos['sn'][0],
1241
            $SETTINGS
1242
        );
1243
1244
        // prepapre background tasks for item keys generation  
1245
        handleUserKeys(
1246
            (int) $userInfo['id'],
1247
            (string) $passwordClear,
1248
            (int) (isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : NUMBER_ITEMS_IN_BATCH),
1249
            uniqidReal(20),
1250
            true,
1251
            true,
1252
            true,
1253
            false,
1254
            $lang->get('email_body_user_config_2'),
1255
        );
1256
1257
        // Complete $userInfo
1258
        $userInfo['has_been_created'] = 1;
1259
    } else {
1260
        $userInfo['has_been_created'] = 0;
1261
    }
1262
1263
    // Update user info with his AD groups
1264
    if ($SETTINGS['ldap_type'] === 'ActiveDirectory' && isset($activeDirectoryExtra) && $activeDirectoryExtra instanceof ActiveDirectoryExtra) {
1265
        $userGroupsData = $activeDirectoryExtra->getUserADGroups(
1266
            $userADInfos[(isset($SETTINGS['ldap_user_dn_attribute']) === true && empty($SETTINGS['ldap_user_dn_attribute']) === false) ? $SETTINGS['ldap_user_dn_attribute'] : 'distinguishedname'][0], 
1267
            $ldapConnection, 
1268
            $SETTINGS
1269
        );
1270
    } elseif ($SETTINGS['ldap_type'] == 'OpenLDAP' && isset($openLdapExtra) && $openLdapExtra instanceof OpenLdapExtra) {
1271
        $userGroupsData = $openLdapExtra->getUserADGroups(
1272
            $userADInfos['dn'],
1273
            $ldapConnection,
1274
            $SETTINGS
1275
        );
1276
    } else {
1277
        // error
1278
        return [
1279
            'error' => true,
1280
            'message' => "Error: Unsupported LDAP type: " . $SETTINGS['ldap_type'],
1281
        ];
1282
    }
1283
    
1284
    handleUserADGroups(
1285
        $username,
1286
        $userInfo,
1287
        $userGroupsData['userGroups'],
1288
        $SETTINGS
1289
    );
1290
1291
    // Finalize authentication
1292
    finalizeAuthentication($userInfo, $passwordClear, $SETTINGS);
1293
1294
    return [
1295
        'error' => false,
1296
        'message' => '',
1297
        'user_info' => $userInfo,
1298
    ];
1299
}
1300
1301
/**
1302
 * Permits to update the user's AD groups with mapping roles
1303
 *
1304
 * @param string $username
1305
 * @param array $userInfo
1306
 * @param array $groups
1307
 * @param array $SETTINGS
1308
 * @return void
1309
 */
1310
function handleUserADGroups(string $username, array $userInfo, array $groups, array $SETTINGS): void
1311
{
1312
    if (isset($SETTINGS['enable_ad_users_with_ad_groups']) === true && (int) $SETTINGS['enable_ad_users_with_ad_groups'] === 1) {
1313
        // Get user groups from AD
1314
        $user_ad_groups = [];
1315
        foreach($groups as $group) {
1316
            //print_r($group);
1317
            // get relation role id for AD group
1318
            $role = DB::queryFirstRow(
1319
                'SELECT lgr.role_id
1320
                FROM ' . prefixTable('ldap_groups_roles') . ' AS lgr
1321
                WHERE lgr.ldap_group_id = %i',
1322
                $group
1323
            );
1324
            if (DB::count() > 0) {
1325
                array_push($user_ad_groups, $role['role_id']); 
1326
            }
1327
        }
1328
        
1329
        // save
1330
        if (count($user_ad_groups) > 0) {
1331
            $user_ad_groups = implode(';', $user_ad_groups);
1332
            DB::update(
1333
                prefixTable('users'),
1334
                [
1335
                    'roles_from_ad_groups' => $user_ad_groups,
1336
                ],
1337
                'id = %i',
1338
                $userInfo['id']
1339
            );
1340
1341
            $userInfo['roles_from_ad_groups'] = $user_ad_groups;
1342
        } else {
1343
            DB::update(
1344
                prefixTable('users'),
1345
                [
1346
                    'roles_from_ad_groups' => null,
1347
                ],
1348
                'id = %i',
1349
                $userInfo['id']
1350
            );
1351
1352
            $userInfo['roles_from_ad_groups'] = [];
1353
        }
1354
    } else {
1355
        // Delete all user's AD groups
1356
        DB::update(
1357
            prefixTable('users'),
1358
            [
1359
                'roles_from_ad_groups' => null,
1360
            ],
1361
            'id = %i',
1362
            $userInfo['id']
1363
        );
1364
    }
1365
}
1366
1367
/**
1368
 * Permits to finalize the authentication process.
1369
 *
1370
 * @param array $userInfo
1371
 * @param string $passwordClear
1372
 * @param array $SETTINGS
1373
 */
1374
function finalizeAuthentication(
1375
    array $userInfo,
1376
    string $passwordClear,
1377
    array $SETTINGS
1378
): void
1379
{
1380
    // load passwordLib library
1381
    $pwdlib = new PasswordLib();
1382
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
1383
1384
    //If user has never been connected then erase current pwd with the ldap's one
1385
    if (empty($userInfo['pw']) === true) {
1386
        // Password are similar in Teampass and AD
1387
        DB::update(
1388
            prefixTable('users'),
1389
            [
1390
                'pw' => $hashedPassword,
1391
            ],
1392
            'id = %i',
1393
            $userInfo['id']
1394
        );
1395
    } elseif ($userInfo['special'] === 'user_added_from_ldap') {
1396
        // Case where user has been added from LDAP and never being connected to TP
1397
        DB::update(
1398
            prefixTable('users'),
1399
            array(
1400
                'pw' => $hashedPassword,
1401
            ),
1402
            'id = %i',
1403
            $userInfo['id']
1404
        );
1405
    } elseif ($pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw']) === false) {
1406
        // Case where user is auth by LDAP but his password in Teampass is not synchronized
1407
        // For example when user has changed his password in AD.
1408
        // So we need to update it in Teampass and ask for private key re-encryption
1409
        DB::update(
1410
            prefixTable('users'),
1411
            [
1412
                'pw' => $hashedPassword,
1413
            ],
1414
            'id = %i',
1415
            $userInfo['id']
1416
        );
1417
    }
1418
}
1419
1420
/**
1421
 * Undocumented function.
1422
 *
1423
 * @param string|array|resource $dataReceived Received data
1424
 * @param string                $userInfo     Result of query
1425
 * @param array                 $SETTINGS     Teampass settings
1426
 *
1427
 * @return array
1428
 */
1429
function yubicoMFACheck($dataReceived, string $userInfo, array $SETTINGS): array
1430
{
1431
    $session = SessionManager::getSession();
1432
    $lang = new Language(); 
1433
    $sessionAdmin = $session->get('user-admin');
1434
    $sessionUrl = $session->get('user-initial_url');
1435
    $sessionPwdAttempts = $session->get('pwd_attempts');
1436
    // Init
1437
    $yubico_key = htmlspecialchars_decode($dataReceived['yubico_key']);
1438
    $yubico_user_key = htmlspecialchars_decode($dataReceived['yubico_user_key']);
1439
    $yubico_user_id = htmlspecialchars_decode($dataReceived['yubico_user_id']);
1440
    if (empty($yubico_user_key) === false && empty($yubico_user_id) === false) {
1441
        // save the new yubico in user's account
1442
        DB::update(
1443
            prefixTable('users'),
1444
            [
1445
                'yubico_user_key' => $yubico_user_key,
1446
                'yubico_user_id' => $yubico_user_id,
1447
            ],
1448
            'id=%i',
1449
            $userInfo['id']
1450
        );
1451
    } else {
1452
        // Check existing yubico credentials
1453
        if ($userInfo['yubico_user_key'] === 'none' || $userInfo['yubico_user_id'] === 'none') {
1454
            return [
1455
                'error' => true,
1456
                'value' => '',
1457
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1458
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1459
                'pwd_attempts' => (int) $sessionPwdAttempts,
1460
                'message' => 'no_user_yubico_credentials',
1461
                'proceedIdentification' => false,
1462
            ];
1463
        }
1464
        $yubico_user_key = $userInfo['yubico_user_key'];
1465
        $yubico_user_id = $userInfo['yubico_user_id'];
1466
    }
1467
1468
    // Now check yubico validity
1469
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Authentication/Yubico/Yubico.php';
1470
    $yubi = new Auth_Yubico($yubico_user_id, $yubico_user_key);
1471
    $auth = $yubi->verify($yubico_key);
1472
    //, null, null, null, 60
1473
1474
    if (PEAR::isError($auth)) {
1475
        return [
1476
            'error' => true,
1477
            'value' => '',
1478
            'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1479
            'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1480
            'pwd_attempts' => (int) $sessionPwdAttempts,
1481
            'message' => $lang->get('yubico_bad_code'),
1482
            'proceedIdentification' => false,
1483
        ];
1484
    }
1485
1486
    return [
1487
        'error' => false,
1488
        'message' => '',
1489
        'proceedIdentification' => true,
1490
    ];
1491
}
1492
1493
/**
1494
 * Undocumented function.
1495
 *
1496
 * @param string $username      User name
1497
 * @param string $passwordClear User password in clear
1498
 * @param array $retLDAP       Received data from LDAP
1499
 * @param array $SETTINGS      Teampass settings
1500
 *
1501
 * @return array
1502
 */
1503
function ldapCreateUser(string $login, string $passwordClear, string $userEmail, string $userName, string $userLastname, array $SETTINGS): array
1504
{
1505
    // Generate user keys pair
1506
    $userKeys = generateUserKeys($passwordClear);
1507
1508
    // load passwordLib library
1509
    $pwdlib = new PasswordLib();
1510
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
1511
1512
    // Insert user in DB
1513
    DB::insert(
1514
        prefixTable('users'),
1515
        [
1516
            'login' => (string) $login,
1517
            'pw' => (string) $hashedPassword,
1518
            'email' => (string) $userEmail,
1519
            'name' => (string) $userName,
1520
            'lastname' => (string) $userLastname,
1521
            'admin' => '0',
1522
            'gestionnaire' => '0',
1523
            'can_manage_all_users' => '0',
1524
            'personal_folder' => $SETTINGS['enable_pf_feature'] === '1' ? '1' : '0',
1525
            'groupes_interdits' => '',
1526
            'groupes_visibles' => '',
1527
            'last_pw_change' => (int) time(),
1528
            'user-language' => (string) $SETTINGS['default_language'],
1529
            'encrypted_psk' => '',
1530
            'isAdministratedByRole' => isset($SETTINGS['ldap_new_user_is_administrated_by']) === true && empty($SETTINGS['ldap_new_user_is_administrated_by']) === false ? $SETTINGS['ldap_new_user_is_administrated_by'] : 0,
1531
            'public_key' => $userKeys['public_key'],
1532
            'private_key' => $userKeys['private_key'],
1533
            'special' => 'none',
1534
            'auth_type' => 'ldap',
1535
            'otp_provided' => '1',
1536
            'is_ready_for_usage' => '0',
1537
        ]
1538
    );
1539
    $newUserId = DB::insertId();
1540
1541
    // Create the API key
1542
    DB::insert(
1543
        prefixTable('api'),
1544
        array(
1545
            'type' => 'user',
1546
            'user_id' => $newUserId,
1547
            'value' => encryptUserObjectKey(base64_encode(base64_encode(uniqidReal(39))), $userKeys['public_key']),
1548
            'timestamp' => time(),
1549
        )
1550
    );
1551
1552
    // Create personnal folder
1553
    if (isKeyExistingAndEqual('enable_pf_feature', 1, $SETTINGS) === true) {
1554
        DB::insert(
1555
            prefixTable('nested_tree'),
1556
            [
1557
                'parent_id' => '0',
1558
                'title' => $newUserId,
1559
                'bloquer_creation' => '0',
1560
                'bloquer_modification' => '0',
1561
                'personal_folder' => '1',
1562
                'categories' => '',
1563
            ]
1564
        );
1565
        // Rebuild tree
1566
        $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1567
        $tree->rebuild();
1568
    }
1569
1570
    return [
1571
        'error' => false,
1572
        'message' => '',
1573
        'proceedIdentification' => true,
1574
        'user_initial_creation_through_ldap' => true,
1575
        'id' => $newUserId,
1576
    ];
1577
}
1578
1579
/**
1580
 * Undocumented function.
1581
 *
1582
 * @param string                $username     Username
1583
 * @param array                 $userInfo     Result of query
1584
 * @param string|array|resource $dataReceived DataReceived
1585
 * @param array                 $SETTINGS     Teampass settings
1586
 *
1587
 * @return array
1588
 */
1589
function googleMFACheck(string $username, array $userInfo, $dataReceived, array $SETTINGS): array
1590
{
1591
    $session = SessionManager::getSession();    
1592
    $lang = new Language(); 
1593
1594
    if (
1595
        isset($dataReceived['GACode']) === true
1596
        && empty($dataReceived['GACode']) === false
1597
    ) {
1598
        $sessionAdmin = $session->get('user-admin');
1599
        $sessionUrl = $session->get('user-initial_url');
1600
        $sessionPwdAttempts = $session->get('pwd_attempts');
1601
        // create new instance
1602
        $tfa = new TwoFactorAuth($SETTINGS['ga_website_name']);
1603
        // Init
1604
        $firstTime = [];
1605
        // now check if it is the 1st time the user is using 2FA
1606
        if ($userInfo['ga_temporary_code'] !== 'none' && $userInfo['ga_temporary_code'] !== 'done') {
1607
            if ($userInfo['ga_temporary_code'] !== $dataReceived['GACode']) {
1608
                return [
1609
                    'error' => true,
1610
                    'message' => $lang->get('ga_bad_code'),
1611
                    'proceedIdentification' => false,
1612
                    'ga_bad_code' => true,
1613
                    'firstTime' => $firstTime,
1614
                ];
1615
            }
1616
1617
            // If first time with MFA code
1618
            $proceedIdentification = false;
1619
            $mfaStatus = 'ga_temporary_code_correct';
1620
            $mfaMessage = $lang->get('ga_flash_qr_and_login');
1621
            // generate new QR
1622
            $new_2fa_qr = $tfa->getQRCodeImageAsDataUri(
1623
                'Teampass - ' . $username,
1624
                $userInfo['ga']
1625
            );
1626
            // clear temporary code from DB
1627
            DB::update(
1628
                prefixTable('users'),
1629
                [
1630
                    'ga_temporary_code' => 'done',
1631
                ],
1632
                'id=%i',
1633
                $userInfo['id']
1634
            );
1635
            $firstTime = [
1636
                'value' => '<img src="' . $new_2fa_qr . '">',
1637
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1638
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1639
                'pwd_attempts' => (int) $sessionPwdAttempts,
1640
                'message' => $mfaMessage,
1641
                'mfaStatus' => $mfaStatus,
1642
            ];
1643
        } else {
1644
            // verify the user GA code
1645
            if ($tfa->verifyCode($userInfo['ga'], $dataReceived['GACode'])) {
1646
                $proceedIdentification = true;
1647
            } else {
1648
                return [
1649
                    'error' => true,
1650
                    'message' => $lang->get('ga_bad_code'),
1651
                    'proceedIdentification' => false,
1652
                    'ga_bad_code' => true,
1653
                    'firstTime' => $firstTime,
1654
                ];
1655
            }
1656
        }
1657
    } else {
1658
        return [
1659
            'error' => true,
1660
            'message' => $lang->get('ga_bad_code'),
1661
            'proceedIdentification' => false,
1662
            'ga_bad_code' => true,
1663
            'firstTime' => [],
1664
        ];
1665
    }
1666
1667
    return [
1668
        'error' => false,
1669
        'message' => '',
1670
        'proceedIdentification' => $proceedIdentification,
1671
        'firstTime' => $firstTime,
1672
    ];
1673
}
1674
1675
1676
/**
1677
 * Perform DUO checks
1678
 *
1679
 * @param string $username
1680
 * @param string|array|resource $dataReceived
1681
 * @param array $SETTINGS
1682
 * @return array
1683
 */
1684
function duoMFACheck(
1685
    string $username,
1686
    $dataReceived,
1687
    array $SETTINGS
1688
): array
1689
{
1690
    $session = SessionManager::getSession();
1691
    $lang = new Language(); 
1692
1693
    $sessionPwdAttempts = $session->get('pwd_attempts');
1694
    $saved_state = null !== $session->get('user-duo_state') ? $session->get('user-duo_state') : '';
1695
    $duo_status = null !== $session->get('user-duo_status') ? $session->get('user-duo_status') : '';
1696
1697
    // Ensure state and login are set
1698
    if (
1699
        (empty($saved_state) || empty($dataReceived['login']) || !isset($dataReceived['duo_state']) || empty($dataReceived['duo_state']))
1700
        && $duo_status === 'IN_PROGRESS'
1701
        && $dataReceived['duo_status'] !== 'start_duo_auth'
1702
    ) {
1703
        return [
1704
            'error' => true,
1705
            'message' => $lang->get('duo_no_data'),
1706
            'pwd_attempts' => (int) $sessionPwdAttempts,
1707
            'proceedIdentification' => false,
1708
        ];
1709
    }
1710
1711
    // Ensure state matches from initial request
1712
    if ($duo_status === 'IN_PROGRESS' && $dataReceived['duo_state'] !== $saved_state) {
1713
        $session->set('user-duo_state', '');
1714
        $session->set('user-duo_status', '');
1715
1716
        // We did not received a proper Duo state
1717
        return [
1718
            'error' => true,
1719
            'message' => $lang->get('duo_error_state'),
1720
            'pwd_attempts' => (int) $sessionPwdAttempts,
1721
            'proceedIdentification' => false,
1722
        ];
1723
    }
1724
1725
    return [
1726
        'error' => false,
1727
        'pwd_attempts' => (int) $sessionPwdAttempts,
1728
        'saved_state' => $saved_state,
1729
        'duo_status' => $duo_status,
1730
    ];
1731
}
1732
1733
1734
/**
1735
 * Create the redirect URL or check if the DUO Universal prompt was completed successfully.
1736
 *
1737
 * @param string                $username               Username
1738
 * @param string|array|resource $dataReceived           DataReceived
1739
 * @param array                 $sessionPwdAttempts     Nb of pwd attempts
1740
 * @param array                 $saved_state            Saved state
1741
 * @param array                 $duo_status             Duo status
1742
 * @param array                 $SETTINGS               Teampass settings
1743
 *
1744
 * @return array
1745
 */
1746
function duoMFAPerform(
1747
    string $username,
1748
    $dataReceived,
1749
    int $sessionPwdAttempts,
1750
    string $saved_state,
1751
    string $duo_status,
1752
    array $SETTINGS
1753
): array
1754
{
1755
    $session = SessionManager::getSession();
1756
    $lang = new Language(); 
1757
1758
    try {
1759
        $duo_client = new Client(
1760
            $SETTINGS['duo_ikey'],
1761
            $SETTINGS['duo_skey'],
1762
            $SETTINGS['duo_host'],
1763
            $SETTINGS['cpassman_url'].'/'.DUO_CALLBACK
1764
        );
1765
    } catch (DuoException $e) {
1766
        return [
1767
            'error' => true,
1768
            'message' => $lang->get('duo_config_error'),
1769
            'debug_message' => $e->getMessage(),
1770
            'pwd_attempts' => (int) $sessionPwdAttempts,
1771
            'proceedIdentification' => false,
1772
        ];
1773
    }
1774
        
1775
    try {
1776
        $duo_error = $lang->get('duo_error_secure');
1777
        $duo_failmode = "none";
1778
        $duo_client->healthCheck();
1779
    } catch (DuoException $e) {
1780
        //Not implemented Duo Failmode in case the Duo services are not available
1781
        /*if ($SETTINGS['duo_failmode'] == "safe") {
1782
            # If we're failing open, errors in 2FA still allow for success
1783
            $duo_error = $lang->get('duo_error_failopen');
1784
            $duo_failmode = "safe";
1785
        } else {
1786
            # Duo has failed and is unavailable, redirect user to the login page
1787
            $duo_error = $lang->get('duo_error_secure');
1788
            $duo_failmode = "secure";
1789
        }*/
1790
        return [
1791
            'error' => true,
1792
            'message' => $duo_error . $lang->get('duo_error_check_config'),
1793
            'pwd_attempts' => (int) $sessionPwdAttempts,
1794
            'debug_message' => $e->getMessage(),
1795
            'proceedIdentification' => false,
1796
        ];
1797
    }
1798
    
1799
    // Check if no one played with the javascript
1800
    if ($duo_status !== 'IN_PROGRESS' && $dataReceived['duo_status'] === 'start_duo_auth') {
1801
        # Create the Duo URL to send the user to
1802
        try {
1803
            $duo_state = $duo_client->generateState();
1804
            $duo_redirect_url = $duo_client->createAuthUrl($username, $duo_state);
1805
        } catch (DuoException $e) {
1806
            return [
1807
                'error' => true,
1808
                'message' => $duo_error . $lang->get('duo_error_url'),
1809
                'pwd_attempts' => (int) $sessionPwdAttempts,
1810
                'debug_message' => $e->getMessage(),
1811
                'proceedIdentification' => false,
1812
            ];
1813
        }
1814
        
1815
        // Somethimes Duo return success but fail to return a URL, double check if the URL has been created
1816
        if (!empty($duo_redirect_url) && isset($duo_redirect_url) && filter_var($duo_redirect_url,FILTER_SANITIZE_URL)) {
1817
            // Since Duo Universal requires a redirect, let's store some info when the user get's back after completing the Duo prompt
1818
            $key = hash('sha256', $duo_state);
1819
            $iv = substr(hash('sha256', $duo_state), 0, 16);
1820
            $duo_data = serialize([
1821
                'duo_login' => $username,
1822
                'duo_pwd' => $dataReceived['pw'],
1823
            ]);
1824
            $duo_data_enc = openssl_encrypt($duo_data, 'AES-256-CBC', $key, 0, $iv);
1825
            $session->set('user-duo_state', $duo_state);
1826
            $session->set('user-duo_data', base64_encode($duo_data_enc));
1827
            $session->set('user-duo_status', 'IN_PROGRESS');
1828
            $session->set('user-login', $username);
1829
            
1830
            // If we got here we can reset the password attempts
1831
            $session->set('pwd_attempts', 0);
1832
            
1833
            return [
1834
                'error' => false,
1835
                'message' => '',
1836
                'proceedIdentification' => false,
1837
                'duo_url_ready' => true,
1838
                'duo_redirect_url' => $duo_redirect_url,
1839
                'duo_failmode' => $duo_failmode,
1840
            ];
1841
        } else {
1842
            return [
1843
                'error' => true,
1844
                'message' => $duo_error . $lang->get('duo_error_url'),
1845
                'pwd_attempts' => (int) $sessionPwdAttempts,
1846
                'proceedIdentification' => false,
1847
            ];
1848
        }
1849
    } elseif ($duo_status === 'IN_PROGRESS' && $dataReceived['duo_code'] !== '') {
1850
        try {
1851
            // Check if the Duo code received is valid
1852
            $decoded_token = $duo_client->exchangeAuthorizationCodeFor2FAResult($dataReceived['duo_code'], $username);
1853
        } catch (DuoException $e) {
1854
            return [
1855
                'error' => true,
1856
                'message' => $lang->get('duo_error_decoding'),
1857
                'pwd_attempts' => (int) $sessionPwdAttempts,
1858
                'debug_message' => $e->getMessage(),
1859
                'proceedIdentification' => false,
1860
            ];
1861
        }
1862
        // return the response (which should be the user name)
1863
        if ($decoded_token['preferred_username'] === $username) {
1864
            $session->set('user-duo_status', 'COMPLET');
1865
            $session->set('user-duo_state','');
1866
            $session->set('user-duo_data','');
1867
            $session->set('user-login', $username);
1868
1869
            return [
1870
                'error' => false,
1871
                'message' => '',
1872
                'proceedIdentification' => true,
1873
                'authenticated_username' => $decoded_token['preferred_username']
1874
            ];
1875
        } else {
1876
            // Something wrong, username from the original Duo request is different than the one received now
1877
            $session->set('user-duo_status','');
1878
            $session->set('user-duo_state','');
1879
            $session->set('user-duo_data','');
1880
1881
            return [
1882
                'error' => true,
1883
                'message' => $lang->get('duo_login_mismatch'),
1884
                'pwd_attempts' => (int) $sessionPwdAttempts,
1885
                'proceedIdentification' => false,
1886
            ];
1887
        }
1888
    }
1889
    // If we are here something wrong
1890
    $session->set('user-duo_status','');
1891
    $session->set('user-duo_state','');
1892
    $session->set('user-duo_data','');
1893
    return [
1894
        'error' => true,
1895
        'message' => $lang->get('duo_login_mismatch'),
1896
        'pwd_attempts' => (int) $sessionPwdAttempts,
1897
        'proceedIdentification' => false,
1898
    ];
1899
}
1900
1901
/**
1902
 * Undocumented function.
1903
 *
1904
 * @param string                $passwordClear Password in clear
1905
 * @param array|string          $userInfo      Array of user data
1906
 * @param array|string|resource $dataReceived  Received data
1907
 * @param string                $username      User name
1908
 * @param array                 $SETTINGS      Teampass settings
1909
 *
1910
 * @return bool
1911
 */
1912
function checkCredentials($passwordClear, $userInfo, $dataReceived, $username, $SETTINGS)
1913
{
1914
    // Set to false
1915
    $userPasswordVerified = false;
1916
    // load passwordLib library
1917
    $pwdlib = new PasswordLib();
1918
    // Check if old encryption used
1919
    if (
1920
        crypt($passwordClear, $userInfo['pw']) === $userInfo['pw']
1921
        && empty($userInfo['pw']) === false
1922
    ) {
1923
        $userPasswordVerified = true;
1924
        //update user's password
1925
        $userInfo['pw'] = $pwdlib->createPasswordHash($passwordClear);
1926
        DB::update(
1927
            prefixTable('users'),
1928
            [
1929
                'pw' => $userInfo['pw'],
1930
            ],
1931
            'id=%i',
1932
            $userInfo['id']
1933
        );
1934
    }
1935
    //echo $passwordClear." - ".$userInfo['pw']." - ".$pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw'])." ;; ";
1936
    // check the given password
1937
    if ($userPasswordVerified !== true) {
1938
        if ($pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw']) === true) {
1939
            $userPasswordVerified = true;
1940
        } else {
1941
            // 2.1.27.24 - manage passwords
1942
            if ($pwdlib->verifyPasswordHash(htmlspecialchars_decode($dataReceived['pw']), $userInfo['pw']) === true) {
1943
                // then the auth is correct but needs to be adapted in DB since change of encoding
1944
                $userInfo['pw'] = $pwdlib->createPasswordHash($passwordClear);
1945
                DB::update(
1946
                    prefixTable('users'),
1947
                    [
1948
                        'pw' => $userInfo['pw'],
1949
                    ],
1950
                    'id=%i',
1951
                    $userInfo['id']
1952
                );
1953
                $userPasswordVerified = true;
1954
            } else {
1955
                $userPasswordVerified = false;
1956
                logEvents(
1957
                    $SETTINGS,
1958
                    'failed_auth',
1959
                    'password_is_not_correct',
1960
                    '',
1961
                    '',
1962
                    stripslashes($username)
1963
                );
1964
            }
1965
        }
1966
    }
1967
1968
    return $userPasswordVerified;
1969
}
1970
1971
/**
1972
 * Undocumented function.
1973
 *
1974
 * @param bool   $enabled text1
1975
 * @param string $dbgFile text2
1976
 * @param string $text    text3
1977
 */
1978
function debugIdentify(bool $enabled, string $dbgFile, string $text): void
1979
{
1980
    if ($enabled === true) {
1981
        $fp = fopen($dbgFile, 'a');
1982
        if ($fp !== false) {
1983
            fwrite(
1984
                $fp,
1985
                $text
1986
            );
1987
        }
1988
    }
1989
}
1990
1991
1992
1993
function identifyGetUserCredentials(
1994
    array $SETTINGS,
1995
    string $serverPHPAuthUser,
1996
    string $serverPHPAuthPw,
1997
    string $userPassword,
1998
    string $userLogin
1999
): array
2000
{
2001
    if ((int) $SETTINGS['enable_http_request_login'] === 1
2002
        && $serverPHPAuthUser !== null
2003
        && (int) $SETTINGS['maintenance_mode'] === 1
2004
    ) {
2005
        if (strpos($serverPHPAuthUser, '@') !== false) {
2006
            return [
2007
                'username' => explode('@', $serverPHPAuthUser)[0],
2008
                'passwordClear' => $serverPHPAuthPw
2009
            ];
2010
        }
2011
        
2012
        if (strpos($serverPHPAuthUser, '\\') !== false) {
2013
            return [
2014
                'username' => explode('\\', $serverPHPAuthUser)[1],
2015
                'passwordClear' => $serverPHPAuthPw
2016
            ];
2017
        }
2018
2019
        return [
2020
            'username' => $serverPHPAuthPw,
2021
            'passwordClear' => $serverPHPAuthPw
2022
        ];
2023
    }
2024
    
2025
    return [
2026
        'username' => $userLogin,
2027
        'passwordClear' => $userPassword
2028
    ];
2029
}
2030
2031
2032
class initialChecks {
2033
    // Properties
2034
    public $login;
2035
2036
    // Methods
2037
    public function get_is_too_much_attempts($attempts) {
2038
        if ($attempts > 30) {
2039
            throw new Exception(
2040
                "error" 
2041
            );
2042
        }
2043
    }
2044
2045
    public function get_user_info($login, $enable_ad_user_auto_creation) {
2046
        $data = DB::queryFirstRow(
2047
            'SELECT u.*, a.value AS api_key
2048
            FROM ' . prefixTable('users') . ' AS u
2049
            LEFT JOIN ' . prefixTable('api') . ' AS a ON (u.id = a.user_id)
2050
            WHERE login = %s AND deleted_at IS NULL',
2051
            $login
2052
        );
2053
        
2054
        // User doesn't exist then return error
2055
        // Except if user creation from LDAP is enabled
2056
        if (DB::count() === 0 && $enable_ad_user_auto_creation === false) {
2057
            throw new Exception(
2058
                "error" 
2059
            );
2060
        }
2061
        $data['ldap_user_to_be_created'] = $enable_ad_user_auto_creation === true && DB::count() === 0 ? true : false;
2062
2063
        // ensure user fonction_id is set to false if not existing
2064
        /*if (is_null($data['fonction_id']) === true) {
2065
            $data['fonction_id'] = '';
2066
        }*/
2067
2068
        // Prepare user roles (fonction_id + roles_from_ad_groups)
2069
        // Disable this as this happend repeadetly and is not necessary when working with AD groups
2070
        //$data['fonction_id'] = is_null($data['roles_from_ad_groups']) === true ? $data['fonction_id'] : (empty($data['roles_from_ad_groups']) === true ? $data['fonction_id'] : $data['fonction_id'] . ';' . $data['roles_from_ad_groups']);
2071
2072
        return $data;
2073
    }
2074
2075
    public function get_teampass_in_maintenance_mode($maintenance_mode, $user_admin) {
2076
        if ((int) $maintenance_mode === 1 && (int) $user_admin === 0) {
2077
            throw new Exception(
2078
                "error" 
2079
            );
2080
        }
2081
    }
2082
2083
    public function get_mfa_code_is_set(
2084
        $yubico,
2085
        $ga,
2086
        $duo,
2087
        $admin,
2088
        $adminMfaRequired,
2089
        $mfa,
2090
        $userMfaSelection,
2091
        $userMfaEnabled
2092
    ) {
2093
        if (
2094
            (empty($userMfaSelection) === true &&
2095
            isOneVarOfArrayEqualToValue(
2096
                [
2097
                    (int) $yubico,
2098
                    (int) $ga,
2099
                    (int) $duo
2100
                ],
2101
                1
2102
            ) === true)
2103
            && (((int) $admin !== 1 && $userMfaEnabled === true) || ((int) $adminMfaRequired === 1 && (int) $admin === 1))
2104
            && $mfa === true
2105
        ) {
2106
            throw new Exception(
2107
                "error" 
2108
            );
2109
        }
2110
    }
2111
2112
    public function get_install_folder_is_not_present($admin, $install_folder) {
2113
        if ((int) $admin === 1 && is_dir($install_folder) === true) {
2114
            throw new Exception(
2115
                "error" 
2116
            );
2117
        }
2118
    }
2119
}
2120
2121
2122
/**
2123
 * Permit to get info about user before auth step
2124
 *
2125
 * @param array $SETTINGS
2126
 * @param integer $sessionPwdAttempts
2127
 * @param string $username
2128
 * @param integer $sessionAdmin
2129
 * @param string $sessionUrl
2130
 * @param string $user_2fa_selection
2131
 * @return array
2132
 */
2133
function identifyDoInitialChecks(
2134
    $SETTINGS,
2135
    int $sessionPwdAttempts,
2136
    string $username,
2137
    int $sessionAdmin,
2138
    string $sessionUrl,
2139
    string $user_2fa_selection
2140
): array
2141
{
2142
    $session = SessionManager::getSession();
2143
    $checks = new initialChecks();
2144
    $enable_ad_user_auto_creation = isset($SETTINGS['enable_ad_user_auto_creation']) === true && (int) $SETTINGS['enable_ad_user_auto_creation'] === 1 ? true : false;
2145
    $lang = new Language(); 
2146
    
2147
    // Brute force management
2148
    try {
2149
        $checks->get_is_too_much_attempts($sessionPwdAttempts);
2150
    } catch (Exception $e) {
2151
        $session->set('next_possible_pwd_attempts', (time() + 10));
2152
        $session->set('pwd_attempts', 0);
2153
2154
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($username), stripslashes($username));
2155
2156
        return [
2157
            'error' => true,
2158
            'array' => [
2159
                'value' => 'bruteforce_wait',
2160
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2161
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2162
                'pwd_attempts' => 0,
2163
                'error' => true,
2164
                'message' => $lang->get('error_bad_credentials_more_than_3_times'),
2165
            ]
2166
        ];
2167
    }
2168
2169
    // Check if user exists
2170
    try {
2171
        $userInfo = $checks->get_user_info($username, $enable_ad_user_auto_creation);
2172
    } catch (Exception $e) {
2173
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($username), stripslashes($username));
2174
        return [
2175
            'error' => true,
2176
            'array' => [
2177
                'value' => 'user_not_exists',
2178
                'error' => true,
2179
                'message' => $lang->get('error_bad_credentials'),
2180
                'pwd_attempts' => (int) $sessionPwdAttempts,
2181
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2182
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2183
            ]
2184
        ];
2185
    }
2186
    
2187
    // Manage Maintenance mode
2188
    try {
2189
        $checks->get_teampass_in_maintenance_mode(
2190
            $SETTINGS['maintenance_mode'],
2191
            $userInfo['admin']
2192
        );
2193
    } catch (Exception $e) {
2194
        return [
2195
            'error' => true,
2196
            'array' => [
2197
                'value' => '',
2198
                'user_admin' => (int) $userInfo['admin'],
2199
                'initial_url' => '',
2200
                'pwd_attempts' => '',
2201
                'error' => 'maintenance_mode_enabled',
2202
                'message' => '',
2203
            ]
2204
        ];
2205
    }
2206
2207
    // user should use MFA?
2208
    $userInfo['mfa_auth_requested_roles'] = mfa_auth_requested_roles(
2209
        (string) $userInfo['fonction_id'],
2210
        is_null($SETTINGS['mfa_for_roles']) === true ? '' : (string) $SETTINGS['mfa_for_roles']
2211
    );
2212
2213
    // Check if 2FA code is requested
2214
    try {
2215
        $checks->get_mfa_code_is_set(
2216
            $SETTINGS['yubico_authentication'],
2217
            $SETTINGS['google_authentication'],
2218
            $SETTINGS['duo'],
2219
            $userInfo['admin'],
2220
            $SETTINGS['admin_2fa_required'],
2221
            $userInfo['mfa_auth_requested_roles'],
2222
            $user_2fa_selection,
2223
            $userInfo['mfa_enabled']
2224
        );
2225
    } catch (Exception $e) {
2226
        return [
2227
            'error' => true,
2228
            'array' => [
2229
                'value' => '2fa_not_set',
2230
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2231
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2232
                'pwd_attempts' => (int) $sessionPwdAttempts,
2233
                'error' => '2fa_not_set',
2234
                'message' => $lang->get('select_valid_2fa_credentials'),
2235
            ]
2236
        ];
2237
    }
2238
2239
    // If admin user then check if folder install exists
2240
    // if yes then refuse connection
2241
    try {
2242
        $checks->get_install_folder_is_not_present(
2243
            $userInfo['admin'],
2244
            '../install'
2245
        );
2246
    } catch (Exception $e) {
2247
        return [
2248
            'error' => true,
2249
            'array' => [
2250
                'value' => '',
2251
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2252
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2253
                'pwd_attempts' => (int) $sessionPwdAttempts,
2254
                'error' => true,
2255
                'message' => $lang->get('remove_install_folder'),
2256
            ]
2257
        ];
2258
    }
2259
2260
    // Return some usefull information about user
2261
    return [
2262
        'error' => false,
2263
        'user_mfa_mode' => $user_2fa_selection,
2264
        'userInfo' => $userInfo,
2265
    ];
2266
}
2267
2268
function identifyDoLDAPChecks(
2269
    $SETTINGS,
2270
    $userInfo,
2271
    string $username,
2272
    string $passwordClear,
2273
    int $sessionAdmin,
2274
    string $sessionUrl,
2275
    int $sessionPwdAttempts
2276
): array
2277
{
2278
    // Prepare LDAP connection if set up
2279
    if ((int) $SETTINGS['ldap_mode'] === 1
2280
        && $username !== 'admin'
2281
        && ((string) $userInfo['auth_type'] === 'ldap' || $userInfo['ldap_user_to_be_created'] === true)
2282
    ) {
2283
        $retLDAP = authenticateThroughAD(
2284
            $username,
2285
            $userInfo,
2286
            $passwordClear,
2287
            $SETTINGS
2288
        );
2289
        if ($retLDAP['error'] === true) {
2290
            return [
2291
                'error' => true,
2292
                'array' => [
2293
                    'value' => '',
2294
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2295
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2296
                    'pwd_attempts' => (int) $sessionPwdAttempts,
2297
                    'error' => true,
2298
                    'message' => "LDAP error: ".$retLDAP['message'],
2299
                ]
2300
            ];
2301
        }
2302
        return [
2303
            'error' => false,
2304
            'retLDAP' => $retLDAP,
2305
            'ldapConnection' => true,
2306
            'userPasswordVerified' => true,
2307
        ];
2308
    }
2309
2310
    // return if no addmin
2311
    return [
2312
        'error' => false,
2313
        'retLDAP' => [],
2314
        'ldapConnection' => false,
2315
        'userPasswordVerified' => false,
2316
    ];
2317
}
2318
2319
2320
function identifyDoMFAChecks(
2321
    $SETTINGS,
2322
    $userInfo,
2323
    $dataReceived,
2324
    $userInitialData,
2325
    string $username
2326
): array
2327
{    
2328
    $session = SessionManager::getSession();
2329
    $lang = new Language(); 
2330
    
2331
    switch ($userInitialData['user_mfa_mode']) {
2332
        case 'google':
2333
            $ret = googleMFACheck(
2334
                $username,
2335
                $userInfo,
2336
                $dataReceived,
2337
                $SETTINGS
2338
            );
2339
            if ($ret['error'] !== false) {
2340
                logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2341
                return [
2342
                    'error' => true,
2343
                    'mfaData' => $ret,
2344
                    'mfaQRCodeInfos' => false,
2345
                ];
2346
            }
2347
2348
            return [
2349
                'error' => false,
2350
                'mfaData' => $ret['firstTime'],
2351
                'mfaQRCodeInfos' => $userInitialData['user_mfa_mode'] === 'google'
2352
                && count($ret['firstTime']) > 0 ? true : false,
2353
            ];
2354
2355
        case 'yubico':
2356
            $ret = yubicoMFACheck(
2357
                $dataReceived,
2358
                $userInfo,
2359
                $SETTINGS
2360
            );
2361
            if ($ret['error'] !== false) {
2362
                return [
2363
                    'error' => true,
2364
                    'mfaData' => $ret,
2365
                    'mfaQRCodeInfos' => false,
2366
                ];
2367
            }
2368
            break;
2369
        
2370
        case 'duo':
2371
            // Prepare Duo connection if set up
2372
            $checks = duoMFACheck(
2373
                $username,
2374
                $dataReceived,
2375
                $SETTINGS
2376
            );
2377
2378
            if ($checks['error'] === true) {
2379
                return [
2380
                    'error' => true,
2381
                    'mfaData' => $checks,
2382
                    'mfaQRCodeInfos' => false,
2383
                ];
2384
            }
2385
2386
            // If we are here
2387
            // Do DUO authentication
2388
            $ret = duoMFAPerform(
2389
                $username,
2390
                $dataReceived,
2391
                $checks['pwd_attempts'],
2392
                $checks['saved_state'],
2393
                $checks['duo_status'],
2394
                $SETTINGS
2395
            );
2396
2397
            if ($ret['error'] !== false) {
2398
                logEvents($SETTINGS, 'failed_auth', 'bad_duo_mfa', '', stripslashes($username), stripslashes($username));
2399
                $session->set('user-duo_status','');
2400
                $session->set('user-duo_state','');
2401
                $session->set('user-duo_data','');
2402
                return [
2403
                    'error' => true,
2404
                    'mfaData' => $ret,
2405
                    'mfaQRCodeInfos' => false,
2406
                ];
2407
            } else if ($ret['duo_url_ready'] === true){
2408
                return [
2409
                    'error' => false,
2410
                    'mfaData' => $ret,
2411
                    'duo_url_ready' => true,
2412
                    'mfaQRCodeInfos' => false,
2413
                ];
2414
            } else if ($ret['error'] === false) {
2415
                return [
2416
                    'error' => false,
2417
                    'mfaData' => $ret,
2418
                    'mfaQRCodeInfos' => false,
2419
                ];
2420
            }
2421
            break;
2422
        
2423
        default:
2424
            logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2425
            return [
2426
                'error' => true,
2427
                'mfaData' => ['message' => $lang->get('wrong_mfa_code')],
2428
                'mfaQRCodeInfos' => false,
2429
            ];
2430
    }
2431
2432
    // If something went wrong, let's catch and return an error
2433
    logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2434
    return [
2435
        'error' => true,
2436
        'mfaData' => ['message' => $lang->get('wrong_mfa_code')],
2437
        'mfaQRCodeInfos' => false,
2438
    ];
2439
}
2440