Passed
Push — master ( d851f1...771c94 )
by Nils
05:29
created

shouldUserAuthWithOauth2()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 42
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2496
        error_log(/** @scrutinizer ignore-type */ print_r($userInfo, true));
Loading history...
2497
2498
        // prepapre background tasks for item keys generation  
2499
        handleUserKeys(
2500
            (int) $userInfo['id'],
2501
            (string) $passwordClear,
2502
            (int) (isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : NUMBER_ITEMS_IN_BATCH),
2503
            uniqidReal(20),
2504
            true,
2505
            true,
2506
            true,
2507
            false,
2508
            $lang->get('email_body_user_config_2'),
2509
        );
2510
2511
        // Complete $userInfo
2512
        $userInfo['has_been_created'] = 1;
2513
2514
        error_log("--- USER CREATED ---");
2515
2516
        return [
2517
            'error' => false,
2518
            'retExternalAD' => $userInfo,
2519
            'oauth2Connection' => true,
2520
            'userPasswordVerified' => true,
2521
        ];
2522
    
2523
    } elseif ((int) $SETTINGS['oauth2_enabled'] === 1
2524
        && isset($userInfo['id']) === true && empty($userInfo['id']) === false
2525
    ) {
2526
        // CHeck if user should use oauth2
2527
        $ret = shouldUserAuthWithOauth2(
2528
            $SETTINGS,
2529
            $userInfo,
2530
            $username
2531
        );
2532
        if ($ret['error'] === true) {
2533
            return [
2534
                'error' => true,
2535
                'message' => $ret['message'],
2536
            ];
2537
        }
2538
        
2539
        // Oauth2 user already exists and authenticated
2540
        error_log("--- USER AUTHENTICATED ---");
2541
        $userInfo['has_been_created'] = 0;
2542
        return [
2543
            'error' => false,
2544
            'retExternalAD' => $userInfo,
2545
            'oauth2Connection' => true,
2546
            'userPasswordVerified' => true,
2547
        ];
2548
    }
2549
2550
    // return if no addmin
2551
    return [
2552
        'error' => false,
2553
        'retLDAP' => [],
2554
        'ldapConnection' => false,
2555
        'userPasswordVerified' => false,
2556
    ];
2557
}
2558
2559
2560
function identifyDoMFAChecks(
2561
    $SETTINGS,
2562
    $userInfo,
2563
    $dataReceived,
2564
    $userInitialData,
2565
    string $username
2566
): array
2567
{
2568
    $session = SessionManager::getSession();
2569
    $lang = new Language($session->get('user-language') ?? 'english');
2570
    
2571
    switch ($userInitialData['user_mfa_mode']) {
2572
        case 'google':
2573
            $ret = googleMFACheck(
2574
                $username,
2575
                $userInfo,
2576
                $dataReceived,
2577
                $SETTINGS
2578
            );
2579
            if ($ret['error'] !== false) {
2580
                logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2581
                return [
2582
                    'error' => true,
2583
                    'mfaData' => $ret,
2584
                    'mfaQRCodeInfos' => false,
2585
                ];
2586
            }
2587
2588
            return [
2589
                'error' => false,
2590
                'mfaData' => $ret['firstTime'],
2591
                'mfaQRCodeInfos' => $userInitialData['user_mfa_mode'] === 'google'
2592
                && count($ret['firstTime']) > 0 ? true : false,
2593
            ];
2594
2595
        case 'yubico':
2596
            $ret = yubicoMFACheck(
2597
                $dataReceived,
2598
                $userInfo,
2599
                $SETTINGS
2600
            );
2601
            if ($ret['error'] !== false) {
2602
                return [
2603
                    'error' => true,
2604
                    'mfaData' => $ret,
2605
                    'mfaQRCodeInfos' => false,
2606
                ];
2607
            }
2608
            break;
2609
        
2610
        case 'duo':
2611
            // Prepare Duo connection if set up
2612
            $checks = duoMFACheck(
2613
                $username,
2614
                $dataReceived,
2615
                $SETTINGS
2616
            );
2617
2618
            if ($checks['error'] === true) {
2619
                return [
2620
                    'error' => true,
2621
                    'mfaData' => $checks,
2622
                    'mfaQRCodeInfos' => false,
2623
                ];
2624
            }
2625
2626
            // If we are here
2627
            // Do DUO authentication
2628
            $ret = duoMFAPerform(
2629
                $username,
2630
                $dataReceived,
2631
                $checks['pwd_attempts'],
2632
                $checks['saved_state'],
2633
                $checks['duo_status'],
2634
                $SETTINGS
2635
            );
2636
2637
            if ($ret['error'] !== false) {
2638
                logEvents($SETTINGS, 'failed_auth', 'bad_duo_mfa', '', stripslashes($username), stripslashes($username));
2639
                $session->set('user-duo_status','');
2640
                $session->set('user-duo_state','');
2641
                $session->set('user-duo_data','');
2642
                return [
2643
                    'error' => true,
2644
                    'mfaData' => $ret,
2645
                    'mfaQRCodeInfos' => false,
2646
                ];
2647
            } else if ($ret['duo_url_ready'] === true){
2648
                return [
2649
                    'error' => false,
2650
                    'mfaData' => $ret,
2651
                    'duo_url_ready' => true,
2652
                    'mfaQRCodeInfos' => false,
2653
                ];
2654
            } else if ($ret['error'] === false) {
2655
                return [
2656
                    'error' => false,
2657
                    'mfaData' => $ret,
2658
                    'mfaQRCodeInfos' => false,
2659
                ];
2660
            }
2661
            break;
2662
        
2663
        default:
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
    }
2671
2672
    // If something went wrong, let's catch and return an error
2673
    logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2674
    return [
2675
        'error' => true,
2676
        'mfaData' => ['message' => $lang->get('wrong_mfa_code')],
2677
        'mfaQRCodeInfos' => false,
2678
    ];
2679
}
2680
2681
function identifyDoAzureChecks(
2682
    array $SETTINGS,
2683
    $userInfo,
2684
    string $username
2685
): array
2686
{
2687
    $session = SessionManager::getSession();
2688
    $lang = new Language($session->get('user-language') ?? 'english');
2689
2690
    logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
2691
    return [
2692
        'error' => true,
2693
        'mfaData' => ['message' => $lang->get('wrong_mfa_code')],
2694
        'mfaQRCodeInfos' => false,
2695
    ];
2696
}