Passed
Push — master ( 61bd0d...285557 )
by Nils
04:23
created

handleUserADGroups()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 42
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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