Passed
Push — master ( a7ae84...1f91ec )
by Nils
09:31
created

initialChecks::get_teampass_in_maintenance_mode()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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