Passed
Push — master ( 97a95f...b0e088 )
by Nils
10:41
created

authenticateThroughAD()   C

Complexity

Conditions 15
Paths 48

Size

Total Lines 127
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 15
eloc 87
nc 48
nop 4
dl 0
loc 127
rs 5.0169
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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