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

authenticateThroughAD()   A

Complexity

Conditions 5
Paths 18

Size

Total Lines 47
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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