Passed
Pull Request — master (#4469)
by Nils
05:56
created

authenticateThroughAD()   F

Complexity

Conditions 28
Paths 52

Size

Total Lines 160
Code Lines 100

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 28
eloc 100
c 1
b 0
f 0
nc 52
nop 4
dl 0
loc 160
rs 3.3333

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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