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

identifyUser()   F

Complexity

Conditions 117
Paths > 20000

Size

Total Lines 645
Code Lines 440

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 117
eloc 440
nc 189274
nop 2
dl 0
loc 645
rs 0
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
30
use TeampassClasses\SessionManager\SessionManager;
31
use TeampassClasses\Language\Language;
32
use TeampassClasses\PerformChecks\PerformChecks;
33
use LdapRecord\Connection;
34
use LdapRecord\Container;
35
use LdapRecord\Auth\Events\Failed;
36
use TeampassClasses\NestedTree\NestedTree;
37
use PasswordLib\PasswordLib;
38
use Duo\DuoUniversal\Client;
39
use Duo\DuoUniversal\DuoException;
40
use RobThree\Auth\TwoFactorAuth;
41
use TeampassClasses\LdapExtra\LdapExtra;
42
use TeampassClasses\LdapExtra\OpenLdapExtra;
43
use TeampassClasses\LdapExtra\ActiveDirectoryExtra;
44
45
// Load functions
46
require_once 'main.functions.php';
47
48
// init
49
$session = SessionManager::getSession();
50
// TODO : ajouter un check sue l'envoi de la key
51
52
loadClasses('DB');
53
$lang = new Language(); 
54
55
56
// Load config if $SETTINGS not defined
57
try {
58
    include_once __DIR__.'/../includes/config/tp.config.php';
59
} catch (Exception $e) {
60
    throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
61
}
62
63
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true || $SETTINGS['cpassman_dir'] === '.') {
64
    $SETTINGS = [];
65
    $SETTINGS['cpassman_dir'] = '..';
66
}
67
error_log('Identify.php: '.print_r($_POST, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($_POST, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

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

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

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