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

identifyDoInitialChecks()   D

Complexity

Conditions 15
Paths 18

Size

Total Lines 128
Code Lines 89

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 89
nc 18
nop 6
dl 0
loc 128
rs 4.9733
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      identify.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use voku\helper\AntiXSS;
33
use 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
}