Passed
Pull Request — master (#4432)
by Nils
05:45
created

checkUserPasswordValidity()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 43
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

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