Passed
Push — master ( 1b3d9a...c79627 )
by Nils
06:30
created

findTpConfigFile()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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