Passed
Branch wip_sessions (2e0cc8)
by Nils
04:59
created

initialChecks::get_mfa_code_is_set()   B

Complexity

Conditions 8
Paths 2

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 11
nc 2
nop 8
dl 0
loc 25
rs 8.4444
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 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...
30
use TeampassClasses\SessionManager\SessionManager;
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
$session = SessionManager::getSession();
50
// TODO : ajouter un check sue l'envoi de la key
51
52
loadClasses('DB');
53
$lang = new Language(); 
54
55
56
// Load config if $SETTINGS not defined
57
try {
58
    include_once __DIR__.'/../includes/config/tp.config.php';
59
} catch (Exception $e) {
60
    throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
61
}
62
63
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true || $SETTINGS['cpassman_dir'] === '.') {
64
    $SETTINGS = [];
65
    $SETTINGS['cpassman_dir'] = '..';
66
}
67
error_log('Identify.php: '.print_r($_POST, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($_POST, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

67
error_log('Identify.php: './** @scrutinizer ignore-type */ print_r($_POST, true));
Loading history...
68
// Do checks
69
// Instantiate the class with posted data
70
$checkUserAccess = new PerformChecks(
71
    dataSanitizer(
72
        [
73
            'type' => isset($_POST['type']) === true ? htmlspecialchars($_POST['type']) : '',
74
        ],
75
        [
76
            'type' => 'trim|escape',
77
        ],
78
    ),
79
    [
80
        'user_id' => returnIfSet($session->get('user-id'), null),
81
        'user_key' => returnIfSet($session->get('key'), null),
82
        'login' => isset($_POST['login']) === false ? null : $_POST['login'],
83
    ]
84
);
85
86
// Handle the case
87
echo $checkUserAccess->caseHandler();
88
if ($checkUserAccess->checkSession() === false) {
89
    error_log('Identify.php L89 - REFUS - '.$checkUserAccess->checkSession());
0 ignored issues
show
Bug introduced by
Are you sure $checkUserAccess->checkSession() of type false can be used in concatenation? ( Ignorable by Annotation )

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

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