Passed
Pull Request — master (#4477)
by
unknown
07:38 queued 01:21
created

createOauth2User()   D

Complexity

Conditions 18
Paths 9

Size

Total Lines 113
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 18
eloc 67
c 5
b 0
f 0
nc 9
nop 5
dl 0
loc 113
rs 4.8666

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