Passed
Push — master ( a8cf6f...d3295c )
by Nils
08:52 queued 03:52
created

initialChecks   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 83
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 32
c 3
b 0
f 0
dl 0
loc 83
rs 10
wmc 20

5 Methods

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