Passed
Pull Request — master (#4431)
by
unknown
06:30
created

checkUserPasswordValidity()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 41
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

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