Passed
Branch wip_sessions (830972)
by Nils
07:55
created

authenticateThroughAD()   D

Complexity

Conditions 29
Paths 62

Size

Total Lines 154
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 96
nc 62
nop 4
dl 0
loc 154
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 * @file      identify.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use voku\helper\AntiXSS;
28
use EZimuel\PHPSecureSession;
29
use TeampassClasses\SuperGlobal\SuperGlobal;
30
use TeampassClasses\SessionManager\SessionManager;
31
use TeampassClasses\Language\Language;
32
use TeampassClasses\PerformChecks\PerformChecks;
33
use LdapRecord\Connection;
34
use LdapRecord\Container;
35
use LdapRecord\Auth\Events\Failed;
36
use TeampassClasses\NestedTree\NestedTree;
37
use PasswordLib\PasswordLib;
38
use Duo\DuoUniversal\Client;
39
use Duo\DuoUniversal\DuoException;
40
use RobThree\Auth\TwoFactorAuth;
41
use TeampassClasses\LdapExtra\LdapExtra;
42
use TeampassClasses\LdapExtra\OpenLdapExtra;
43
use TeampassClasses\LdapExtra\ActiveDirectoryExtra;
44
45
// Load functions
46
require_once 'main.functions.php';
47
48
// Resume the session
49
if (isset($_POST['sessionId'])) {
50
    $sessionId = $_POST['sessionId'];
51
    // Validate the session ID format to prevent injection attacks
52
    if (preg_match('/^[a-z0-9]{26,40}$/', $sessionId)) {
53
        session_id($sessionId);
54
    } else {
55
        // Invalid session ID, handle the error
56
        echo "Invalid session ID";
57
        exit;
58
    }
59
}
60
61
// init
62
$session = SessionManager::getSession();
63
64
loadClasses('DB');
65
$superGlobal = new SuperGlobal();
66
$lang = new Language(); 
67
68
69
// Load config if $SETTINGS not defined
70
try {
71
    include_once __DIR__.'/../includes/config/tp.config.php';
72
} catch (Exception $e) {
73
    throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
74
}
75
76
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true || $SETTINGS['cpassman_dir'] === '.') {
77
    $SETTINGS = [];
78
    $SETTINGS['cpassman_dir'] = '..';
79
}
80
81
// Do checks
82
// Instantiate the class with posted data
83
$checkUserAccess = new PerformChecks(
84
    dataSanitizer(
85
        [
86
            'type' => isset($_POST['type']) === true ? htmlspecialchars($_POST['type']) : '',
87
        ],
88
        [
89
            'type' => 'trim|escape',
90
        ],
91
    ),
92
    [
93
        'user_id' => returnIfSet($session->get('user-id'), null),
94
        'user_key' => returnIfSet($session->get('key'), null),
95
        'login' => isset($_POST['login']) === false ? null : $_POST['login'],
96
    ]
97
);
98
99
// Handle the case
100
echo $checkUserAccess->caseHandler();
101
if ($checkUserAccess->checkSession() === false) {
102
    // Not allowed page
103
    $session->set('system-error_code', ERR_NOT_ALLOWED);
104
    include $SETTINGS['cpassman_dir'] . '/error.php';
105
    exit;
106
}
107
108
// Define Timezone
109
date_default_timezone_set(isset($SETTINGS['timezone']) === true ? $SETTINGS['timezone'] : 'UTC');
110
111
// Set header properties
112
header('Content-type: text/html; charset=utf-8');
113
header('Cache-Control: no-cache, no-store, must-revalidate');
114
error_reporting(E_ERROR);
115
116
// --------------------------------- //
117
118
// Prepare POST variables
119
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
120
$post_login = filter_input(INPUT_POST, 'login', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
121
$post_data = filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
122
123
124
if ($post_type === 'identify_user') {
125
    //--------
126
    // NORMAL IDENTICATION STEP
127
    //--------
128
129
    // Ensure Complexity levels are translated
130
    defineComplexity();
131
132
    /**
133
     * Permits to handle login attempts
134
     *
135
     * @param string $post_data
136
     * @param array $SETTINGS
137
     * @return bool|string
138
     */
139
    function handleAuthAttempts($post_data, $SETTINGS): bool|string
140
    {
141
        $session = SessionManager::getSession();
142
        $lang = new Language();
143
        $sessionPwdAttempts = $session->get('pwd_attempts');
144
        $nextPossibleAttempts = (int) $session->get('next_possible_pwd_attempts');
145
146
        // Check if the user is currently within the waiting period
147
        if ($nextPossibleAttempts > 0 && time() < $nextPossibleAttempts) {
148
            // Brute force wait
149
            $remainingSeconds = $nextPossibleAttempts - time();
150
            $errorResponse = prepareExchangedData([
151
                'value' => 'bruteforce_wait',
152
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionAdmin does not exist. Did you maybe mean $session?
Loading history...
153
                'initial_url' => isset($sessionUrl) ? $sessionUrl : '',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionUrl does not exist. Did you maybe mean $session?
Loading history...
154
                'pwd_attempts' => 0,
155
                'error' => true,
156
                'message' => $lang->get('error_bad_credentials_more_than_3_times'),
157
                'remaining_seconds' => $remainingSeconds,
158
            ], 'encode');
159
160
            echo $errorResponse;
161
            return false;
162
        }
163
164
        // Increment the counter of login attempts
165
        $sessionPwdAttempts = ($sessionPwdAttempts === '') ? 1 : ++$sessionPwdAttempts;
166
        $session->set('pwd_attempts', $sessionPwdAttempts);
167
168
        // Check for brute force attempts
169
        if ($sessionPwdAttempts <= 3) {
170
            // Identify the user through Teampass process
171
            identifyUser($post_data, $SETTINGS);
172
        } else {
173
            // Reset attempts and set waiting period on the fourth consecutive attempt
174
            $session->set('pwd_attempts', 0);
175
176
            if ($sessionPwdAttempts === 4) {
177
                // On the fourth consecutive attempt, trigger the waiting period
178
                $nextPossibleAttempts = time() + 10;
179
                $session->set('next_possible_pwd_attempts', $nextPossibleAttempts);
180
181
                // Send an error response indicating the waiting period
182
                $errorResponse = prepareExchangedData([
183
                    'value' => 'bruteforce_wait',
184
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
185
                    'initial_url' => isset($sessionUrl) ? $sessionUrl : '',
186
                    'pwd_attempts' => 0,
187
                    'error' => true,
188
                    'message' => $lang->get('error_bad_credentials_more_than_3_times'),
189
                    'remaining_seconds' => 10,
190
                ], 'encode');
191
192
                echo $errorResponse;
193
                return false;
194
            }
195
196
            // Identify the user through Teampass process
197
            identifyUser($post_data, $SETTINGS);
198
        }
199
200
        return true;
201
    }
202
203
    handleAuthAttempts($post_data, $SETTINGS);
204
205
    // ---
206
    // ---
207
    // ---
208
} elseif ($post_type === 'get2FAMethods') {
209
    //--------
210
    // Get MFA methods
211
    //--------
212
    //
213
214
    // Encrypt data to return
215
    echo json_encode([
216
        'ret' => prepareExchangedData(
217
            [
218
                'agses' => isKeyExistingAndEqual('agses_authentication_enabled', 1, $SETTINGS) === true ? true : false,
219
                'google' => isKeyExistingAndEqual('google_authentication', 1, $SETTINGS) === true ? true : false,
220
                'yubico' => isKeyExistingAndEqual('yubico_authentication', 1, $SETTINGS) === true ? true : false,
221
                'duo' => isKeyExistingAndEqual('duo', 1, $SETTINGS) === true ? true : false,
222
            ],
223
            'encode'
224
        ),
225
        'key' => $session->get('key'),
226
    ]);
227
    return false;
228
}
229
230
/**
231
 * Complete authentication of user through Teampass
232
 *
233
 * @param string $sentData Credentials
234
 * @param array $SETTINGS Teampass settings
235
 *
236
 * @return bool
237
 */
238
function identifyUser(string $sentData, array $SETTINGS): bool
239
{
240
    $antiXss = new AntiXSS();
241
    $superGlobal = new SuperGlobal();
242
    $lang = new Language();
243
    $session = SessionManager::getSession();
244
245
    // Prepare GET variables
246
    $sessionAdmin = $session->get('user-admin');
247
    $sessionPwdAttempts = $session->get('pwd_attempts');
248
    $sessionUrl = $session->get('user-initial_url');
249
    $server = [];
250
    $server['PHP_AUTH_USER'] = $superGlobal->get('PHP_AUTH_USER', 'SERVER');
251
    $server['PHP_AUTH_PW'] = $superGlobal->get('PHP_AUTH_PW', 'SERVER');
252
    
253
    // decrypt and retreive data in JSON format
254
    if ($session->get('key') === null) {
255
        $dataReceived = $sentData;
256
    } else {
257
        $dataReceived = prepareExchangedData(
258
            $sentData,
259
            'decode',
260
            $session->get('key')
261
        );
262
        //$session->set('key', $session->get('key'));
263
    }
264
265
    // Check if Duo auth is in progress and pass the pw and login back to the standard login process
266
    if(
267
        isKeyExistingAndEqual('duo', 1, $SETTINGS) === true
268
        && $dataReceived['user_2fa_selection'] === 'duo'
269
        && $session->get('user-duo_status') === 'IN_PROGRESS'
270
        && !empty($dataReceived['duo_state'])
271
    ){
272
        $key = hash('sha256', $dataReceived['duo_state']);
273
        $iv = substr(hash('sha256', $dataReceived['duo_state']), 0, 16);
274
        $duo_data_dec = openssl_decrypt(base64_decode($session->get('user-duo_status')), 'AES-256-CBC', $key, 0, $iv);
275
        // Clear the data from the Duo process to continue clean with the standard login process
276
        $session->set('user-duo_data','');
277
        if($duo_data_dec === false){
278
            echo prepareExchangedData(
279
                [
280
                    'error' => true,
281
                    'message' => $lang->get('duo_error_decrypt'),
282
                ],
283
                'encode'
284
            );
285
            return false;
286
        }
287
        $duo_data = unserialize($duo_data_dec);
288
        $dataReceived['pw'] = $duo_data['duo_pwd'];
289
        $dataReceived['login'] = $duo_data['duo_login'];
290
    }
291
292
    if(isset($dataReceived['pw']) === false || isset($dataReceived['login']) === false) {
293
        echo json_encode([
294
            'data' => prepareExchangedData(
295
                [
296
                    'error' => true,
297
                    'message' => $lang->get('ga_enter_credentials'),
298
                ],
299
                'encode'
300
            ),
301
            'key' => $session->get('key')
302
        ]);
303
        return false;
304
    }
305
306
    // prepare variables    
307
    $userCredentials = identifyGetUserCredentials(
308
        $SETTINGS,
309
        (string) $server['PHP_AUTH_USER'],
310
        (string) $server['PHP_AUTH_PW'],
311
        (string) filter_var($dataReceived['pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
312
        (string) filter_var($dataReceived['login'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
313
    );
314
    $username = $userCredentials['username'];
315
    $passwordClear = $userCredentials['passwordClear'];
316
    
317
    // DO initial checks
318
    $userInitialData = identifyDoInitialChecks(
319
        $SETTINGS,
320
        (int) $sessionPwdAttempts,
321
        (string) $username,
322
        (int) $sessionAdmin,
323
        (string) $sessionUrl,
324
        (string) filter_var($dataReceived['user_2fa_selection'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
325
    );
326
    // if user doesn't exist in Teampass then return error
327
    if ($userInitialData['error'] === true) {
328
        echo prepareExchangedData(
329
            $userInitialData['array'],
330
            'encode'
331
        );
332
        return false;
333
    }
334
335
    $userInfo = $userInitialData['userInfo'];
336
    $return = '';
337
    $userLdap = identifyDoLDAPChecks(
338
        $SETTINGS,
339
        $userInfo,
340
        (string) $username,
341
        (string) $passwordClear,
342
        (int) $sessionAdmin,
343
        (string) $sessionUrl,
344
        (int) $sessionPwdAttempts
345
    );
346
    if ($userLdap['error'] === true) {
0 ignored issues
show
introduced by
The condition $userLdap['error'] === true is always false.
Loading history...
347
        echo prepareExchangedData(
348
            $userLdap['array'],
349
            'encode'
350
        );
351
        return false;
352
    }
353
    if (isset($userLdap['user_info']) === true && (int) $userLdap['user_info']['has_been_created'] === 1) {
354
        /*$userInfo = DB::queryfirstrow(
355
            'SELECT *
356
            FROM ' . prefixTable('users') . '
357
            WHERE login = %s',
358
            $username
359
        );*/
360
        //$userInfo = $userLdap['user_info'];
361
        echo json_encode([
362
            'data' => prepareExchangedData(
363
                [
364
                    'error' => true,
365
                    'message' => '',
366
                    'extra' => 'ad_user_created',
367
                ],
368
                'encode'
369
            ),
370
            'key' => $session->get('key')
371
        ]);
372
        return false;
373
    }
374
375
    // Check user and password
376
    if ($userLdap['userPasswordVerified'] === false && (int) checkCredentials($passwordClear, $userInfo, $dataReceived, $username, $SETTINGS) !== 1) {
377
        echo prepareExchangedData(
378
            [
379
                'value' => '',
380
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
381
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
382
                'pwd_attempts' => (int) $sessionPwdAttempts,
383
                'error' => true,
384
                'message' => $lang->get('error_bad_credentials'),
385
            ],
386
            'encode'
387
        );
388
        return false;
389
    }
390
391
    // Check if MFA is required
392
    if ((isOneVarOfArrayEqualToValue(
393
                [
394
                    (int) $SETTINGS['yubico_authentication'],
395
                    (int) $SETTINGS['google_authentication'],
396
                    (int) $SETTINGS['duo']
397
                ],
398
                1
399
            ) === true)
400
        && (((int) $userInfo['admin'] !== 1 && (int) $userInfo['mfa_enabled'] === 1) || ((int) $SETTINGS['admin_2fa_required'] === 1 && (int) $userInfo['admin'] === 1))
401
        && $userInfo['mfa_auth_requested_roles'] === true
402
    ) {
403
        // Check user against MFA method if selected
404
        $userMfa = identifyDoMFAChecks(
405
            $SETTINGS,
406
            $userInfo,
407
            $dataReceived,
408
            $userInitialData,
409
            (string) $username
410
        );
411
        if ($userMfa['error'] === true) {
412
            echo prepareExchangedData(
413
                [
414
                    'error' => true,
415
                    'message' => $userMfa['mfaData']['message'],
416
                    'mfaStatus' => $userMfa['mfaData']['mfaStatus'],
417
                ],
418
                'encode'
419
            );
420
            return false;
421
        } elseif ($userMfa['mfaQRCodeInfos'] === true) {
422
            // Case where user has initiated Google Auth
423
            // Return QR code
424
            echo prepareExchangedData(
425
                [
426
                    'value' => $userMfa['mfaData']['value'],
427
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
428
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
429
                    'pwd_attempts' => (int) $sessionPwdAttempts,
430
                    'error' => false,
431
                    'message' => $userMfa['mfaData']['message'],
432
                    'mfaStatus' => $userMfa['mfaData']['mfaStatus'],
433
                ],
434
                'encode'
435
            );
436
            return false;
437
        } elseif ($userMfa['duo_url_ready'] === true) {
438
            // Case where user has initiated Duo Auth
439
            // Return the DUO redirect URL
440
            echo prepareExchangedData(
441
                [
442
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
443
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
444
                    'pwd_attempts' => (int) $sessionPwdAttempts,
445
                    'error' => false,
446
                    'message' => $userMfa['mfaData']['message'],
447
                    'duo_url_ready' => $userMfa['mfaData']['duo_url_ready'],
448
                    'duo_redirect_url' => $userMfa['mfaData']['duo_redirect_url'],
449
                    'mfaStatus' => $userMfa['mfaData']['mfaStatus'],
450
                ],
451
                'encode'
452
            );
453
            return false;
454
        }
455
    }
456
    
457
    // Can connect if
458
    // 1- no LDAP mode + user enabled + pw ok
459
    // 2- LDAP mode + user enabled + ldap connection ok + user is not admin
460
    // 3- LDAP mode + user enabled + pw ok + usre is admin
461
    // This in order to allow admin by default to connect even if LDAP is activated
462
    if (canUserGetLog(
463
            $SETTINGS,
464
            (int) $userInfo['disabled'],
465
            $username,
466
            $userLdap['ldapConnection']
467
        ) === true
468
    ) {
469
        //$superGlobal->put('autoriser', true, 'SESSION');
470
        $session->set('pwd_attempts', 0);
471
472
        // Check if any unsuccessfull login tries exist
473
        $attemptsInfos = handleLoginAttempts(
474
            $userInfo['id'],
475
            $userInfo['login'],
476
            $userInfo['last_connexion'],
477
            $username,
478
            $SETTINGS,
479
        );
480
            
481
        // Save account in SESSION
482
        $session->set('user-unsuccessfull_login_attempts_list', $attemptsInfos['attemptsList'] === 0 ? true : false);
483
        $session->set('user-unsuccessfull_login_attempts_shown', $attemptsInfos['attemptsCount'] === 0 ? true : false);
484
        $session->set('user-unsuccessfull_login_attempts_nb', DB::count());
485
        $session->set('user-login', stripslashes($username));
486
        $session->set('user-name', empty($userInfo['name']) === false ? stripslashes($userInfo['name']) : '');
487
        $session->set('user-lastname', empty($userInfo['lastname']) === false ? stripslashes($userInfo['lastname']) : '');
488
        $session->set('user-id', (int) $userInfo['id']);
489
        $session->set('user-password', $passwordClear);
490
        $session->set('user-admin', (int) $userInfo['admin']);
491
        $session->set('user-manager', (int) $userInfo['gestionnaire']);
492
        $session->set('user-can_manage_all_users', $userInfo['can_manage_all_users']);
493
        $session->set('user-read_only', $userInfo['read_only']);
494
        $session->set('user-last_pw_change', $userInfo['last_pw_change']);
495
        $session->set('user-last_pw', $userInfo['last_pw']);
496
        $session->set('user-force_relog', $userInfo['force-relog']);
497
        $session->set('user-can_create_root_folder', $userInfo['can_create_root_folder']);
498
        $session->set('user-email', $userInfo['email']);
499
        //$session->set('user-ga', $userInfo['ga']);
500
        $session->set('user-avatar', $userInfo['avatar']);
501
        $session->set('user-avatar_thumb', $userInfo['avatar_thumb']);
502
        $session->set('user-upgrade_needed', $userInfo['upgrade_needed']);
503
        $session->set('user-is_ready_for_usage', $userInfo['is_ready_for_usage']);
504
        $session->set('user-personal_folder_enabled', $userInfo['personal_folder']);
505
        $session->set(
506
            'user-tree_load_strategy',
507
            (isset($userInfo['treeloadstrategy']) === false || empty($userInfo['treeloadstrategy']) === true) ? 'full' : $userInfo['treeloadstrategy']
508
        );
509
        //$superGlobal->put('user_agsescardid', $userInfo['agses-usercardid'], 'SESSION', 'user');
510
        $session->set('user-language', $userInfo['user_language']);
511
        $session->set('user-timezone', $userInfo['usertimezone']);
512
        $session->set('user-keys_recovery_time', $userInfo['keys_recovery_time']);
513
514
        // User signature keys
515
        $returnKeys = prepareUserEncryptionKeys($userInfo, $passwordClear);  
516
        $session->set('user-private_key', $returnKeys['private_key_clear']);
517
        $session->set('user-public_key', $returnKeys['public_key']);      
518
519
        // API key
520
        $session->set(
521
            'user-api_key',
522
            empty($userInfo['api_key']) === false ? base64_decode(decryptUserObjectKey($userInfo['api_key'], $returnKeys['private_key_clear'])) : '',
523
        );
524
        
525
        $session->set('user-special', $userInfo['special']);
526
        $session->set('user-auth_type', $userInfo['auth_type']);
527
        // manage session expiration
528
        $session->set('user-session_duration', (int) (time() + ($dataReceived['duree_session'] * 60)));
529
530
        // check feedback regarding user password validity
531
        $return = checkUserPasswordValidity(
532
            $userInfo,
533
            $session->get('user-num_days_before_exp'),
534
            $session->get('user-last_pw_change'),
535
            $SETTINGS
536
        );
537
        $session->set('user-validite_pw', $return['validite_pw']);
538
        $session->set('user-last_pw_change', $return['last_pw_change']);
539
        $session->set('user-num_days_before_exp', $return['numDaysBeforePwExpiration']);
540
        $session->set('user-force_relog', $return['user_force_relog']);
541
        
542
        $session->set('user-last_connection', empty($userInfo['last_connexion']) === false ? (int) $userInfo['last_connexion'] : (int) time());
543
        $session->set('user-latest_items', empty($userInfo['latest_items']) === false ? explode(';', $userInfo['latest_items']) : []);
544
        $session->set('user-favorites', empty($userInfo['favourites']) === false ? explode(';', $userInfo['favourites']) : []);
545
        $session->set('user-accessible_folders', empty($userInfo['groupes_visibles']) === false ? explode(';', $userInfo['groupes_visibles']) : []);
546
        $session->set('user-no_access_folders', empty($userInfo['groupes_interdits']) === false ? explode(';', $userInfo['groupes_interdits']) : []);
547
        
548
        // User's roles
549
        if (strpos($userInfo['fonction_id'] !== NULL ? (string) $userInfo['fonction_id'] : '', ',') !== -1) {
550
            // Convert , to ;
551
            $userInfo['fonction_id'] = str_replace(',', ';', (string) $userInfo['fonction_id']);
552
            DB::update(
553
                prefixTable('users'),
554
                [
555
                    'fonction_id' => $userInfo['fonction_id'],
556
                ],
557
                'id = %i',
558
                $session->get('user-id')
559
            );
560
        }
561
        // Append with roles from AD groups
562
        if (is_null($userInfo['roles_from_ad_groups']) === false) {
563
            $userInfo['fonction_id'] = empty($userInfo['fonction_id'])  === true ? $userInfo['roles_from_ad_groups'] : $userInfo['fonction_id']. ';' . $userInfo['roles_from_ad_groups'];
564
        }
565
        // store
566
        $session->set('user-roles', $userInfo['fonction_id']);
567
        $session->set('user-roles_array', array_unique(array_filter(explode(';', $userInfo['fonction_id']))));
568
        
569
        // build array of roles
570
        $session->set('user-pw_complexity', 0);
571
        $session->set('system-array_roles', []);
572
        if (count($session->get('user-roles_array')) > 0) {
573
            $rolesList = DB::query(
574
                'SELECT id, title, complexity
575
                FROM ' . prefixTable('roles_title') . '
576
                WHERE id IN %li',
577
                $session->get('user-roles_array')
578
            );
579
            $excludeUser = isset($SETTINGS['exclude_user']) ? str_contains($session->get('user-login'), $SETTINGS['exclude_user']) : false;
580
            $adjustPermissions = ($session->get('user-id') >= 1000000 && !$excludeUser && (isset($SETTINGS['admin_needle']) || isset($SETTINGS['manager_needle']) || isset($SETTINGS['tp_manager_needle']) || isset($SETTINGS['read_only_needle'])));
581
            if ($adjustPermissions) {
582
                $userInfo['admin'] = $userInfo['gestionnaire'] = $userInfo['can_manage_all_users'] = $userInfo['read_only'] = 0;
583
            }
584
            foreach ($rolesList as $role) {
585
                SessionManager::addRemoveFromSessionAssociativeArray(
586
                    'system-array_roles',
587
                    [
588
                        'id' => $role['id'],
589
                        'title' => $role['title'],
590
                    ],
591
                    'add'
592
                );
593
                
594
                if ($adjustPermissions) {
595
                    if (isset($SETTINGS['admin_needle']) && str_contains($role['title'], $SETTINGS['admin_needle'])) {
596
                        $userInfo['gestionnaire'] = $userInfo['can_manage_all_users'] = $userInfo['read_only'] = 0;
597
                        $userInfo['admin'] = 1;
598
                    }    
599
                    if (isset($SETTINGS['manager_needle']) && str_contains($role['title'], $SETTINGS['manager_needle'])) {
600
                        $userInfo['admin'] = $userInfo['can_manage_all_users'] = $userInfo['read_only'] = 0;
601
                        $userInfo['gestionnaire'] = 1;
602
                    }
603
                    if (isset($SETTINGS['tp_manager_needle']) && str_contains($role['title'], $SETTINGS['tp_manager_needle'])) {
604
                        $userInfo['admin'] = $userInfo['gestionnaire'] = $userInfo['read_only'] = 0;
605
                        $userInfo['can_manage_all_users'] = 1;
606
                    }
607
                    if (isset($SETTINGS['read_only_needle']) && str_contains($role['title'], $SETTINGS['read_only_needle'])) {
608
                        $userInfo['admin'] = $userInfo['gestionnaire'] = $userInfo['can_manage_all_users'] = 0;
609
                        $userInfo['read_only'] = 1;
610
                    }
611
                }
612
613
                // get highest complexity
614
                if ($session->get('user-pw_complexity') < (int) $role['complexity']) {
615
                    $session->set('user-pw_complexity', (int) $role['complexity']);
616
                }
617
            }
618
            if ($adjustPermissions) {
619
                $session->set('user-admin', (int) $userInfo['admin']);
620
                $session->set('user-manager', (int) $userInfo['gestionnaire']);
621
                $session->set('user-can_manage_all_users',(int)  $userInfo['can_manage_all_users']);
622
                $session->set('user-read_only', (int) $userInfo['read_only']);
623
                DB::update(
624
                    prefixTable('users'),
625
                    [
626
                        'admin' => $userInfo['admin'],
627
                        'gestionnaire' => $userInfo['gestionnaire'],
628
                        'can_manage_all_users' => $userInfo['can_manage_all_users'],
629
                        'read_only' => $userInfo['read_only'],
630
                    ],
631
                    'id = %i',
632
                    $session->get('user-id')
633
                );
634
            }
635
        }
636
637
        // Set some settings
638
        $SETTINGS['update_needed'] = '';
639
640
        // Update table
641
        DB::update(
642
            prefixTable('users'),
643
            array_merge(
644
                [
645
                    'key_tempo' => $session->get('key'),
646
                    'last_connexion' => time(),
647
                    'timestamp' => time(),
648
                    'disabled' => 0,
649
                    'no_bad_attempts' => 0,
650
                    'session_end' => $session->get('user-session_duration'),
651
                    'user_ip' => $dataReceived['client'],
652
                ],
653
                $returnKeys['update_keys_in_db']
654
            ),
655
            'id=%i',
656
            $userInfo['id']
657
        );
658
        
659
        // Get user's rights
660
        if ($userLdap['user_initial_creation_through_ldap'] === true) {
661
            // is new LDAP user. Show only his personal folder
662
            if ($SETTINGS['enable_pf_feature'] === '1') {
663
                $session->set('user-personal_visible_folders', [$userInfo['id']]);
664
                $session->set('user-personal_folders', [$userInfo['id']]);
665
            } else {
666
                $session->set('user-personal_visible_folders', []);
667
                $session->set('user-personal_folders', []);
668
            }
669
            $session->set('user-all_non_personal_folders', []);
670
            $session->set('user-roles_array', []);
671
            $session->set('user-read_only_folders', []);
672
            $session->set('user-list_folders_limited', []);
673
            $session->set('system-list_folders_editable_by_role', []);
674
            $session->set('system-list_restricted_folders_for_items', []);
675
            $session->set('user-nb_folders', 1);
676
            $session->set('user-nb_roles', 1);
677
        } else {
678
            identifyUserRights(
679
                $userInfo['groupes_visibles'],
680
                $session->get('user-no_access_folders'),
681
                $userInfo['admin'],
682
                $userInfo['fonction_id'],
683
                $SETTINGS
684
            );
685
        }
686
        // Get some more elements
687
        $session->set('system-screen_height', $dataReceived['screenHeight']);
688
689
        // Get last seen items
690
        $session->set('user-latest_items_tab', []);
691
        $session->set('user-nb_roles', 0);
692
        foreach ($session->get('user-latest_items') as $item) {
693
            if (! empty($item)) {
694
                $dataLastItems = DB::queryFirstRow(
695
                    'SELECT id,label,id_tree
696
                    FROM ' . prefixTable('items') . '
697
                    WHERE id=%i',
698
                    $item
699
                );
700
                SessionManager::addRemoveFromSessionAssociativeArray(
701
                    'user-latest_items_tab',
702
                    [
703
                        'id' => $item,
704
                        'label' => $dataLastItems['label'],
705
                        'url' => 'index.php?page=items&amp;group=' . $dataLastItems['id_tree'] . '&amp;id=' . $item,
706
                    ],
707
                    'add'
708
                );
709
            }
710
        }
711
712
        // Get cahce tree info
713
        $cacheTreeData = DB::queryFirstRow(
714
            'SELECT visible_folders
715
            FROM ' . prefixTable('cache_tree') . '
716
            WHERE user_id=%i',
717
            (int) $session->get('user-id')
718
        );
719
        if (DB::count() > 0 && empty($cacheTreeData['visible_folders']) === true) {
720
            $session->set('user-cache_tree', '');
721
            // Prepare new task
722
            DB::insert(
723
                prefixTable('processes'),
724
                array(
725
                    'created_at' => time(),
726
                    'process_type' => 'user_build_cache_tree',
727
                    'arguments' => json_encode([
728
                        'user_id' => (int) $session->get('user-id'),
729
                    ], JSON_HEX_QUOT | JSON_HEX_TAG),
730
                    'updated_at' => '',
731
                    'finished_at' => '',
732
                    'output' => '',
733
                )
734
            );
735
        } else {
736
            $session->set('user-cache_tree', $cacheTreeData['visible_folders']);
737
        }
738
739
        // send back the random key
740
        $return = $dataReceived['randomstring'];
741
        // Send email
742
        if (
743
            isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true
744
            && (int) $sessionAdmin !== 1
745
        ) {
746
            // get all Admin users
747
            $val = DB::queryfirstrow('SELECT email FROM ' . prefixTable('users') . " WHERE admin = %i and email != ''", 1);
748
            if (DB::count() > 0) {
749
                // Add email to table
750
                prepareSendingEmail(
751
                    $lang->get('email_subject_on_user_login'),
752
                    str_replace(
753
                        [
754
                            '#tp_user#',
755
                            '#tp_date#',
756
                            '#tp_time#',
757
                        ],
758
                        [
759
                            ' ' . $session->get('user-login') . ' (IP: ' . getClientIpServer() . ')',
760
                            date($SETTINGS['date_format'], (int) $session->get('user-last_connection')),
761
                            date($SETTINGS['time_format'], (int) $session->get('user-last_connection')),
762
                        ],
763
                        $lang->get('email_body_on_user_login')
764
                    ),
765
                    $val['email'],
766
                    $lang->get('administrator'),
767
                    $SETTINGS
768
                );
769
            }
770
        }
771
772
        // Ensure Complexity levels are translated
773
        defineComplexity();
774
775
        echo prepareExchangedData(
776
            [
777
                'value' => $return,
778
                'user_id' => $session->get('user-id') !== null ? $session->get('user-id') : '',
779
                'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
780
                'initial_url' => $antiXss->xss_clean($sessionUrl),
781
                'pwd_attempts' => 0,
782
                'error' => false,
783
                'message' => null !== $session->get('user-upgrade_needed') && (int) $session->get('user-upgrade_needed') === 1 ? 'ask_for_otc' : '',
784
                'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
785
                'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
786
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
787
                'private_key_conform' => $session->get('user-id') !== null
788
                    && empty($session->get('user-private_key')) === false
789
                    && $session->get('user-private_key') !== 'none' ? true : false,
790
                'session_key' => $session->get('key'),
791
                'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
792
                'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
793
                'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
794
                'upgrade_needed' => isset($userInfo['upgrade_needed']) === true ? (int) $userInfo['upgrade_needed'] : 0,
795
                'special' => isset($userInfo['special']) === true ? (int) $userInfo['special'] : 0,
796
            ],
797
            'encode'
798
        );
799
    
800
        return true;
801
802
    } elseif ((int) $userInfo['disabled'] === 1) {
803
        // User and password is okay but account is locked
804
        echo prepareExchangedData(
805
            [
806
                'value' => $return,
807
                'user_id' => $session->get('user-id') !== null ? (int) $session->get('user-id') : '',
808
                'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
809
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
810
                'pwd_attempts' => 0,
811
                'error' => 'user_is_locked',
812
                'message' => $lang->get('account_is_locked'),
813
                'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
814
                'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
815
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
816
                'private_key_conform' => null !== $session->get('user-private_key')
817
                    && empty($session->get('user-private_key')) === false
818
                    && $session->get('user-private_key') !== 'none' ? true : false,
819
                'session_key' => $session->get('key'),
820
                'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
821
                'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
822
                'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
823
            ],
824
            'encode'
825
        );
826
        return false;
827
    }
828
829
    // DEFAULT CASE
830
    // User exists in the DB but Password is false
831
    // check if user is locked
832
    if (isUserLocked(
833
            (int) $userInfo['no_bad_attempts'],
834
            $userInfo['id'],
835
            $username,
836
            $SETTINGS
837
        ) === true
838
    ) {
839
        echo prepareExchangedData(
840
            [
841
                'value' => $return,
842
                'user_id' => $session->get('user-id') !== null ? (int) $session->get('user-id') : '',
843
                'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
844
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
845
                'pwd_attempts' => 0,
846
                'error' => 'user_is_locked',
847
                'message' => $lang->get('account_is_locked'),
848
                'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
849
                'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
850
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
851
                'private_key_conform' => $session->get('user-id') !== null
852
                    && empty($session->get('user-private_key')) === false
853
                    && $session->get('user-private_key') !== 'none' ? true : false,
854
                'session_key' => $session->get('key'),
855
                'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
856
                'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
857
                'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
858
            ],
859
            'encode'
860
        );
861
        return false;
862
    }
863
    echo prepareExchangedData(
864
        [
865
            'value' => $return,
866
            'user_id' => $session->get('user-id') !== null ? (int) $session->get('user-id') : '',
867
            'user_admin' => null !== $session->get('user-admin') ? $session->get('user-admin') : 0,
868
            'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
869
            'pwd_attempts' => (int) $sessionPwdAttempts,
870
            'error' => true,
871
            'message' => $lang->get('error_not_allowed_to_authenticate'),
872
            'first_connection' => $session->get('user-validite_pw') === 0 ? true : false,
873
            'password_complexity' => TP_PW_COMPLEXITY[$session->get('user-pw_complexity')][1],
874
            'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
875
            'private_key_conform' => $session->get('user-id') !== null
876
                    && empty($session->get('user-private_key')) === false
877
                    && $session->get('user-private_key') !== 'none' ? true : false,
878
            'session_key' => $session->get('key'),
879
            'can_create_root_folder' => null !== $session->get('user-can_create_root_folder') ? (int) $session->get('user-can_create_root_folder') : '',
880
            'shown_warning_unsuccessful_login' => $session->get('user-unsuccessfull_login_attempts_shown'),
881
            'nb_unsuccessful_logins' => $session->get('user-unsuccessfull_login_attempts_nb'),
882
        ],
883
        'encode'
884
    );
885
    return false;
886
}
887
888
/**
889
 * Check if any unsuccessfull login tries exist
890
 *
891
 * @param int       $userInfoId
892
 * @param string    $userInfoLogin
893
 * @param string    $userInfoLastConnection
894
 * @param string    $username
895
 * @param array     $SETTINGS
896
 * @return array
897
 */
898
function handleLoginAttempts(
899
    $userInfoId,
900
    $userInfoLogin,
901
    $userInfoLastConnection,
902
    $username,
903
    $SETTINGS
904
) : array
905
{
906
    $rows = DB::query(
907
        'SELECT date
908
        FROM ' . prefixTable('log_system') . "
909
        WHERE field_1 = %s
910
        AND type = 'failed_auth'
911
        AND label = 'password_is_not_correct'
912
        AND date >= %s AND date < %s",
913
        $userInfoLogin,
914
        $userInfoLastConnection,
915
        time()
916
    );
917
    $arrAttempts = [];
918
    if (DB::count() > 0) {
919
        foreach ($rows as $record) {
920
            array_push(
921
                $arrAttempts,
922
                date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['date'])
923
            );
924
        }
925
    }
926
    
927
928
    // Log into DB the user's connection
929
    if (isKeyExistingAndEqual('log_connections', 1, $SETTINGS) === true) {
930
        logEvents($SETTINGS, 'user_connection', 'connection', (string) $userInfoId, stripslashes($username));
931
    }
932
933
    return [
934
        'attemptsList' => $arrAttempts,
935
        'attemptsCount' => count($rows),
936
    ];
937
}
938
939
/**
940
 * Permits to load config file
941
 *
942
 * @return boolean
943
 */
944
function findTpConfigFile() : bool
945
{
946
    if (file_exists('../includes/config/tp.config.php')) {
947
        include_once '../includes/config/tp.config.php';
948
        return true;
949
    } elseif (file_exists('./includes/config/tp.config.php')) {
950
        include_once './includes/config/tp.config.php';
951
    } elseif (file_exists('../../includes/config/tp.config.php')) {
952
        include_once '../../includes/config/tp.config.php';
953
    } elseif (file_exists('../../../includes/config/tp.config.php')) {
954
        include_once '../../../includes/config/tp.config.php';
955
    }
956
    return false;
957
}
958
959
/**
960
 * Can you user get logged into main page
961
 *
962
 * @param array     $SETTINGS
963
 * @param int       $userInfoDisabled
964
 * @param string    $username
965
 * @param bool      $ldapConnection
966
 *
967
 * @return boolean
968
 */
969
function canUserGetLog(
970
    $SETTINGS,
971
    $userInfoDisabled,
972
    $username,
973
    $ldapConnection
974
) : bool
975
{
976
    include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
977
978
    if ((int) $userInfoDisabled === 1) {
979
        return false;
980
    }
981
982
    if (isKeyExistingAndEqual('ldap_mode', 0, $SETTINGS) === true) {
983
        return true;
984
    }
985
    
986
    if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true 
987
        && (
988
            ($ldapConnection === true && $username !== 'admin')
989
            || $username === 'admin'
990
        )
991
    ) {
992
        return true;
993
    }
994
995
    if (isKeyExistingAndEqual('ldap_and_local_authentication', 1, $SETTINGS) === true
996
        && isset($SETTINGS['ldap_mode']) === true && in_array($SETTINGS['ldap_mode'], ['1', '2']) === true
997
    ) {
998
        return true;
999
    }
1000
1001
    return false;
1002
}
1003
1004
/**
1005
 * Manages if user is locked or not
1006
 *
1007
 * @param int       $nbAttempts
1008
 * @param int       $userId
1009
 * @param string    $username
1010
 * @param string    $key
1011
 * @param array     $SETTINGS
1012
 *
1013
 * @return boolean
1014
 */
1015
function isUserLocked(
1016
    $nbAttempts,
1017
    $userId,
1018
    $username,
1019
    $SETTINGS
1020
) : bool 
1021
{
1022
    $userIsLocked = false;
1023
    $nbAttempts++;
1024
    if (
1025
        (int) $SETTINGS['nb_bad_authentication'] > 0
1026
        && (int) $SETTINGS['nb_bad_authentication'] < $nbAttempts
1027
    ) {
1028
        // User is now locked as too many attempts
1029
        $userIsLocked = true;
1030
1031
        // log it
1032
        if (isKeyExistingAndEqual('log_connections', 1, $SETTINGS) === true) {
1033
            logEvents($SETTINGS, 'user_locked', 'connection', (string) $userId, stripslashes($username));
1034
        }
1035
    }
1036
    
1037
    DB::update(
1038
        prefixTable('users'),
1039
        [
1040
            'disabled' => $userIsLocked,
1041
            'no_bad_attempts' => $nbAttempts,
1042
        ],
1043
        'id=%i',
1044
        $userId
1045
    );
1046
1047
    return $userIsLocked;
1048
}
1049
1050
1051
/**
1052
 * 
1053
 * Prepare user keys
1054
 * 
1055
 * @param array $userInfo   User account information
1056
 * @param string $passwordClear
1057
 *
1058
 * @return array
1059
 */
1060
function prepareUserEncryptionKeys($userInfo, $passwordClear) : array
1061
{
1062
    if (is_null($userInfo['private_key']) === true || empty($userInfo['private_key']) === true || $userInfo['private_key'] === 'none') {
1063
        // No keys have been generated yet
1064
        // Create them
1065
        $userKeys = generateUserKeys($passwordClear);
1066
1067
        return [
1068
            'public_key' => $userKeys['public_key'],
1069
            'private_key_clear' => $userKeys['private_key_clear'],
1070
            'update_keys_in_db' => [
1071
                'public_key' => $userKeys['public_key'],
1072
                'private_key' => $userKeys['private_key'],
1073
            ],
1074
        ];
1075
    } 
1076
    
1077
    if ($userInfo['special'] === 'generate-keys') {
1078
        return [
1079
            'public_key' => $userInfo['public_key'],
1080
            'private_key_clear' => '',
1081
            'update_keys_in_db' => [],
1082
        ];
1083
    }
1084
    
1085
    // Don't perform this in case of special login action
1086
    if ($userInfo['special'] === 'otc_is_required_on_next_login' || $userInfo['special'] === 'user_added_from_ldap') {
1087
        return [
1088
            'public_key' => $userInfo['public_key'],
1089
            'private_key_clear' => '',
1090
            'update_keys_in_db' => [],
1091
        ];
1092
    }
1093
    
1094
    // Uncrypt private key
1095
    return [
1096
        'public_key' => $userInfo['public_key'],
1097
        'private_key_clear' => decryptPrivateKey($passwordClear, $userInfo['private_key']),
1098
        'update_keys_in_db' => [],
1099
    ];
1100
}
1101
1102
1103
/**
1104
 * CHECK PASSWORD VALIDITY
1105
 * Don't take into consideration if LDAP in use
1106
 * 
1107
 * @param array $userInfo                       User account information
1108
 * @param int $numDaysBeforePwExpiration
1109
 * @param int $lastPwChange
1110
 * @param array $SETTINGS                       Teampass settings
1111
 *
1112
 * @return array
1113
 */
1114
function checkUserPasswordValidity($userInfo, $numDaysBeforePwExpiration, $lastPwChange, $SETTINGS)
1115
{
1116
    if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true) {
1117
        return [
1118
            'validite_pw' => true,
1119
            'last_pw_change' => $userInfo['last_pw_change'],
1120
            'user_force_relog' => '',
1121
            'numDaysBeforePwExpiration' => '',
1122
        ];
1123
    }
1124
1125
    if (isset($userInfo['last_pw_change']) === true) {
1126
        if ((int) $SETTINGS['pw_life_duration'] === 0) {
1127
            return [
1128
                'validite_pw' => true,
1129
                'last_pw_change' => '',
1130
                'user_force_relog' => 'infinite',
1131
                'numDaysBeforePwExpiration' => '',
1132
            ];
1133
        }
1134
        
1135
        return [
1136
            'validite_pw' => $numDaysBeforePwExpiration <= 0 ? false : true,
1137
            'last_pw_change' => $userInfo['last_pw_change'],
1138
            'user_force_relog' => 'infinite',
1139
            'numDaysBeforePwExpiration' => $SETTINGS['pw_life_duration'] - round(
1140
                (mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')) - $lastPwChange) / (24 * 60 * 60)),
1141
        ];
1142
    } else {
1143
        return [
1144
            'validite_pw' => false,
1145
            'last_pw_change' => '',
1146
            'user_force_relog' => '',
1147
            'numDaysBeforePwExpiration' => '',
1148
        ];
1149
    }
1150
}
1151
1152
1153
/**
1154
 * Authenticate a user through AD.
1155
 *
1156
 * @param string $username      Username
1157
 * @param array $userInfo       User account information
1158
 * @param string $passwordClear Password
1159
 * @param array $SETTINGS       Teampass settings
1160
 *
1161
 * @return array
1162
 */
1163
function authenticateThroughAD(string $username, array $userInfo, string $passwordClear, array $SETTINGS): array
1164
{
1165
    $lang = new Language(); 
1166
1167
    // 1- Connect to LDAP
1168
    try {
1169
        switch ($SETTINGS['ldap_type']) {
1170
            case 'ActiveDirectory':
1171
                $ldapExtra = new LdapExtra($SETTINGS);
1172
                $ldapConnection = $ldapExtra->establishLdapConnection();
1173
                $activeDirectoryExtra = new ActiveDirectoryExtra();
1174
                break;
1175
            case 'OpenLDAP':
1176
                // Establish connection for OpenLDAP
1177
                $ldapExtra = new LdapExtra($SETTINGS);
1178
                $ldapConnection = $ldapExtra->establishLdapConnection();
1179
1180
                // Create an instance of OpenLdapExtra and configure it
1181
                $openLdapExtra = new OpenLdapExtra();
1182
                break;
1183
            default:
1184
                throw new Exception("Unsupported LDAP type: " . $SETTINGS['ldap_type']);
1185
        }
1186
    } catch (Exception $e) {
1187
        echo prepareExchangedData(array(
1188
            'error' => true,
1189
            'message' => $e->getMessage(),
1190
        ), 'encode');
1191
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1192
    }
1193
    
1194
    try {
1195
        // 2- Get user info from AD
1196
        // We want to isolate attribute ldap_user_attribute or mostly samAccountName
1197
        $userADInfos = $ldapConnection->query()
1198
            ->where((isset($SETTINGS['ldap_user_attribute']) ===true && empty($SETTINGS['ldap_user_attribute']) === false) ? $SETTINGS['ldap_user_attribute'] : 'samaccountname', '=', $username)
1199
            ->firstOrFail();
1200
1201
        // Is user enabled? Only ActiveDirectory
1202
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory' && isset($activeDirectoryExtra) && $activeDirectoryExtra instanceof ActiveDirectoryExtra) {
1203
            //require_once 'ldap.activedirectory.php';
1204
            if ($activeDirectoryExtra->userIsEnabled((string) $userADInfos['dn'], $ldapConnection) === false) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $activeDirectoryExtra does not seem to be defined for all execution paths leading up to this point.
Loading history...
1205
                return [
1206
                    'error' => true,
1207
                    'message' => "Error : User is not enabled",
1208
                ];
1209
            }
1210
        }
1211
1212
        // 3- User auth attempt
1213
        // For AD, we use attribute userPrincipalName
1214
        // For OpenLDAP and others, we use attribute dn
1215
        $userAuthAttempt = $ldapConnection->auth()->attempt(
1216
            $SETTINGS['ldap_type'] === 'ActiveDirectory' ?
1217
                $userADInfos['userprincipalname'][0] :  // refering to https://ldaprecord.com/docs/core/v2/authentication#basic-authentication
1218
                $userADInfos['dn'],
1219
            $passwordClear
1220
        );
1221
1222
        // User is not auth then return error
1223
        if ($userAuthAttempt === false) {
1224
            return [
1225
                'error' => true,
1226
                'message' => "Error: User is not authenticated",
1227
            ];
1228
        }
1229
1230
    } catch (\LdapRecord\Query\ObjectNotFoundException $e) {
1231
        $error = $e->getDetailedError();
1232
        return [
1233
            'error' => true,
1234
            'message' => $lang->get('error')." - ".(isset($error) === true ? $error->getErrorCode()." - ".$error->getErrorMessage(). "<br>".$error->getDiagnosticMessage() : $e),
1235
1236
        ];
1237
    }
1238
1239
    // 4- Check shadowexpire attribute
1240
    // if === 1 then user disabled
1241
    if (
1242
        (isset($userADInfos['shadowexpire'][0]) === true && (int) $userADInfos['shadowexpire'][0] === 1)
1243
        ||
1244
        (isset($userADInfos['accountexpires'][0]) === true && (int) $userADInfos['accountexpires'][0] < time() && (int) $userADInfos['accountexpires'][0] != 0)
1245
    ) {
1246
        return [
1247
            'error' => true,
1248
            'message' => $lang->get('error_ad_user_expired'),
1249
        ];
1250
    }
1251
1252
    // Create LDAP user if not exists and tasks enabled
1253
    if ($userInfo['ldap_user_to_be_created'] === true) {   
1254
        $userInfo = ldapCreateUser(
1255
            $username,
1256
            $passwordClear,
1257
            $userADInfos['mail'][0],
1258
            $userADInfos['givenname'][0],
1259
            $userADInfos['sn'][0],
1260
            $SETTINGS
1261
        );
1262
1263
        // prepapre background tasks for item keys generation  
1264
        handleUserKeys(
1265
            (int) $userInfo['id'],
1266
            (string) $passwordClear,
1267
            (int) (isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : NUMBER_ITEMS_IN_BATCH),
1268
            uniqidReal(20),
1269
            true,
1270
            true,
1271
            true,
1272
            false,
1273
            $lang->get('email_body_user_config_2'),
1274
        );
1275
1276
        // Complete $userInfo
1277
        $userInfo['has_been_created'] = 1;
1278
    } else {
1279
        $userInfo['has_been_created'] = 0;
1280
    }
1281
1282
    // Update user info with his AD groups
1283
    if ($SETTINGS['ldap_type'] === 'ActiveDirectory' && isset($activeDirectoryExtra) && $activeDirectoryExtra instanceof ActiveDirectoryExtra) {
1284
        $userGroupsData = $activeDirectoryExtra->getUserADGroups(
1285
            $userADInfos[(isset($SETTINGS['ldap_user_dn_attribute']) === true && empty($SETTINGS['ldap_user_dn_attribute']) === false) ? $SETTINGS['ldap_user_dn_attribute'] : 'distinguishedname'][0], 
1286
            $ldapConnection, 
1287
            $SETTINGS
1288
        );
1289
    } elseif ($SETTINGS['ldap_type'] == 'OpenLDAP' && isset($openLdapExtra) && $openLdapExtra instanceof OpenLdapExtra) {
1290
        $userGroupsData = $openLdapExtra->getUserADGroups(
1291
            $userADInfos['dn'],
1292
            $ldapConnection,
1293
            $SETTINGS
1294
        );
1295
    } else {
1296
        // error
1297
        return [
1298
            'error' => true,
1299
            'message' => "Error: Unsupported LDAP type: " . $SETTINGS['ldap_type'],
1300
        ];
1301
    }
1302
    
1303
    handleUserADGroups(
1304
        $username,
1305
        $userInfo,
1306
        $userGroupsData['userGroups'],
1307
        $SETTINGS
1308
    );
1309
1310
    // Finalize authentication
1311
    finalizeAuthentication($userInfo, $passwordClear, $SETTINGS);
1312
1313
    return [
1314
        'error' => false,
1315
        'message' => '',
1316
        'user_info' => $userInfo,
1317
    ];
1318
}
1319
1320
/**
1321
 * Permits to update the user's AD groups with mapping roles
1322
 *
1323
 * @param string $username
1324
 * @param array $userInfo
1325
 * @param array $groups
1326
 * @param array $SETTINGS
1327
 * @return void
1328
 */
1329
function handleUserADGroups(string $username, array $userInfo, array $groups, array $SETTINGS): void
1330
{
1331
    if (isset($SETTINGS['enable_ad_users_with_ad_groups']) === true && (int) $SETTINGS['enable_ad_users_with_ad_groups'] === 1) {
1332
        // Get user groups from AD
1333
        $user_ad_groups = [];
1334
        foreach($groups as $group) {
1335
            //print_r($group);
1336
            // get relation role id for AD group
1337
            $role = DB::queryFirstRow(
1338
                'SELECT lgr.role_id
1339
                FROM ' . prefixTable('ldap_groups_roles') . ' AS lgr
1340
                WHERE lgr.ldap_group_id = %i',
1341
                $group
1342
            );
1343
            if (DB::count() > 0) {
1344
                array_push($user_ad_groups, $role['role_id']); 
1345
            }
1346
        }
1347
        
1348
        // save
1349
        if (count($user_ad_groups) > 0) {
1350
            $user_ad_groups = implode(';', $user_ad_groups);
1351
            DB::update(
1352
                prefixTable('users'),
1353
                [
1354
                    'roles_from_ad_groups' => $user_ad_groups,
1355
                ],
1356
                'id = %i',
1357
                $userInfo['id']
1358
            );
1359
1360
            $userInfo['roles_from_ad_groups'] = $user_ad_groups;
1361
        } else {
1362
            DB::update(
1363
                prefixTable('users'),
1364
                [
1365
                    'roles_from_ad_groups' => null,
1366
                ],
1367
                'id = %i',
1368
                $userInfo['id']
1369
            );
1370
1371
            $userInfo['roles_from_ad_groups'] = [];
1372
        }
1373
    } else {
1374
        // Delete all user's AD groups
1375
        DB::update(
1376
            prefixTable('users'),
1377
            [
1378
                'roles_from_ad_groups' => null,
1379
            ],
1380
            'id = %i',
1381
            $userInfo['id']
1382
        );
1383
    }
1384
}
1385
1386
/**
1387
 * Permits to finalize the authentication process.
1388
 *
1389
 * @param array $userInfo
1390
 * @param string $passwordClear
1391
 * @param array $SETTINGS
1392
 */
1393
function finalizeAuthentication(
1394
    array $userInfo,
1395
    string $passwordClear,
1396
    array $SETTINGS
1397
): void
1398
{
1399
    // load passwordLib library
1400
    $pwdlib = new PasswordLib();
1401
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
1402
1403
    //If user has never been connected then erase current pwd with the ldap's one
1404
    if (empty($userInfo['pw']) === true) {
1405
        // Password are similar in Teampass and AD
1406
        DB::update(
1407
            prefixTable('users'),
1408
            [
1409
                'pw' => $hashedPassword,
1410
            ],
1411
            'id = %i',
1412
            $userInfo['id']
1413
        );
1414
    } elseif ($userInfo['special'] === 'user_added_from_ldap') {
1415
        // Case where user has been added from LDAP and never being connected to TP
1416
        DB::update(
1417
            prefixTable('users'),
1418
            array(
1419
                'pw' => $hashedPassword,
1420
            ),
1421
            'id = %i',
1422
            $userInfo['id']
1423
        );
1424
    } elseif ($pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw']) === false) {
1425
        // Case where user is auth by LDAP but his password in Teampass is not synchronized
1426
        // For example when user has changed his password in AD.
1427
        // So we need to update it in Teampass and ask for private key re-encryption
1428
        DB::update(
1429
            prefixTable('users'),
1430
            [
1431
                'pw' => $hashedPassword,
1432
            ],
1433
            'id = %i',
1434
            $userInfo['id']
1435
        );
1436
    }
1437
}
1438
1439
/**
1440
 * Undocumented function.
1441
 *
1442
 * @param string|array|resource $dataReceived Received data
1443
 * @param string                $userInfo     Result of query
1444
 * @param array                 $SETTINGS     Teampass settings
1445
 *
1446
 * @return array
1447
 */
1448
function yubicoMFACheck($dataReceived, string $userInfo, array $SETTINGS): array
1449
{
1450
    $session = SessionManager::getSession();
1451
    
1452
    // Load superGlobals
1453
    $superGlobal = new SuperGlobal();
0 ignored issues
show
Unused Code introduced by
The assignment to $superGlobal is dead and can be removed.
Loading history...
1454
    $lang = new Language(); 
1455
    $sessionAdmin = $session->get('user-admin');
1456
    $sessionUrl = $session->get('user-initial_url');
1457
    $sessionPwdAttempts = $session->get('pwd_attempts');
1458
    // Init
1459
    $yubico_key = htmlspecialchars_decode($dataReceived['yubico_key']);
1460
    $yubico_user_key = htmlspecialchars_decode($dataReceived['yubico_user_key']);
1461
    $yubico_user_id = htmlspecialchars_decode($dataReceived['yubico_user_id']);
1462
    if (empty($yubico_user_key) === false && empty($yubico_user_id) === false) {
1463
        // save the new yubico in user's account
1464
        DB::update(
1465
            prefixTable('users'),
1466
            [
1467
                'yubico_user_key' => $yubico_user_key,
1468
                'yubico_user_id' => $yubico_user_id,
1469
            ],
1470
            'id=%i',
1471
            $userInfo['id']
1472
        );
1473
    } else {
1474
        // Check existing yubico credentials
1475
        if ($userInfo['yubico_user_key'] === 'none' || $userInfo['yubico_user_id'] === 'none') {
1476
            return [
1477
                'error' => true,
1478
                'value' => '',
1479
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1480
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1481
                'pwd_attempts' => (int) $sessionPwdAttempts,
1482
                'message' => 'no_user_yubico_credentials',
1483
                'proceedIdentification' => false,
1484
            ];
1485
        }
1486
        $yubico_user_key = $userInfo['yubico_user_key'];
1487
        $yubico_user_id = $userInfo['yubico_user_id'];
1488
    }
1489
1490
    // Now check yubico validity
1491
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Authentication/Yubico/Yubico.php';
1492
    $yubi = new Auth_Yubico($yubico_user_id, $yubico_user_key);
1493
    $auth = $yubi->verify($yubico_key);
1494
    //, null, null, null, 60
1495
1496
    if (PEAR::isError($auth)) {
1497
        return [
1498
            'error' => true,
1499
            'value' => '',
1500
            'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1501
            'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1502
            'pwd_attempts' => (int) $sessionPwdAttempts,
1503
            'message' => $lang->get('yubico_bad_code'),
1504
            'proceedIdentification' => false,
1505
        ];
1506
    }
1507
1508
    return [
1509
        'error' => false,
1510
        'message' => '',
1511
        'proceedIdentification' => true,
1512
    ];
1513
}
1514
1515
/**
1516
 * Undocumented function.
1517
 *
1518
 * @param string $username      User name
1519
 * @param string $passwordClear User password in clear
1520
 * @param array $retLDAP       Received data from LDAP
1521
 * @param array $SETTINGS      Teampass settings
1522
 *
1523
 * @return array
1524
 */
1525
function ldapCreateUser(string $login, string $passwordClear, string $userEmail, string $userName, string $userLastname, array $SETTINGS): array
1526
{
1527
    // Generate user keys pair
1528
    $userKeys = generateUserKeys($passwordClear);
1529
1530
    // load passwordLib library
1531
    $pwdlib = new PasswordLib();
1532
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
1533
1534
    // Insert user in DB
1535
    DB::insert(
1536
        prefixTable('users'),
1537
        [
1538
            'login' => (string) $login,
1539
            'pw' => (string) $hashedPassword,
1540
            'email' => (string) $userEmail,
1541
            'name' => (string) $userName,
1542
            'lastname' => (string) $userLastname,
1543
            'admin' => '0',
1544
            'gestionnaire' => '0',
1545
            'can_manage_all_users' => '0',
1546
            'personal_folder' => $SETTINGS['enable_pf_feature'] === '1' ? '1' : '0',
1547
            'groupes_interdits' => '',
1548
            'groupes_visibles' => '',
1549
            'last_pw_change' => (int) time(),
1550
            'user-language' => (string) $SETTINGS['default_language'],
1551
            'encrypted_psk' => '',
1552
            '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,
1553
            'public_key' => $userKeys['public_key'],
1554
            'private_key' => $userKeys['private_key'],
1555
            'special' => 'none',
1556
            'auth_type' => 'ldap',
1557
            'otp_provided' => '1',
1558
            'is_ready_for_usage' => '0',
1559
        ]
1560
    );
1561
    $newUserId = DB::insertId();
1562
1563
    // Create the API key
1564
    DB::insert(
1565
        prefixTable('api'),
1566
        array(
1567
            'type' => 'user',
1568
            'user_id' => $newUserId,
1569
            'value' => encryptUserObjectKey(base64_encode(base64_encode(uniqidReal(39))), $userKeys['public_key']),
1570
            'timestamp' => time(),
1571
        )
1572
    );
1573
1574
    // Create personnal folder
1575
    if (isKeyExistingAndEqual('enable_pf_feature', 1, $SETTINGS) === true) {
1576
        DB::insert(
1577
            prefixTable('nested_tree'),
1578
            [
1579
                'parent_id' => '0',
1580
                'title' => $newUserId,
1581
                'bloquer_creation' => '0',
1582
                'bloquer_modification' => '0',
1583
                'personal_folder' => '1',
1584
                'categories' => '',
1585
            ]
1586
        );
1587
        // Rebuild tree
1588
        $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1589
        $tree->rebuild();
1590
    }
1591
1592
    return [
1593
        'error' => false,
1594
        'message' => '',
1595
        'proceedIdentification' => true,
1596
        'user_initial_creation_through_ldap' => true,
1597
        'id' => $newUserId,
1598
    ];
1599
}
1600
1601
/**
1602
 * Undocumented function.
1603
 *
1604
 * @param string                $username     Username
1605
 * @param array                 $userInfo     Result of query
1606
 * @param string|array|resource $dataReceived DataReceived
1607
 * @param array                 $SETTINGS     Teampass settings
1608
 *
1609
 * @return array
1610
 */
1611
function googleMFACheck(string $username, array $userInfo, $dataReceived, array $SETTINGS): array
1612
{
1613
    $session = SessionManager::getSession();    
1614
    $lang = new Language(); 
1615
1616
    if (
1617
        isset($dataReceived['GACode']) === true
1618
        && empty($dataReceived['GACode']) === false
1619
    ) {
1620
        $sessionAdmin = $session->get('user-admin');
1621
        $sessionUrl = $session->get('user-initial_url');
1622
        $sessionPwdAttempts = $session->get('pwd_attempts');
1623
        // create new instance
1624
        $tfa = new TwoFactorAuth($SETTINGS['ga_website_name']);
1625
        // Init
1626
        $firstTime = [];
1627
        // now check if it is the 1st time the user is using 2FA
1628
        if ($userInfo['ga_temporary_code'] !== 'none' && $userInfo['ga_temporary_code'] !== 'done') {
1629
            if ($userInfo['ga_temporary_code'] !== $dataReceived['GACode']) {
1630
                return [
1631
                    'error' => true,
1632
                    'message' => $lang->get('ga_bad_code'),
1633
                    'proceedIdentification' => false,
1634
                    'ga_bad_code' => true,
1635
                    'firstTime' => $firstTime,
1636
                ];
1637
            }
1638
1639
            // If first time with MFA code
1640
            $proceedIdentification = false;
1641
            $mfaStatus = 'ga_temporary_code_correct';
1642
            $mfaMessage = $lang->get('ga_flash_qr_and_login');
1643
            // generate new QR
1644
            $new_2fa_qr = $tfa->getQRCodeImageAsDataUri(
1645
                'Teampass - ' . $username,
1646
                $userInfo['ga']
1647
            );
1648
            // clear temporary code from DB
1649
            DB::update(
1650
                prefixTable('users'),
1651
                [
1652
                    'ga_temporary_code' => 'done',
1653
                ],
1654
                'id=%i',
1655
                $userInfo['id']
1656
            );
1657
            $firstTime = [
1658
                'value' => '<img src="' . $new_2fa_qr . '">',
1659
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1660
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1661
                'pwd_attempts' => (int) $sessionPwdAttempts,
1662
                'message' => $mfaMessage,
1663
                'mfaStatus' => $mfaStatus,
1664
            ];
1665
        } else {
1666
            // verify the user GA code
1667
            if ($tfa->verifyCode($userInfo['ga'], $dataReceived['GACode'])) {
1668
                $proceedIdentification = true;
1669
            } else {
1670
                return [
1671
                    'error' => true,
1672
                    'message' => $lang->get('ga_bad_code'),
1673
                    'proceedIdentification' => false,
1674
                    'ga_bad_code' => true,
1675
                    'firstTime' => $firstTime,
1676
                ];
1677
            }
1678
        }
1679
    } else {
1680
        return [
1681
            'error' => true,
1682
            'message' => $lang->get('ga_bad_code'),
1683
            'proceedIdentification' => false,
1684
            'ga_bad_code' => true,
1685
            'firstTime' => [],
1686
        ];
1687
    }
1688
1689
    return [
1690
        'error' => false,
1691
        'message' => '',
1692
        'proceedIdentification' => $proceedIdentification,
1693
        'firstTime' => $firstTime,
1694
    ];
1695
}
1696
1697
1698
/**
1699
 * Perform DUO checks
1700
 *
1701
 * @param string $username
1702
 * @param string|array|resource $dataReceived
1703
 * @param array $SETTINGS
1704
 * @return array
1705
 */
1706
function duoMFACheck(
1707
    string $username,
1708
    $dataReceived,
1709
    array $SETTINGS
1710
): array
1711
{
1712
    $session = SessionManager::getSession();
1713
    
1714
    // Load superGlobals
1715
    $superGlobal = new SuperGlobal();
0 ignored issues
show
Unused Code introduced by
The assignment to $superGlobal is dead and can be removed.
Loading history...
1716
    $lang = new Language(); 
1717
1718
    $sessionPwdAttempts = $session->get('pwd_attempts');
1719
    $saved_state = null !== $session->get('user-duo_state') ? $session->get('user-duo_state') : '';
1720
    $duo_status = null !== $session->get('user-duo_status') ? $session->get('user-duo_status') : '';
1721
1722
    // Ensure state and login are set
1723
    if (
1724
        (empty($saved_state) || empty($dataReceived['login']) || !isset($dataReceived['duo_state']) || empty($dataReceived['duo_state']))
1725
        && $duo_status === 'IN_PROGRESS'
1726
        && $dataReceived['duo_status'] !== 'start_duo_auth'
1727
    ) {
1728
        return [
1729
            'error' => true,
1730
            'message' => $lang->get('duo_no_data'),
1731
            'pwd_attempts' => (int) $sessionPwdAttempts,
1732
            'proceedIdentification' => false,
1733
        ];
1734
    }
1735
1736
    // Ensure state matches from initial request
1737
    if ($duo_status === 'IN_PROGRESS' && $dataReceived['duo_state'] !== $saved_state) {
1738
        $session->set('user-duo_state', '');
1739
        $session->set('user-duo_status', '');
1740
1741
        // We did not received a proper Duo state
1742
        return [
1743
            'error' => true,
1744
            'message' => $lang->get('duo_error_state'),
1745
            'pwd_attempts' => (int) $sessionPwdAttempts,
1746
            'proceedIdentification' => false,
1747
        ];
1748
    }
1749
1750
    return [
1751
        'error' => false,
1752
        'pwd_attempts' => (int) $sessionPwdAttempts,
1753
        'saved_state' => $saved_state,
1754
        'duo_status' => $duo_status,
1755
    ];
1756
}
1757
1758
1759
/**
1760
 * Create the redirect URL or check if the DUO Universal prompt was completed successfully.
1761
 *
1762
 * @param string                $username               Username
1763
 * @param string|array|resource $dataReceived           DataReceived
1764
 * @param array                 $sessionPwdAttempts     Nb of pwd attempts
1765
 * @param array                 $saved_state            Saved state
1766
 * @param array                 $duo_status             Duo status
1767
 * @param array                 $SETTINGS               Teampass settings
1768
 *
1769
 * @return array
1770
 */
1771
function duoMFAPerform(
1772
    string $username,
1773
    $dataReceived,
1774
    int $sessionPwdAttempts,
1775
    string $saved_state,
1776
    string $duo_status,
1777
    array $SETTINGS
1778
): array
1779
{
1780
    $session = SessionManager::getSession();
1781
    
1782
    // Load superGlobals
1783
    $superGlobal = new SuperGlobal();
0 ignored issues
show
Unused Code introduced by
The assignment to $superGlobal is dead and can be removed.
Loading history...
1784
    $lang = new Language(); 
1785
1786
    try {
1787
        $duo_client = new Client(
1788
            $SETTINGS['duo_ikey'],
1789
            $SETTINGS['duo_skey'],
1790
            $SETTINGS['duo_host'],
1791
            $SETTINGS['cpassman_url'].'/'.DUO_CALLBACK
1792
        );
1793
    } catch (DuoException $e) {
1794
        return [
1795
            'error' => true,
1796
            'message' => $lang->get('duo_config_error'),
1797
            'debug_message' => $e->getMessage(),
1798
            'pwd_attempts' => (int) $sessionPwdAttempts,
1799
            'proceedIdentification' => false,
1800
        ];
1801
    }
1802
        
1803
    try {
1804
        $duo_error = $lang->get('duo_error_secure');
1805
        $duo_failmode = "none";
1806
        $duo_client->healthCheck();
1807
    } catch (DuoException $e) {
1808
        //Not implemented Duo Failmode in case the Duo services are not available
1809
        /*if ($SETTINGS['duo_failmode'] == "safe") {
1810
            # If we're failing open, errors in 2FA still allow for success
1811
            $duo_error = $lang->get('duo_error_failopen');
1812
            $duo_failmode = "safe";
1813
        } else {
1814
            # Duo has failed and is unavailable, redirect user to the login page
1815
            $duo_error = $lang->get('duo_error_secure');
1816
            $duo_failmode = "secure";
1817
        }*/
1818
        return [
1819
            'error' => true,
1820
            'message' => $duo_error . $lang->get('duo_error_check_config'),
1821
            'pwd_attempts' => (int) $sessionPwdAttempts,
1822
            'debug_message' => $e->getMessage(),
1823
            'proceedIdentification' => false,
1824
        ];
1825
    }
1826
    
1827
    // Check if no one played with the javascript
1828
    if ($duo_status !== 'IN_PROGRESS' && $dataReceived['duo_status'] === 'start_duo_auth') {
1829
        # Create the Duo URL to send the user to
1830
        try {
1831
            $duo_state = $duo_client->generateState();
1832
            $duo_redirect_url = $duo_client->createAuthUrl($username, $duo_state);
1833
        } catch (DuoException $e) {
1834
            return [
1835
                'error' => true,
1836
                'message' => $duo_error . $lang->get('duo_error_url'),
1837
                'pwd_attempts' => (int) $sessionPwdAttempts,
1838
                'debug_message' => $e->getMessage(),
1839
                'proceedIdentification' => false,
1840
            ];
1841
        }
1842
        
1843
        // Somethimes Duo return success but fail to return a URL, double check if the URL has been created
1844
        if (!empty($duo_redirect_url) && isset($duo_redirect_url) && filter_var($duo_redirect_url,FILTER_SANITIZE_URL)) {
1845
            // Since Duo Universal requires a redirect, let's store some info when the user get's back after completing the Duo prompt
1846
            $key = hash('sha256', $duo_state);
1847
            $iv = substr(hash('sha256', $duo_state), 0, 16);
1848
            $duo_data = serialize([
1849
                'duo_login' => $username,
1850
                'duo_pwd' => $dataReceived['pw'],
1851
            ]);
1852
            $duo_data_enc = openssl_encrypt($duo_data, 'AES-256-CBC', $key, 0, $iv);
1853
            $session->set('user-duo_state', $duo_state);
1854
            $session->set('user-duo_data', base64_encode($duo_data_enc));
1855
            $session->set('user-duo_status', 'IN_PROGRESS');
1856
            $session->set('user-login', $username);
1857
            
1858
            // If we got here we can reset the password attempts
1859
            $session->set('pwd_attempts', 0);
1860
            
1861
            return [
1862
                'error' => false,
1863
                'message' => '',
1864
                'proceedIdentification' => false,
1865
                'duo_url_ready' => true,
1866
                'duo_redirect_url' => $duo_redirect_url,
1867
                'duo_failmode' => $duo_failmode,
1868
            ];
1869
        } else {
1870
            return [
1871
                'error' => true,
1872
                'message' => $duo_error . $lang->get('duo_error_url'),
1873
                'pwd_attempts' => (int) $sessionPwdAttempts,
1874
                'proceedIdentification' => false,
1875
            ];
1876
        }
1877
    } elseif ($duo_status === 'IN_PROGRESS' && $dataReceived['duo_code'] !== '') {
1878
        try {
1879
            // Check if the Duo code received is valid
1880
            $decoded_token = $duo_client->exchangeAuthorizationCodeFor2FAResult($dataReceived['duo_code'], $username);
1881
        } catch (DuoException $e) {
1882
            return [
1883
                'error' => true,
1884
                'message' => $lang->get('duo_error_decoding'),
1885
                'pwd_attempts' => (int) $sessionPwdAttempts,
1886
                'debug_message' => $e->getMessage(),
1887
                'proceedIdentification' => false,
1888
            ];
1889
        }
1890
        // return the response (which should be the user name)
1891
        if ($decoded_token['preferred_username'] === $username) {
1892
            $session->set('user-duo_status', 'COMPLET');
1893
            $session->set('user-duo_state','');
1894
            $session->set('user-duo_data','');
1895
            $session->set('user-login', $username);
1896
1897
            return [
1898
                'error' => false,
1899
                'message' => '',
1900
                'proceedIdentification' => true,
1901
                'authenticated_username' => $decoded_token['preferred_username']
1902
            ];
1903
        } else {
1904
            // Something wrong, username from the original Duo request is different than the one received now
1905
            $session->set('user-duo_status','');
1906
            $session->set('user-duo_state','');
1907
            $session->set('user-duo_data','');
1908
1909
            return [
1910
                'error' => true,
1911
                'message' => $lang->get('duo_login_mismatch'),
1912
                'pwd_attempts' => (int) $sessionPwdAttempts,
1913
                'proceedIdentification' => false,
1914
            ];
1915
        }
1916
    }
1917
    // If we are here something wrong
1918
    $session->set('user-duo_status','');
1919
    $session->set('user-duo_state','');
1920
    $session->set('user-duo_data','');
1921
    return [
1922
        'error' => true,
1923
        'message' => $lang->get('duo_login_mismatch'),
1924
        'pwd_attempts' => (int) $sessionPwdAttempts,
1925
        'proceedIdentification' => false,
1926
    ];
1927
}
1928
1929
/**
1930
 * Undocumented function.
1931
 *
1932
 * @param string                $passwordClear Password in clear
1933
 * @param array|string          $userInfo      Array of user data
1934
 * @param array|string|resource $dataReceived  Received data
1935
 * @param string                $username      User name
1936
 * @param array                 $SETTINGS      Teampass settings
1937
 *
1938
 * @return bool
1939
 */
1940
function checkCredentials($passwordClear, $userInfo, $dataReceived, $username, $SETTINGS)
1941
{
1942
    // Set to false
1943
    $userPasswordVerified = false;
1944
    // load passwordLib library
1945
    $pwdlib = new PasswordLib();
1946
    // Check if old encryption used
1947
    if (
1948
        crypt($passwordClear, $userInfo['pw']) === $userInfo['pw']
1949
        && empty($userInfo['pw']) === false
1950
    ) {
1951
        $userPasswordVerified = true;
1952
        //update user's password
1953
        $userInfo['pw'] = $pwdlib->createPasswordHash($passwordClear);
1954
        DB::update(
1955
            prefixTable('users'),
1956
            [
1957
                'pw' => $userInfo['pw'],
1958
            ],
1959
            'id=%i',
1960
            $userInfo['id']
1961
        );
1962
    }
1963
    //echo $passwordClear." - ".$userInfo['pw']." - ".$pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw'])." ;; ";
1964
    // check the given password
1965
    if ($userPasswordVerified !== true) {
1966
        if ($pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw']) === true) {
1967
            $userPasswordVerified = true;
1968
        } else {
1969
            // 2.1.27.24 - manage passwords
1970
            if ($pwdlib->verifyPasswordHash(htmlspecialchars_decode($dataReceived['pw']), $userInfo['pw']) === true) {
1971
                // then the auth is correct but needs to be adapted in DB since change of encoding
1972
                $userInfo['pw'] = $pwdlib->createPasswordHash($passwordClear);
1973
                DB::update(
1974
                    prefixTable('users'),
1975
                    [
1976
                        'pw' => $userInfo['pw'],
1977
                    ],
1978
                    'id=%i',
1979
                    $userInfo['id']
1980
                );
1981
                $userPasswordVerified = true;
1982
            } else {
1983
                $userPasswordVerified = false;
1984
                logEvents(
1985
                    $SETTINGS,
1986
                    'failed_auth',
1987
                    'password_is_not_correct',
1988
                    '',
1989
                    '',
1990
                    stripslashes($username)
1991
                );
1992
            }
1993
        }
1994
    }
1995
1996
    return $userPasswordVerified;
1997
}
1998
1999
/**
2000
 * Undocumented function.
2001
 *
2002
 * @param bool   $enabled text1
2003
 * @param string $dbgFile text2
2004
 * @param string $text    text3
2005
 */
2006
function debugIdentify(bool $enabled, string $dbgFile, string $text): void
2007
{
2008
    if ($enabled === true) {
2009
        $fp = fopen($dbgFile, 'a');
2010
        if ($fp !== false) {
2011
            fwrite(
2012
                $fp,
2013
                $text
2014
            );
2015
        }
2016
    }
2017
}
2018
2019
2020
2021
function identifyGetUserCredentials(
2022
    array $SETTINGS,
2023
    string $serverPHPAuthUser,
2024
    string $serverPHPAuthPw,
2025
    string $userPassword,
2026
    string $userLogin
2027
): array
2028
{
2029
    if ((int) $SETTINGS['enable_http_request_login'] === 1
2030
        && $serverPHPAuthUser !== null
2031
        && (int) $SETTINGS['maintenance_mode'] === 1
2032
    ) {
2033
        if (strpos($serverPHPAuthUser, '@') !== false) {
2034
            return [
2035
                'username' => explode('@', $serverPHPAuthUser)[0],
2036
                'passwordClear' => $serverPHPAuthPw
2037
            ];
2038
        }
2039
        
2040
        if (strpos($serverPHPAuthUser, '\\') !== false) {
2041
            return [
2042
                'username' => explode('\\', $serverPHPAuthUser)[1],
2043
                'passwordClear' => $serverPHPAuthPw
2044
            ];
2045
        }
2046
2047
        return [
2048
            'username' => $serverPHPAuthPw,
2049
            'passwordClear' => $serverPHPAuthPw
2050
        ];
2051
    }
2052
    
2053
    return [
2054
        'username' => $userLogin,
2055
        'passwordClear' => $userPassword
2056
    ];
2057
}
2058
2059
2060
class initialChecks {
2061
    // Properties
2062
    public $login;
2063
2064
    // Methods
2065
    public function get_is_too_much_attempts($attempts) {
2066
        if ($attempts > 30) {
2067
            throw new Exception(
2068
                "error" 
2069
            );
2070
        }
2071
    }
2072
2073
    public function get_user_info($login, $enable_ad_user_auto_creation) {
2074
        $data = DB::queryFirstRow(
2075
            'SELECT u.*, a.value AS api_key
2076
            FROM ' . prefixTable('users') . ' AS u
2077
            LEFT JOIN ' . prefixTable('api') . ' AS a ON (u.id = a.user_id)
2078
            WHERE login = %s AND deleted_at IS NULL',
2079
            $login
2080
        );
2081
        
2082
        // User doesn't exist then return error
2083
        // Except if user creation from LDAP is enabled
2084
        if (DB::count() === 0 && $enable_ad_user_auto_creation === false) {
2085
            throw new Exception(
2086
                "error" 
2087
            );
2088
        }
2089
        $data['ldap_user_to_be_created'] = $enable_ad_user_auto_creation === true && DB::count() === 0 ? true : false;
2090
2091
        // ensure user fonction_id is set to false if not existing
2092
        /*if (is_null($data['fonction_id']) === true) {
2093
            $data['fonction_id'] = '';
2094
        }*/
2095
2096
        // Prepare user roles (fonction_id + roles_from_ad_groups)
2097
        // Disable this as this happend repeadetly and is not necessary when working with AD groups
2098
        //$data['fonction_id'] = is_null($data['roles_from_ad_groups']) === true ? $data['fonction_id'] : (empty($data['roles_from_ad_groups']) === true ? $data['fonction_id'] : $data['fonction_id'] . ';' . $data['roles_from_ad_groups']);
2099
2100
        return $data;
2101
    }
2102
2103
    public function get_teampass_in_maintenance_mode($maintenance_mode, $user_admin) {
2104
        if ((int) $maintenance_mode === 1 && (int) $user_admin === 0) {
2105
            throw new Exception(
2106
                "error" 
2107
            );
2108
        }
2109
    }
2110
2111
    public function get_mfa_code_is_set(
2112
        $yubico,
2113
        $ga,
2114
        $duo,
2115
        $admin,
2116
        $adminMfaRequired,
2117
        $mfa,
2118
        $userMfaSelection,
2119
        $userMfaEnabled
2120
    ) {
2121
        if (
2122
            (empty($userMfaSelection) === true &&
2123
            isOneVarOfArrayEqualToValue(
2124
                [
2125
                    (int) $yubico,
2126
                    (int) $ga,
2127
                    (int) $duo
2128
                ],
2129
                1
2130
            ) === true)
2131
            && (((int) $admin !== 1 && $userMfaEnabled === true) || ((int) $adminMfaRequired === 1 && (int) $admin === 1))
2132
            && $mfa === true
2133
        ) {
2134
            throw new Exception(
2135
                "error" 
2136
            );
2137
        }
2138
    }
2139
2140
    public function get_install_folder_is_not_present($admin, $install_folder) {
2141
        if ((int) $admin === 1 && is_dir($install_folder) === true) {
2142
            throw new Exception(
2143
                "error" 
2144
            );
2145
        }
2146
    }
2147
}
2148
2149
2150
/**
2151
 * Permit to get info about user before auth step
2152
 *
2153
 * @param array $SETTINGS
2154
 * @param integer $sessionPwdAttempts
2155
 * @param string $username
2156
 * @param integer $sessionAdmin
2157
 * @param string $sessionUrl
2158
 * @param string $user_2fa_selection
2159
 * @return array
2160
 */
2161
function identifyDoInitialChecks(
2162
    $SETTINGS,
2163
    int $sessionPwdAttempts,
2164
    string $username,
2165
    int $sessionAdmin,
2166
    string $sessionUrl,
2167
    string $user_2fa_selection
2168
): array
2169
{
2170
    $session = SessionManager::getSession();
2171
    
2172
    $checks = new initialChecks();
2173
    $enable_ad_user_auto_creation = isset($SETTINGS['enable_ad_user_auto_creation']) === true && (int) $SETTINGS['enable_ad_user_auto_creation'] === 1 ? true : false;
2174
    // Load superGlobals
2175
    $superGlobal = new SuperGlobal();
0 ignored issues
show
Unused Code introduced by
The assignment to $superGlobal is dead and can be removed.
Loading history...
2176
    $lang = new Language(); 
2177
    
2178
    // Brute force management
2179
    try {
2180
        $checks->get_is_too_much_attempts($sessionPwdAttempts);
2181
    } catch (Exception $e) {
2182
        $session->set('next_possible_pwd_attempts', (time() + 10));
2183
        $session->set('pwd_attempts', 0);
2184
2185
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($username), stripslashes($username));
2186
2187
        return [
2188
            'error' => true,
2189
            'array' => [
2190
                'value' => 'bruteforce_wait',
2191
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2192
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2193
                'pwd_attempts' => 0,
2194
                'error' => true,
2195
                'message' => $lang->get('error_bad_credentials_more_than_3_times'),
2196
            ]
2197
        ];
2198
    }
2199
2200
    // Check if user exists
2201
    try {
2202
        $userInfo = $checks->get_user_info($username, $enable_ad_user_auto_creation);
2203
    } catch (Exception $e) {
2204
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($username), stripslashes($username));
2205
        return [
2206
            'error' => true,
2207
            'array' => [
2208
                'value' => 'user_not_exists',
2209
                'error' => true,
2210
                'message' => $lang->get('error_bad_credentials'),
2211
                'pwd_attempts' => (int) $sessionPwdAttempts,
2212
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2213
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2214
            ]
2215
        ];
2216
    }
2217
    
2218
    // Manage Maintenance mode
2219
    try {
2220
        $checks->get_teampass_in_maintenance_mode(
2221
            $SETTINGS['maintenance_mode'],
2222
            $userInfo['admin']
2223
        );
2224
    } catch (Exception $e) {
2225
        return [
2226
            'error' => true,
2227
            'array' => [
2228
                'value' => '',
2229
                'user_admin' => (int) $userInfo['admin'],
2230
                'initial_url' => '',
2231
                'pwd_attempts' => '',
2232
                'error' => 'maintenance_mode_enabled',
2233
                'message' => '',
2234
            ]
2235
        ];
2236
    }
2237
2238
    // user should use MFA?
2239
    $userInfo['mfa_auth_requested_roles'] = mfa_auth_requested_roles(
2240
        (string) $userInfo['fonction_id'],
2241
        is_null($SETTINGS['mfa_for_roles']) === true ? '' : (string) $SETTINGS['mfa_for_roles']
2242
    );
2243
2244
    // Check if 2FA code is requested
2245
    try {
2246
        $checks->get_mfa_code_is_set(
2247
            $SETTINGS['yubico_authentication'],
2248
            $SETTINGS['google_authentication'],
2249
            $SETTINGS['duo'],
2250
            $userInfo['admin'],
2251
            $SETTINGS['admin_2fa_required'],
2252
            $userInfo['mfa_auth_requested_roles'],
2253
            $user_2fa_selection,
2254
            $userInfo['mfa_enabled']
2255
        );
2256
    } catch (Exception $e) {
2257
        return [
2258
            'error' => true,
2259
            'array' => [
2260
                'value' => '2fa_not_set',
2261
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2262
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2263
                'pwd_attempts' => (int) $sessionPwdAttempts,
2264
                'error' => '2fa_not_set',
2265
                'message' => $lang->get('select_valid_2fa_credentials'),
2266
            ]
2267
        ];
2268
    }
2269
2270
    // If admin user then check if folder install exists
2271
    // if yes then refuse connection
2272
    try {
2273
        $checks->get_install_folder_is_not_present(
2274
            $userInfo['admin'],
2275
            '../install'
2276
        );
2277
    } catch (Exception $e) {
2278
        return [
2279
            'error' => true,
2280
            'array' => [
2281
                'value' => '',
2282
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2283
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2284
                'pwd_attempts' => (int) $sessionPwdAttempts,
2285
                'error' => true,
2286
                'message' => $lang->get('remove_install_folder'),
2287
            ]
2288
        ];
2289
    }
2290
2291
    // Return some usefull information about user
2292
    return [
2293
        'error' => false,
2294
        'user_mfa_mode' => $user_2fa_selection,
2295
        'userInfo' => $userInfo,
2296
    ];
2297
}
2298
2299
function identifyDoLDAPChecks(
2300
    $SETTINGS,
2301
    $userInfo,
2302
    string $username,
2303
    string $passwordClear,
2304
    int $sessionAdmin,
2305
    string $sessionUrl,
2306
    int $sessionPwdAttempts
2307
): array
2308
{
2309
    // Prepare LDAP connection if set up
2310
    if ((int) $SETTINGS['ldap_mode'] === 1
2311
        && $username !== 'admin'
2312
        && ((string) $userInfo['auth_type'] === 'ldap' || $userInfo['ldap_user_to_be_created'] === true)
2313
    ) {
2314
        $retLDAP = authenticateThroughAD(
2315
            $username,
2316
            $userInfo,
2317
            $passwordClear,
2318
            $SETTINGS
2319
        );
2320
        if ($retLDAP['error'] === true) {
2321
            return [
2322
                'error' => true,
2323
                'array' => [
2324
                    'value' => '',
2325
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
2326
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
2327
                    'pwd_attempts' => (int) $sessionPwdAttempts,
2328
                    'error' => true,
2329
                    'message' => "LDAP error: ".$retLDAP['message'],
2330
                ]
2331
            ];
2332
        }
2333
        return [
2334
            'error' => false,
2335
            'retLDAP' => $retLDAP,
2336
            'ldapConnection' => true,
2337
            'userPasswordVerified' => true,
2338
        ];
2339
    }
2340
2341
    // return if no addmin
2342
    return [
2343
        'error' => false,
2344
        'retLDAP' => [],
2345
        'ldapConnection' => false,
2346
        'userPasswordVerified' => false,
2347
    ];
2348
}
2349
2350
2351
function identifyDoMFAChecks(
2352
    $SETTINGS,
2353
    $userInfo,
2354
    $dataReceived,
2355
    $userInitialData,
2356
    string $username
2357
): array
2358
{    
2359
    $session = SessionManager::getSession();
2360
    
2361
    // Load superGlobals
2362
    $superGlobal = new SuperGlobal();
0 ignored issues
show
Unused Code introduced by
The assignment to $superGlobal is dead and can be removed.
Loading history...
2363
    $lang = new Language(); 
2364
    
2365
    switch ($userInitialData['user_mfa_mode']) {
2366
        case 'google':
2367
            $ret = googleMFACheck(
2368
                $username,
2369
                $userInfo,
2370
                $dataReceived,
2371
                $SETTINGS
2372
            );
2373
            if ($ret['error'] !== false) {
2374
                logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2375
                return [
2376
                    'error' => true,
2377
                    'mfaData' => $ret,
2378
                    'mfaQRCodeInfos' => false,
2379
                ];
2380
            }
2381
2382
            return [
2383
                'error' => false,
2384
                'mfaData' => $ret['firstTime'],
2385
                'mfaQRCodeInfos' => $userInitialData['user_mfa_mode'] === 'google'
2386
                && count($ret['firstTime']) > 0 ? true : false,
2387
            ];
2388
2389
        case 'yubico':
2390
            $ret = yubicoMFACheck(
2391
                $dataReceived,
2392
                $userInfo,
2393
                $SETTINGS
2394
            );
2395
            if ($ret['error'] !== false) {
2396
                return [
2397
                    'error' => true,
2398
                    'mfaData' => $ret,
2399
                    'mfaQRCodeInfos' => false,
2400
                ];
2401
            }
2402
            break;
2403
        
2404
        case 'duo':
2405
            // Prepare Duo connection if set up
2406
            $checks = duoMFACheck(
2407
                $username,
2408
                $dataReceived,
2409
                $SETTINGS
2410
            );
2411
2412
            if ($checks['error'] === true) {
2413
                return [
2414
                    'error' => true,
2415
                    'mfaData' => $checks,
2416
                    'mfaQRCodeInfos' => false,
2417
                ];
2418
            }
2419
2420
            // If we are here
2421
            // Do DUO authentication
2422
            $ret = duoMFAPerform(
2423
                $username,
2424
                $dataReceived,
2425
                $checks['pwd_attempts'],
2426
                $checks['saved_state'],
2427
                $checks['duo_status'],
2428
                $SETTINGS
2429
            );
2430
2431
            if ($ret['error'] !== false) {
2432
                logEvents($SETTINGS, 'failed_auth', 'bad_duo_mfa', '', stripslashes($username), stripslashes($username));
2433
                $session->set('user-duo_status','');
2434
                $session->set('user-duo_state','');
2435
                $session->set('user-duo_data','');
2436
                return [
2437
                    'error' => true,
2438
                    'mfaData' => $ret,
2439
                    'mfaQRCodeInfos' => false,
2440
                ];
2441
            } else if ($ret['duo_url_ready'] === true){
2442
                return [
2443
                    'error' => false,
2444
                    'mfaData' => $ret,
2445
                    'duo_url_ready' => true,
2446
                    'mfaQRCodeInfos' => false,
2447
                ];
2448
            } else if ($ret['error'] === false) {
2449
                return [
2450
                    'error' => false,
2451
                    'mfaData' => $ret,
2452
                    'mfaQRCodeInfos' => false,
2453
                ];
2454
            }
2455
            break;
2456
        
2457
        default:
2458
            logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2459
            return [
2460
                'error' => true,
2461
                'mfaData' => ['message' => $lang->get('wrong_mfa_code')],
2462
                'mfaQRCodeInfos' => false,
2463
            ];
2464
    }
2465
2466
    // If something went wrong, let's catch and return an error
2467
    logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2468
    return [
2469
        'error' => true,
2470
        'mfaData' => ['message' => $lang->get('wrong_mfa_code')],
2471
        'mfaQRCodeInfos' => false,
2472
    ];
2473
}
2474