Passed
Push — master ( d39f52...c923eb )
by Nils
05:04
created

initialChecks::is2faCodeRequired()   B

Complexity

Conditions 8
Paths 2

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 11
nc 2
nop 8
dl 0
loc 25
rs 8.4444
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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