Passed
Push — master ( b3a8da...efcd91 )
by Nils
04:39
created

canUserGetLog()   B

Complexity

Conditions 10
Paths 5

Size

Total Lines 44
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 14
c 2
b 0
f 0
nc 5
nop 4
dl 0
loc 44
rs 7.6666

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This 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
 *
15
 * @file      identify.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2022 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
require_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
53
require_once $SETTINGS['cpassman_dir'] . '/includes/config/include.php';
54
require_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
55
56
// If Debug then clean the files
57
if (DEBUGLDAP === true) {
58
    define('DEBUGLDAPFILE', $SETTINGS['path_to_files_folder'] . '/ldap.debug.txt');
59
    $fp = fopen(DEBUGLDAPFILE, 'w');
60
    fclose($fp);
61
}
62
if (DEBUGDUO === true) {
63
    define('DEBUGDUOFILE', $SETTINGS['path_to_files_folder'] . '/duo.debug.txt');
64
    $fp = fopen(DEBUGDUOFILE, 'w');
65
    fclose($fp);
66
}
67
68
// Prepare POST variables
69
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_STRING);
70
$post_login = filter_input(INPUT_POST, 'login', FILTER_SANITIZE_STRING);
71
$post_sig_response = filter_input(INPUT_POST, 'sig_response', FILTER_SANITIZE_STRING);
72
//$post_cardid = filter_input(INPUT_POST, 'cardid', FILTER_SANITIZE_STRING);
73
$post_data = filter_input(INPUT_POST, 'data', FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES);
74
75
// connect to the server
76
if (defined('DB_PASSWD_CLEAR') === false) {
77
    define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
78
}
79
require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
80
if (defined('DB_PASSWD_CLEAR') === false) {
81
    define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
82
}
83
DB::$host = DB_HOST;
84
DB::$user = DB_USER;
85
DB::$password = DB_PASSWD_CLEAR;
86
DB::$dbName = DB_NAME;
87
DB::$port = DB_PORT;
88
DB::$encoding = DB_ENCODING;
89
if ($post_type === 'identify_duo_user') {
90
    //--------
91
    // DUO AUTHENTICATION
92
    //--------
93
    // This step creates the DUO request encrypted key
94
95
    // load library
96
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Authentication/DuoSecurity/Duo.php';
97
    $sig_request = Duo::signRequest(
98
        $SETTINGS['IKEY'],
99
        $SETTINGS['SKEY'],
100
        $SETTINGS['AKEY'],
101
        $post_login
102
    );
103
    if (DEBUGDUO === true) {
104
        debugIdentify(
105
            DEBUGDUO,
106
            DEBUGDUOFILE,
107
            "\n\n-----\n\n" .
108
                'sig request : ' . $post_login . "\n" .
109
                'resp : ' . $sig_request . "\n"
110
        );
111
    }
112
113
    // load csrfprotector
114
    $csrfp_config = include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/csrfp/libs/csrfp.config.php';
115
    // return result
116
    echo '[{"sig_request" : "' . $sig_request . '" , "csrfp_token" : "' . $csrfp_config['CSRFP_TOKEN'] . '" , "csrfp_key" : "' . filter_var($_COOKIE[$csrfp_config['CSRFP_TOKEN']], FILTER_SANITIZE_STRING) . '"}]';
117
// ---
118
    // ---
119
} elseif ($post_type === 'identify_duo_user_check') {
120
    //--------
121
    // DUO AUTHENTICATION
122
    // this step is verifying the response received from the server
123
    //--------
124
125
    // load library
126
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Authentication/DuoSecurity/Duo.php';
127
    $authenticated_username = Duo::verifyResponse(
128
        $SETTINGS['duo_ikey'],
129
        $SETTINGS['duo_skey'],
130
        $SETTINGS['duo_akey'],
131
        $post_sig_response
132
    );
133
134
    // return the response (which should be the user name)
135
    if ($authenticated_username === $post_login) {
136
        // Check if this account exists in Teampass or only in LDAP
137
        if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true) {
138
            // is user in Teampass?
139
            DB::queryfirstrow(
140
                'SELECT id
141
                FROM ' . prefixTable('users') . '
142
                WHERE login = %s',
143
                $post_login
144
            );
145
            if (DB::count() === 0) {
146
                // Ask your administrator to create your account in Teampass
147
                echo '[{"authenticated_username" : "ERR|This user is not yet imported inside Teampass."}]';
148
            }
149
        }
150
151
        echo '[{"authenticated_username" : "' . $authenticated_username . '"}]';
152
    } else {
153
        echo '[{"authenticated_username" : "' . $authenticated_username . '"}]';
154
    }
155
    // ---
156
    // ---
157
} elseif ($post_type === 'identify_user') {
158
    //--------
159
    // NORMAL IDENTICATION STEP
160
    //--------
161
162
    // Ensure Complexity levels are translated
163
    defineComplexity();
164
165
    // Load superGlobals
166
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
167
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
168
    // Prepare GET variables
169
    $sessionPwdAttempts = $superGlobal->get('pwd_attempts', 'SESSION');
170
    // increment counter of login attempts
171
    if ($sessionPwdAttempts === '') {
172
        $sessionPwdAttempts = 1;
173
    } else {
174
        ++$sessionPwdAttempts;
175
    }
176
177
    $superGlobal->put('pwd_attempts', $sessionPwdAttempts, 'SESSION');
178
    // manage brute force
179
    if ($sessionPwdAttempts <= 3) {
180
        $sessionPwdAttempts = 0;
181
182
        // identify the user through Teampass process
183
        identifyUser(
184
            $post_data,
185
            $SETTINGS
186
        );
187
    } elseif (isset($_SESSION['next_possible_pwd_attempts']) && time() > $_SESSION['next_possible_pwd_attempts'] && $sessionPwdAttempts > 3) {
188
        $sessionPwdAttempts = 0;
189
        // identify the user through Teampass process
190
        identifyUser(
191
            $post_data,
192
            $SETTINGS
193
        );
194
    } else {
195
        $_SESSION['next_possible_pwd_attempts'] = time() + 10;
196
        // Encrypt data to return
197
        echo prepareExchangedData(
198
    $SETTINGS['cpassman_dir'],
199
            [
200
                'value' => 'bruteforce_wait',
201
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
202
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
203
                'pwd_attempts' => (int) $sessionPwdAttempts,
204
                'error' => true,
205
                'message' => langHdl('error_bad_credentials_more_than_3_times'),
206
            ],
207
            'encode'
208
        );
209
        return false;
210
    }
211
    // ---
212
    // ---
213
    // ---
214
} elseif ($post_type === 'get2FAMethods') {
215
    //--------
216
    // Get MFA methods
217
    //--------
218
    //
219
220
    // Encrypt data to return
221
    echo prepareExchangedData(
222
    $SETTINGS['cpassman_dir'],
223
        [
224
            'agses' => isKeyExistingAndEqual('agses_authentication_enabled', 1, $SETTINGS) === true ? true : false,
225
            'google' => isKeyExistingAndEqual('google_authentication', 1, $SETTINGS) === true ? true : false,
226
            'yubico' => isKeyExistingAndEqual('yubico_authentication', 1, $SETTINGS) === true ? true : false,
227
            'duo' => isKeyExistingAndEqual('duo', 1, $SETTINGS) === true ? true : false,
228
        ],
229
        'encode'
230
    );
231
    return false;
232
}
233
234
/**
235
 * Complete authentication of user through Teampass
236
 *
237
 * @param string $sentData Credentials
238
 * @param array $SETTINGS Teampass settings
239
 *
240
 * @return bool
241
 */
242
function identifyUser(string $sentData, array $SETTINGS): bool
243
{
244
    // Load config
245
    if (findTpConfigFile() === false) {
246
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
247
    }
248
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
249
    include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
250
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
251
    
252
    header('Content-type: text/html; charset=utf-8');
253
    error_reporting(E_ERROR);
254
255
    // Load AntiXSS
256
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
257
    $antiXss = new voku\helper\AntiXSS();
258
259
    // Load superGlobals
260
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
261
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
262
263
    // Prepare GET variables
264
    $sessionUserLanguage = $superGlobal->get('user_language', 'SESSION');
265
    $sessionKey = $superGlobal->get('key', 'SESSION');
266
    $sessionAdmin = $superGlobal->get('user_admin', 'SESSION');
267
    $sessionPwdAttempts = $superGlobal->get('pwd_attempts', 'SESSION');
268
    $sessionUrl = $superGlobal->get('initial_url', 'SESSION');
269
    $server = [];
270
    $server['PHP_AUTH_USER'] = $superGlobal->get('PHP_AUTH_USER', 'SERVER');
271
    $server['PHP_AUTH_PW'] = $superGlobal->get('PHP_AUTH_PW', 'SERVER');
272
273
    // connect to the server
274
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
275
    DB::$host = DB_HOST;
276
    DB::$user = DB_USER;
277
    DB::$password = defined('DB_PASSWD_CLEAR') === false ? defuseReturnDecrypted(DB_PASSWD, $SETTINGS) : DB_PASSWD_CLEAR;
278
    DB::$dbName = DB_NAME;
279
    DB::$port = DB_PORT;
280
    DB::$encoding = DB_ENCODING;
281
    // User's language loading
282
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $sessionUserLanguage . '.php';
283
    
284
    // decrypt and retreive data in JSON format
285
    if (empty($sessionKey) === true) {
286
        $dataReceived = $sentData;
287
    } else {
288
        $dataReceived = prepareExchangedData(
289
            $SETTINGS['cpassman_dir'],
290
            $sentData,
291
            'decode',
292
            $sessionKey
293
        );
294
        $superGlobal->put('key', $sessionKey, 'SESSION');
295
    }
296
297
    // prepare variables    
298
    $userCredentials = identifyGetUserCredentials(
299
        $SETTINGS,
300
        (string) $server['PHP_AUTH_USER'],
301
        (string) $server['PHP_AUTH_PW'],
302
        (string) filter_var($dataReceived['pw'], FILTER_SANITIZE_STRING),
303
        (string) filter_var($dataReceived['login'], FILTER_SANITIZE_STRING)
304
    );
305
    $username = $userCredentials['username'];
306
    $passwordClear = $userCredentials['passwordClear'];
307
308
    // DO initial checks
309
    $userInitialData = identifyDoInitialChecks(
310
        $SETTINGS,
311
        (int) $sessionPwdAttempts,
312
        (string) $username,
313
        (int) $sessionAdmin,
314
        (string) $sessionUrl,
315
        (string) filter_var($dataReceived['user_2fa_selection'], FILTER_SANITIZE_STRING)
316
    );
317
    if ($userInitialData['error'] === true) {
318
        echo prepareExchangedData(
319
            $SETTINGS['cpassman_dir'],
320
            $userInitialData['array'],
321
            'encode'
322
        );
323
        return false;
324
    }
325
326
    $userInfo = $userInitialData['userInfo'];
327
    $return = '';
328
    $userLdap = identifyDoLDAPChecks(
329
        $SETTINGS,
330
        $userInfo,
331
        (string) $username,
332
        (string) $passwordClear,
333
        (int) $sessionAdmin,
334
        (string) $sessionUrl,
335
        (int) $sessionPwdAttempts
336
    );
337
    if ($userLdap['error'] === true) {
338
        echo prepareExchangedData(
339
            $SETTINGS['cpassman_dir'],
340
            $userLdap['array'],
341
            'encode'
342
        );
343
        return false;
344
    }
345
346
    // Check user and password
347
    if ($userLdap['userPasswordVerified'] === false && (int) checkCredentials($passwordClear, $userInfo, $dataReceived, $username, $SETTINGS) !== 1) {
348
        echo prepareExchangedData(
349
            $SETTINGS['cpassman_dir'],
350
            [
351
                'value' => '',
352
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
353
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
354
                'pwd_attempts' => (int) $sessionPwdAttempts,
355
                'error' => 'user_not_exists2',
356
                'message' => langHdl('error_bad_credentials'),
357
            ],
358
            'encode'
359
        );
360
        return false;
361
    }
362
363
364
    // Check user against MFA method if selected
365
    $userMfa = identifyDoMFAChecks(
366
        $SETTINGS,
367
        $userInfo,
368
        $dataReceived,
369
        $userInitialData,
370
        (string) $username
371
    );
372
    if ($userMfa['error'] === true) {
373
        echo prepareExchangedData(
374
            $SETTINGS['cpassman_dir'],
375
            $userMfa['mfaData'],
376
            'encode'
377
        );
378
        return false;
379
    } elseif ($userMfa['mfaQRCodeInfos'] === true) {
380
        // Case where user has initiated Google Auth
381
        // Return QR code
382
        echo prepareExchangedData(
383
            $SETTINGS['cpassman_dir'],
384
            [
385
                'value' => $userMfa['mfaData']['firstTime']['value'],
386
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
387
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
388
                'pwd_attempts' => (int) $sessionPwdAttempts,
389
                'error' => false,
390
                'message' => $userMfa['mfaData']['firstTime']['message'],
391
                'mfaStatus' => $userMfa['mfaData']['firstTime']['mfaStatus'],
392
            ],
393
            'encode'
394
        );
395
        return false;
396
    }
397
398
    // Can connect if
399
    // 1- no LDAP mode + user enabled + pw ok
400
    // 2- LDAP mode + user enabled + ldap connection ok + user is not admin
401
    // 3- LDAP mode + user enabled + pw ok + usre is admin
402
    // This in order to allow admin by default to connect even if LDAP is activated
403
    if (canUserGetLog(
404
            $SETTINGS,
405
            (int) $userInfo['disabled'],
406
            $username,
407
            $userLdap['ldapConnection']
408
        ) === true
409
    ) {
410
        $superGlobal->put('autoriser', true, 'SESSION');
411
        $superGlobal->put('pwd_attempts', 0, 'SESSION');
412
        /*// Debug
413
                debugIdentify(
414
                    DEBUGDUO,
415
                    DEBUGDUOFILE,
416
                    "User's token: " . $key . "\n"
417
                );*/
418
419
        // Check if any unsuccessfull login tries exist
420
        $attemptsInfos = handleLoginAttempts(
421
            $userInfo['id'],
422
            $userInfo['login'],
423
            $userInfo['last_connexion'],
424
            $username,
425
            $SETTINGS,
426
        );
427
            
428
        // Save account in SESSION
429
        $superGlobal->put('unsuccessfull_login_attempts_list', $attemptsInfos['attemptsList'], 'SESSION', 'user');
430
        $superGlobal->put('unsuccessfull_login_attempts_shown', $attemptsInfos['attemptsCount'] === 0 ? true : false, 'SESSION', 'user');
431
        $superGlobal->put('unsuccessfull_login_attempts_nb', DB::count(), 'SESSION', 'user');
432
        $superGlobal->put('login', stripslashes($username), 'SESSION');
433
        $superGlobal->put('name', empty($userInfo['name']) === false ? stripslashes($userInfo['name']) : '', 'SESSION');
434
        $superGlobal->put('lastname', empty($userInfo['lastname']) === false ? stripslashes($userInfo['lastname']) : '', 'SESSION');
435
        $superGlobal->put('user_id', (int) $userInfo['id'], 'SESSION');
436
        $superGlobal->put('user_pwd', $passwordClear, 'SESSION');
437
        $superGlobal->put('admin', $userInfo['admin'], 'SESSION');
438
        $superGlobal->put('user_manager', $userInfo['gestionnaire'], 'SESSION');
439
        $superGlobal->put('user_can_manage_all_users', $userInfo['can_manage_all_users'], 'SESSION');
440
        $superGlobal->put('user_read_only', $userInfo['read_only'], 'SESSION');
441
        $superGlobal->put('last_pw_change', (int) $userInfo['last_pw_change'], 'SESSION');
442
        $superGlobal->put('last_pw', $userInfo['last_pw'], 'SESSION');
443
        $superGlobal->put('can_create_root_folder', $userInfo['can_create_root_folder'], 'SESSION');
444
        $superGlobal->put('personal_folder', $userInfo['personal_folder'], 'SESSION');
445
        $superGlobal->put('user_language', $userInfo['user_language'], 'SESSION');
446
        $superGlobal->put('user_email', $userInfo['email'], 'SESSION');
447
        $superGlobal->put('user_ga', $userInfo['ga'], 'SESSION');
448
        $superGlobal->put('user_avatar', $userInfo['avatar'], 'SESSION');
449
        $superGlobal->put('user_avatar_thumb', $userInfo['avatar_thumb'], 'SESSION');
450
        $superGlobal->put('user_upgrade_needed', $userInfo['upgrade_needed'], 'SESSION');
451
        $superGlobal->put('user_force_relog', $userInfo['force-relog'], 'SESSION');
452
        $superGlobal->put(
453
            'user_treeloadstrategy',
454
            (isset($userInfo['treeloadstrategy']) === false || empty($userInfo['treeloadstrategy']) === true) ? 'full' : $userInfo['treeloadstrategy'],
455
            'SESSION',
456
            'user'
457
        );
458
        $superGlobal->put('user_treeloadstrategy', $userInfo['treeloadstrategy'], 'SESSION', 'user');
459
        $superGlobal->put('user_agsescardid', $userInfo['agses-usercardid'], 'SESSION', 'user');
460
        $superGlobal->put('user_language', $userInfo['user_language'], 'SESSION', 'user');
461
        $superGlobal->put('user_timezone', $userInfo['usertimezone'], 'SESSION', 'user');
462
        $superGlobal->put('session_duration', $dataReceived['duree_session'] * 60, 'SESSION', 'user');
463
        $superGlobal->put('api-key', $userInfo['user_api_key'], 'SESSION', 'user');
464
        $superGlobal->put('special', $userInfo['special'], 'SESSION', 'user');
465
        $superGlobal->put('auth_type', $userInfo['auth_type'], 'SESSION', 'user');
466
        // manage session expiration
467
        $superGlobal->put('sessionDuration', (int) (time() + ($dataReceived['duree_session'] * 60)), 'SESSION');
468
469
        // check feedback regarding user password validity
470
        $return = checkUserPasswordValidity(
471
            $userInfo,
472
            $superGlobal->get('numDaysBeforePwExpiration', 'SESSION'),
473
            $superGlobal->get('last_pw_change', 'SESSION'),
474
            $SETTINGS
475
        );
476
        $superGlobal->put('validite_pw', $return['validite_pw'], 'SESSION');
477
        $superGlobal->put('last_pw_change', $return['last_pw_change'], 'SESSION');
478
        $superGlobal->put('numDaysBeforePwExpiration', $return['numDaysBeforePwExpiration'], 'SESSION');
479
        $superGlobal->put('user_force_relog', $return['user_force_relog'], 'SESSION');
480
481
482
        $superGlobal->put(
483
            'last_connection',
484
            empty($userInfo['last_connexion']) === false ? (int) $userInfo['last_connexion'] : (int) time(),
485
            'SESSION'
486
        );
487
        
488
        $superGlobal->put(
489
            'latest_items',
490
            empty($userInfo['latest_items']) === false ? explode(';', $userInfo['latest_items']) : [],
491
            'SESSION'
492
        );
493
        
494
        $superGlobal->put(
495
            'favourites',
496
            empty($userInfo['favourites']) === false ? explode(';', $userInfo['favourites']) : [],
497
            'SESSION'
498
        );
499
        
500
        $superGlobal->put(
501
            'groupes_visibles',
502
            empty($userInfo['groupes_visibles']) === false ? explode(';', $userInfo['groupes_visibles']) : [],
503
            'SESSION'
504
        );
505
        
506
        $superGlobal->put(
507
            'no_access_folders',
508
            empty($userInfo['groupes_interdits']) === false ? explode(';', $userInfo['groupes_interdits']) : [],
509
            'SESSION'
510
        );
511
        
512
        // User's roles
513
        if (strpos($userInfo['fonction_id'] !== NULL ? (string) $userInfo['fonction_id'] : '', ',') !== -1) {
514
            // Convert , to ;
515
            $userInfo['fonction_id'] = str_replace(',', ';', (string) $userInfo['fonction_id']);
516
            DB::update(
517
                prefixTable('users'),
518
                [
519
                    'fonction_id' => $userInfo['fonction_id'],
520
                ],
521
                'id = %i',
522
                $superGlobal->get('user_id', 'SESSION')
523
            );
524
        }
525
        $superGlobal->put('fonction_id', $userInfo['fonction_id'], 'SESSION');
526
        $superGlobal->put('user_roles', array_filter(explode(';', $userInfo['fonction_id'])), 'SESSION');
527
        // build array of roles
528
        $superGlobal->put('user_pw_complexity', 0, 'SESSION');
529
        $superGlobal->put('arr_roles', [], 'SESSION');
530
        if (count($superGlobal->get('user_roles', 'SESSION')) > 0) {
531
            $rolesList = DB::query(
532
                'SELECT id, title, complexity
533
                FROM ' . prefixTable('roles_title') . '
534
                WHERE id IN %li',
535
                $superGlobal->get('user_roles', 'SESSION')
536
            );
537
            foreach ($rolesList as $role) {
538
                $superGlobal->put(
539
                    $role['id'],
540
                    [
541
                        'id' => $role['id'],
542
                        'title' => $role['title'],
543
                    ],
544
                    'SESSION',
545
                    'arr_roles'
546
                );
547
                // get highest complexity
548
                if (intval($superGlobal->get('user_pw_complexity', 'SESSION')) < intval($role['complexity'])) {
549
                    $superGlobal->put('user_pw_complexity', $role['complexity'], 'SESSION');
550
                }
551
            }
552
        }
553
554
        // build complete array of roles
555
        $superGlobal->put('arr_roles_full', [], 'SESSION');
556
        $rows = DB::query('SELECT id, title FROM ' . prefixTable('roles_title') . ' ORDER BY title ASC');
557
        foreach ($rows as $record) {
558
            $superGlobal->put(
559
                $record['id'],
560
                [
561
                    'id' => $record['id'],
562
                    'title' => $record['title'],
563
                ],
564
                'SESSION',
565
                'arr_roles_full'
566
            );
567
        }
568
        // Set some settings
569
        $SETTINGS['update_needed'] = '';
570
571
        // User signature keys
572
        $returnKeys = prepareUserEncryptionKeys($userInfo, $passwordClear);        
573
        $superGlobal->put('public_key', $returnKeys['public_key'], 'SESSION', 'user');
574
        $superGlobal->put('private_key', $returnKeys['private_key_clear'], 'SESSION', 'user');
575
576
        // Update table
577
        DB::update(
578
            prefixTable('users'),
579
            array_merge(
580
                [
581
                    'key_tempo' => $superGlobal->get('key', 'SESSION'),
582
                    'last_connexion' => time(),
583
                    'timestamp' => time(),
584
                    'disabled' => 0,
585
                    'no_bad_attempts' => 0,
586
                    'session_end' => $superGlobal->get('sessionDuration', 'SESSION'),
587
                    'user_ip' => $dataReceived['client'],
588
                ],
589
                $returnKeys['update_keys_in_db']
590
            ),
591
            'id=%i',
592
            $userInfo['id']
593
        );
594
        
595
        // Get user's rights
596
        if ($userLdap['user_initial_creation_through_ldap'] !== false) {
597
            // is new LDAP user. Show only his personal folder
598
            if ($SETTINGS['enable_pf_feature'] === '1') {
599
                $superGlobal->put('personal_visible_groups', [$userInfo['id']], 'SESSION');
600
                $superGlobal->put('personal_folders', [$userInfo['id']], 'SESSION');
601
            } else {
602
                $superGlobal->put('personal_visible_groups', [], 'SESSION');
603
                $superGlobal->put('personal_folders', [], 'SESSION');
604
            }
605
            $superGlobal->put('all_non_personal_folders', [], 'SESSION');
606
            $superGlobal->put('groupes_visibles', [], 'SESSION');
607
            $superGlobal->put('read_only_folders', [], 'SESSION');
608
            $superGlobal->put('list_folders_limited', '', 'SESSION');
609
            $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
610
            $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
611
            $superGlobal->put('nb_folders', 1, 'SESSION');
612
            $superGlobal->put('nb_roles', 0, 'SESSION');
613
        } else {
614
            identifyUserRights(
615
                $userInfo['groupes_visibles'],
616
                $superGlobal->get('no_access_folders', 'SESSION'),
617
                $userInfo['admin'],
618
                $userInfo['fonction_id'],
619
                $SETTINGS
620
            );
621
        }
622
        // Get some more elements
623
        $superGlobal->put('screenHeight', $dataReceived['screenHeight'], 'SESSION');
624
        // Get last seen items
625
        $superGlobal->put('latest_items_tab', [], 'SESSION');
626
        $superGlobal->put('nb_roles', 0, 'SESSION');
627
        foreach ($superGlobal->get('latest_items', 'SESSION') as $item) {
628
            if (! empty($item)) {
629
                $dataLastItems = DB::queryFirstRow(
630
                    'SELECT id,label,id_tree
631
                    FROM ' . prefixTable('items') . '
632
                    WHERE id=%i',
633
                    $item
634
                );
635
                $superGlobal->put(
636
                    $item,
637
                    [
638
                        'id' => $item,
639
                        'label' => $dataLastItems['label'],
640
                        'url' => 'index.php?page=items&amp;group=' . $dataLastItems['id_tree'] . '&amp;id=' . $item,
641
                    ],
642
                    'SESSION',
643
                    'latest_items_tab'
644
                );
645
            }
646
        }
647
        // send back the random key
648
        $return = $dataReceived['randomstring'];
649
        // Send email
650
        if (
651
            isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true
652
            && (int) $sessionAdmin !== 1
653
        ) {
654
            // get all Admin users
655
            $receivers = '';
656
            $rows = DB::query('SELECT email FROM ' . prefixTable('users') . " WHERE admin = %i and email != ''", 1);
657
            foreach ($rows as $record) {
658
                if (empty($receivers)) {
659
                    $receivers = $record['email'];
660
                } else {
661
                    $receivers = ',' . $record['email'];
662
                }
663
            }
664
            // Add email to table
665
            DB::insert(
666
                prefixTable('emails'),
667
                [
668
                    'timestamp' => time(),
669
                    'subject' => langHdl('email_subject_on_user_login'),
670
                    'body' => str_replace(
671
                        [
672
                            '#tp_user#',
673
                            '#tp_date#',
674
                            '#tp_time#',
675
                        ],
676
                        [
677
                            ' ' . $superGlobal->get('login', 'SESSION') . ' (IP: ' . getClientIpServer() . ')',
678
                            date($SETTINGS['date_format'], (int) $superGlobal->get('last_connection', 'SESSION')),
679
                            date($SETTINGS['time_format'], (int) $superGlobal->get('last_connection', 'SESSION')),
680
                        ],
681
                        langHdl('email_body_on_user_login')
682
                    ),
683
                    'receivers' => $receivers,
684
                    'status' => 'not_sent',
685
                ]
686
            );
687
        }
688
689
        // Ensure Complexity levels are translated
690
        defineComplexity();
691
692
        echo prepareExchangedData(
693
            $SETTINGS['cpassman_dir'],
694
            [
695
                'value' => $return,
696
                'user_id' => $superGlobal->get('user_id', 'SESSION') !== null ? (int) $superGlobal->get('user_id', 'SESSION') : '',
697
                'user_admin' => $superGlobal->get('admin', 'SESSION') !== null ? (int) $superGlobal->get('admin', 'SESSION') : 0,
698
                'initial_url' => $antiXss->xss_clean($sessionUrl),
699
                'pwd_attempts' => 0,
700
                'error' => false,
701
                'message' => $superGlobal->get('user_upgrade_needed', 'SESSION', 'user') !== null && (int) $superGlobal->get('user_upgrade_needed', 'SESSION', 'user') === 1 ? 'ask_for_otc' : '',
702
                'first_connection' => $superGlobal->get('validite_pw', 'SESSION') === false ? true : false,
703
                'password_complexity' => TP_PW_COMPLEXITY[$superGlobal->get('user_pw_complexity', 'SESSION')][1],
704
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
705
                'private_key_conform' => $superGlobal->get('user_id', 'SESSION') !== null
706
                    && empty($superGlobal->get('private_key', 'SESSION', 'user')) === false
707
                    && $superGlobal->get('private_key', 'SESSION', 'user') !== 'none' ? true : false,
708
                'session_key' => $superGlobal->get('key', 'SESSION'),
709
                'can_create_root_folder' => $superGlobal->get('can_create_root_folder', 'SESSION') !== null ? (int) $superGlobal->get('can_create_root_folder', 'SESSION') : '',
710
                'shown_warning_unsuccessful_login' => $superGlobal->get('unsuccessfull_login_attempts_shown', 'SESSION', 'user'),
711
                'nb_unsuccessful_logins' => $superGlobal->get('unsuccessfull_login_attempts_nb', 'SESSION', 'user'),
712
                'upgrade_needed' => isset($userInfo['upgrade_needed']) === true ? (int) $userInfo['upgrade_needed'] : 0,
713
                'special' => isset($userInfo['special']) === true ? (int) $userInfo['special'] : 0,
714
            ],
715
            'encode'
716
        );
717
    
718
        return true;
719
720
    } elseif ((int) $userInfo['disabled'] === 1) {
721
        // User and password is okay but account is locked
722
        echo prepareExchangedData(
723
            $SETTINGS['cpassman_dir'],
724
            [
725
                'value' => $return,
726
                'user_id' => $superGlobal->get('user_id', 'SESSION') !== null ? (int) $superGlobal->get('user_id', 'SESSION') : '',
727
                'user_admin' => $superGlobal->get('admin', 'SESSION') !== null ? (int) $superGlobal->get('admin', 'SESSION') : 0,
728
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
729
                'pwd_attempts' => 0,
730
                'error' => 'user_is_locked',
731
                'message' => langHdl('account_is_locked'),
732
                'first_connection' => $superGlobal->get('validite_pw', 'SESSION') === false ? true : false,
733
                'password_complexity' => TP_PW_COMPLEXITY[$superGlobal->get('user_pw_complexity', 'SESSION')][1],
734
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
735
                'private_key_conform' => $superGlobal->get('private_key', 'SESSION', 'user') !== null
736
                    && empty($superGlobal->get('private_key', 'SESSION', 'user')) === false
737
                    && $superGlobal->get('private_key', 'SESSION', 'user') !== 'none' ? true : false,
738
                'session_key' => $superGlobal->get('key', 'SESSION'),
739
                'can_create_root_folder' => $superGlobal->get('can_create_root_folder', 'SESSION') !== null ? (int) $superGlobal->get('can_create_root_folder', 'SESSION') : '',
740
                'shown_warning_unsuccessful_login' => $superGlobal->get('unsuccessfull_login_attempts_shown', 'SESSION', 'user'),
741
                'nb_unsuccessful_logins' => $superGlobal->get('unsuccessfull_login_attempts_nb', 'SESSION', 'user'),
742
            ],
743
            'encode'
744
        );
745
        return false;
746
    }
747
748
    // DEFAULT CASE
749
    // User exists in the DB but Password is false
750
    // check if user is locked
751
    if (isUserLocked(
752
            (int) $userInfo['no_bad_attempts'],
753
            $userInfo['id'],
754
            $username,
755
            $superGlobal->get('key', 'SESSION'),
756
            $SETTINGS
757
        ) === true
758
    ) {
759
        echo prepareExchangedData(
760
            $SETTINGS['cpassman_dir'],
761
            [
762
                'value' => $return,
763
                'user_id' => $superGlobal->get('user_id', 'SESSION') !== null ? (int) $superGlobal->get('user_id', 'SESSION') : '',
764
                'user_admin' => $superGlobal->get('admin', 'SESSION') !== null ? (int) $superGlobal->get('admin', 'SESSION') : 0,
765
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
766
                'pwd_attempts' => 0,
767
                'error' => 'user_is_locked',
768
                'message' => langHdl('account_is_locked'),
769
                'first_connection' => $superGlobal->get('validite_pw', 'SESSION') === false ? true : false,
770
                'password_complexity' => TP_PW_COMPLEXITY[$superGlobal->get('user_pw_complexity', 'SESSION')][1],
771
                'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
772
                'private_key_conform' => $superGlobal->get('user_id', 'SESSION') !== null
773
                    && empty($superGlobal->get('private_key', 'SESSION', 'user')) === false
774
                    && $superGlobal->get('private_key', 'SESSION', 'user') !== 'none' ? true : false,
775
                'session_key' => $superGlobal->get('key', 'SESSION'),
776
                'can_create_root_folder' => $superGlobal->get('can_create_root_folder', 'SESSION') !== null ? (int) $superGlobal->get('can_create_root_folder', 'SESSION') : '',
777
                'shown_warning_unsuccessful_login' => $superGlobal->get('unsuccessfull_login_attempts_shown', 'SESSION', 'user'),
778
                'nb_unsuccessful_logins' => $superGlobal->get('unsuccessfull_login_attempts_nb', 'SESSION', 'user'),
779
            ],
780
            'encode'
781
        );
782
        return false;
783
    }
784
    
785
    echo prepareExchangedData(
786
        $SETTINGS['cpassman_dir'],
787
        [
788
            'value' => $return,
789
            'user_id' => $superGlobal->get('user_id', 'SESSION') !== null ? (int) $superGlobal->get('user_id', 'SESSION') : '',
790
            'user_admin' => $superGlobal->get('admin', 'SESSION') !== null ? (int) $superGlobal->get('admin', 'SESSION') : 0,
791
            'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
792
            'pwd_attempts' => (int) $sessionPwdAttempts,
793
            'error' => 'user_not_exists3',
794
            'message' => langHdl('error_bad_credentials'),
795
            'first_connection' => $superGlobal->get('validite_pw', 'SESSION') === false ? true : false,
796
            'password_complexity' => TP_PW_COMPLEXITY[$superGlobal->get('user_pw_complexity', 'SESSION')][1],
797
            'password_change_expected' => $userInfo['special'] === 'password_change_expected' ? true : false,
798
            'private_key_conform' => $superGlobal->get('user_id', 'SESSION') !== null
799
                    && empty($superGlobal->get('private_key', 'SESSION', 'user')) === false
800
                    && $superGlobal->get('private_key', 'SESSION', 'user') !== 'none' ? true : false,
801
            'session_key' => $superGlobal->get('key', 'SESSION'),
802
            'can_create_root_folder' => $superGlobal->get('can_create_root_folder', 'SESSION') !== null ? (int) $superGlobal->get('can_create_root_folder', 'SESSION') : '',
803
            'shown_warning_unsuccessful_login' => $superGlobal->get('unsuccessfull_login_attempts_shown', 'SESSION', 'user'),
804
            'nb_unsuccessful_logins' => $superGlobal->get('unsuccessfull_login_attempts_nb', 'SESSION', 'user'),
805
        ],
806
        'encode'
807
    );
808
    return false;
809
}
810
811
/**
812
 * Check if any unsuccessfull login tries exist
813
 *
814
 * @param int       $userInfoId
815
 * @param string    $userInfoLogin
816
 * @param string    $userInfoLastConnection
817
 * @param string    $username
818
 * @param array     $SETTINGS
819
 * @return array
820
 */
821
function handleLoginAttempts(
822
    $userInfoId,
823
    $userInfoLogin,
824
    $userInfoLastConnection,
825
    $username,
826
    $SETTINGS,
827
) : array
828
{
829
    $rows = DB::query(
830
        'SELECT date
831
        FROM ' . prefixTable('log_system') . "
832
        WHERE field_1 = %s
833
        AND type = 'failed_auth'
834
        AND label = 'password_is_not_correct'
835
        AND date >= %s AND date < %s",
836
        $userInfoLogin,
837
        $userInfoLastConnection,
838
        time()
839
    );
840
    $arrAttempts = [];
841
    if (DB::count() > 0) {
842
        foreach ($rows as $record) {
843
            array_push(
844
                $arrAttempts,
845
                date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['date'])
846
            );
847
        }
848
    }
849
    
850
851
    // Log into DB the user's connection
852
    if (isKeyExistingAndEqual('log_connections', 1, $SETTINGS) === true) {
853
        logEvents($SETTINGS, 'user_connection', 'connection', (string) $userInfoId, stripslashes($username));
854
    }
855
856
    return [
857
        'attemptsList' => $arrAttempts,
858
        'attemptsCount' => DB::count(),
859
    ];
860
}
861
862
/**
863
 * Permits to load config file
864
 *
865
 * @return boolean
866
 */
867
function findTpConfigFile() : bool
868
{
869
    if (file_exists('../includes/config/tp.config.php')) {
870
        include_once '../includes/config/tp.config.php';
871
        return true;
872
    } elseif (file_exists('./includes/config/tp.config.php')) {
873
        include_once './includes/config/tp.config.php';
874
    } elseif (file_exists('../../includes/config/tp.config.php')) {
875
        include_once '../../includes/config/tp.config.php';
876
    } elseif (file_exists('../../../includes/config/tp.config.php')) {
877
        include_once '../../../includes/config/tp.config.php';
878
    }
879
    return false;
880
}
881
882
/**
883
 * Can you user get logged into main page
884
 *
885
 * @param array     $SETTINGS
886
 * @param int       $userInfoDisabled
887
 * @param string    $username
888
 * @param bool      $ldapConnection
889
 *
890
 * @return boolean
891
 */
892
function canUserGetLog(
893
    $SETTINGS,
894
    $userInfoDisabled,
895
    $username,
896
    $ldapConnection
897
) : bool
898
{
899
    include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
900
901
    if ((int) $userInfoDisabled === 1) {
902
        return false;
903
    }
904
905
    if (isKeyExistingAndEqual('ldap_mode', 0, $SETTINGS) === true) {
906
        return true;
907
    }
908
    
909
    if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true 
910
        && (
911
            ($ldapConnection === true && $username !== 'admin')
912
            || $username === 'admin')
913
    ) {
914
        return true;
915
    }
916
917
    if (isKeyExistingAndEqual('ldap_and_local_authentication', 1, $SETTINGS) === true
918
        && isset($SETTINGS['ldap_mode']) === true && in_array($SETTINGS['ldap_mode'], ['1', '2']) === true
919
    ) {
920
        return true;
921
    }
922
    /*
923
    if (
924
        (isKeyExistingAndEqual('ldap_mode', 0, $SETTINGS) === true && (int) $userInfoDisabled === 0)
925
        || (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true && $ldapConnection === true && (int) $userInfoDisabled === 0 && $username !== 'admin')
926
        || (isKeyExistingAndEqual('ldap_mode', 2, $SETTINGS) === true && $ldapConnection === true && (int) $userInfoDisabled === 0 && $username !== 'admin')
927
        || (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true && $username === 'admin' && (int) $userInfoDisabled === 0)
928
        || (isKeyExistingAndEqual('ldap_and_local_authentication', 1, $SETTINGS) === true
929
            && isset($SETTINGS['ldap_mode']) === true && in_array($SETTINGS['ldap_mode'], ['1', '2']) === true
930
            && (int) $userInfoDisabled === 0)
931
    ) {
932
        return true;
933
    }
934
    */
935
    return false;
936
}
937
938
/**
939
 * Manages if user is locked or not
940
 *
941
 * @param int       $nbAttempts
942
 * @param int       $userId
943
 * @param string    $username
944
 * @param string    $key
945
 * @param array     $SETTINGS
946
 *
947
 * @return boolean
948
 */
949
function isUserLocked(
950
    $nbAttempts,
951
    $userId,
952
    $username,
953
    $key,
954
    $SETTINGS
955
) : bool 
956
{
957
    $userIsLocked = false;
958
    $nbAttempts++;
959
    if (
960
        (int) $SETTINGS['nb_bad_authentication'] > 0
961
        && (int) $SETTINGS['nb_bad_authentication'] < $nbAttempts
962
    ) {
963
        // User is now locked as too many attempts
964
        $userIsLocked = true;
965
966
        // log it
967
        if (isKeyExistingAndEqual('log_connections', 1, $SETTINGS) === true) {
968
            logEvents($SETTINGS, 'user_locked', 'connection', (string) $userId, stripslashes($username));
969
        }
970
    }
971
    
972
    DB::update(
973
        prefixTable('users'),
974
        [
975
            'key_tempo' => $key,
976
            'disabled' => $userIsLocked,
977
            'no_bad_attempts' => $nbAttempts,
978
        ],
979
        'id=%i',
980
        $userId
981
    );
982
983
    return $userIsLocked;
984
}
985
986
987
/**
988
 * 
989
 * Prepare user keys
990
 * 
991
 * @param array $userInfo   User account information
992
 * @param string $passwordClear
993
 *
994
 * @return array
995
 */
996
function prepareUserEncryptionKeys($userInfo, $passwordClear) : array
997
{
998
    if (is_null($userInfo['private_key']) === true || empty($userInfo['private_key']) === true || $userInfo['private_key'] === 'none') {
999
        // No keys have been generated yet
1000
        // Create them
1001
        $userKeys = generateUserKeys($passwordClear);
1002
1003
        return [
1004
            'public_key' => $userKeys['public_key'],
1005
            'private_key_clear' => $userKeys['private_key_clear'],
1006
            'update_keys_in_db' => [
1007
                'public_key' => $userKeys['public_key'],
1008
                'private_key' => $userKeys['private_key'],
1009
            ],
1010
        ];
1011
    } 
1012
    
1013
    if ($userInfo['special'] === 'generate-keys') {
1014
        return [
1015
            'public_key' => $userInfo['public_key'],
1016
            'private_key_clear' => '',
1017
            'update_keys_in_db' => [],
1018
        ];
1019
    }
1020
    
1021
    // Don't perform this in case of special login action
1022
    if ($userInfo['special'] === 'otc_is_required_on_next_login' || $userInfo['special'] === 'user_added_from_ldap') {
1023
        return [
1024
            'public_key' => $userInfo['public_key'],
1025
            'private_key_clear' => '',
1026
            'update_keys_in_db' => [],
1027
        ];
1028
    }
1029
    
1030
    // Uncrypt private key
1031
    return [
1032
        'public_key' => $userInfo['public_key'],
1033
        'private_key_clear' => decryptPrivateKey($passwordClear, $userInfo['private_key']),
1034
        'update_keys_in_db' => [],
1035
    ];
1036
}
1037
1038
1039
/**
1040
 * CHECK PASSWORD VALIDITY
1041
 * Don't take into consideration if LDAP in use
1042
 * 
1043
 * @param array $userInfo                       User account information
1044
 * @param int $numDaysBeforePwExpiration
1045
 * @param int $lastPwChange
1046
 * @param array $SETTINGS                       Teampass settings
1047
 *
1048
 * @return array
1049
 */
1050
function checkUserPasswordValidity($userInfo, $numDaysBeforePwExpiration, $lastPwChange, $SETTINGS)
1051
{
1052
    if (isKeyExistingAndEqual('ldap_mode', 1, $SETTINGS) === true) {
1053
        return [
1054
            'validite_pw' => true,
1055
            'last_pw_change' => true,
1056
            'user_force_relog' => '',
1057
            'numDaysBeforePwExpiration' => '',
1058
        ];
1059
    }
1060
1061
    if (isset($userInfo['last_pw_change']) === true) {
1062
        if ((int) $SETTINGS['pw_life_duration'] === 0) {
1063
            return [
1064
                'validite_pw' => true,
1065
                'last_pw_change' => '',
1066
                'user_force_relog' => 'infinite',
1067
                'numDaysBeforePwExpiration' => '',
1068
            ];
1069
        }
1070
        
1071
        return [
1072
            'validite_pw' => $numDaysBeforePwExpiration <= 0 ? false : true,
1073
            'last_pw_change' => '',
1074
            'user_force_relog' => 'infinite',
1075
            'numDaysBeforePwExpiration' => $SETTINGS['pw_life_duration'] - round(
1076
                (mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')) - $lastPwChange) / (24 * 60 * 60)),
1077
        ];
1078
    } else {
1079
        return [
1080
            'validite_pw' => false,
1081
            'last_pw_change' => '',
1082
            'user_force_relog' => '',
1083
            'numDaysBeforePwExpiration' => '',
1084
        ];
1085
    }
1086
}
1087
1088
1089
/**
1090
 * Authenticate a user through AD.
1091
 *
1092
 * @param string $username      Username
1093
 * @param array $userInfo       User account information
1094
 * @param string $passwordClear Password
1095
 * @param array $SETTINGS       Teampass settings
1096
 *
1097
 * @return array
1098
 */
1099
function authenticateThroughAD(string $username, array $userInfo, string $passwordClear, array $SETTINGS): array
1100
{
1101
    // Load expected libraries
1102
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Illuminate/Contracts/Auth/Authenticatable.php';
1103
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/EnumeratesValues.php';
1104
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
1105
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/helpers.php';
1106
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
1107
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Contracts/Support/Jsonable.php';
1108
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Contracts/Support/Arrayable.php';
1109
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Enumerable.php';
1110
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Collection.php';
1111
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/CarbonTimeZone.php';
1112
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Units.php';
1113
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Week.php';
1114
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Timestamp.php';
1115
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Test.php';
1116
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/ObjectInitialisation.php';
1117
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Serialization.php';
1118
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/IntervalRounding.php';
1119
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Rounding.php';
1120
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Localization.php';
1121
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Options.php';
1122
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Cast.php';
1123
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Mutability.php';
1124
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Modifiers.php';
1125
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Mixin.php';
1126
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Macro.php';
1127
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Difference.php';
1128
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Creator.php';
1129
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Converter.php';
1130
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Comparison.php';
1131
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Boundaries.php';
1132
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Traits/Date.php';
1133
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/CarbonInterface.php';
1134
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Carbon/Carbon.php';
1135
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
1136
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
1137
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
1138
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/HandlesConnection.php';
1139
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
1140
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
1141
    $ad->register();
1142
1143
    // Build ldap configuration array
1144
    $config = [
1145
        // Mandatory Configuration Options
1146
        'hosts' => [explode(',', $SETTINGS['ldap_hosts'])],
1147
        'base_dn' => $SETTINGS['ldap_bdn'],
1148
        'username' => $SETTINGS['ldap_username'],
1149
        'password' => $SETTINGS['ldap_password'],
1150
1151
        // Optional Configuration Options
1152
        'port' => $SETTINGS['ldap_port'],
1153
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
1154
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
1155
        'version' => 3,
1156
        'timeout' => 5,
1157
        'follow_referrals' => false,
1158
1159
        // Custom LDAP Options
1160
        'options' => [
1161
            // See: http://php.net/ldap_set_option
1162
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
1163
        ],
1164
    ];
1165
    //prepare connection
1166
    $connection = new Connection($config);
1167
    
1168
    try {
1169
        // Connect to LDAP
1170
        $connection->connect();
1171
1172
        // Get user info from AD
1173
        // We want to isolate attribute ldap_user_attribute
1174
        $userADInfos = $connection->query()
1175
            ->where((isset($SETTINGS['ldap_user_attribute']) ===true && empty($SETTINGS['ldap_user_attribute']) === false) ? $SETTINGS['ldap_user_attribute'] : 'distinguishedname', '=', $username)
1176
            ->firstOrFail();
1177
1178
        // Check shadowexpire attribute - if === 1 then user disabled
1179
        if (
1180
            (isset($userADInfos['shadowexpire'][0]) === true && (int) $userADInfos['shadowexpire'][0] === 1)
1181
            ||
1182
            (isset($userADInfos['accountexpires'][0]) === true && (int) $userADInfos['accountexpires'][0] < time())
1183
        ) {
1184
            return [
1185
                'error' => true,
1186
                'message' => langHdl('error_ad_user_expired'),
1187
            ];
1188
        }
1189
1190
        // User auth attempt
1191
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
1192
            $userAuthAttempt = $connection->auth()->attempt(
1193
                $userADInfos[(isset($SETTINGS['ldap_user_dn_attribute']) === true && empty($SETTINGS['ldap_user_dn_attribute']) === false) ? $SETTINGS['ldap_user_dn_attribute'] : 'cn'][0],
1194
                $passwordClear
1195
            );
1196
        } else {
1197
            $userAuthAttempt = $connection->auth()->attempt(
1198
                $userADInfos['dn'],
1199
                $passwordClear
1200
            );
1201
        }
1202
1203
    } catch (\LdapRecord\Auth\BindException $e) {
1204
        $error = $e->getDetailedError();
1205
        return [
1206
            'error' => true,
1207
            'message' => langHdl('error').' : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage(),
1208
1209
        ];
1210
    }
1211
1212
    // User is not auth then return error
1213
    if ($userAuthAttempt === false) {
1214
        return [
1215
            'error' => true,
1216
            'message' => "Error : User could not be authentificated",
1217
        ];
1218
    }
1219
1220
    // load passwordLib library
1221
    $pwdlib = new SplClassLoader('PasswordLib', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1222
    $pwdlib->register();
1223
    $pwdlib = new PasswordLib\PasswordLib();
1224
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
1225
    //If user has never been connected then erase current pwd with the ldap's one
1226
    if (empty($userInfo['pw']) === true) {
1227
        // Password are similar in Teampass and AD
1228
        DB::update(
1229
            prefixTable('users'),
1230
            [
1231
                'pw' => $hashedPassword,
1232
            ],
1233
            'id = %i',
1234
            $userInfo['id']
1235
        );
1236
    } elseif ($userInfo['special'] === 'user_added_from_ldap') {
1237
        // Case where user has been added from LDAP and never being connected to TP
1238
        DB::update(
1239
            prefixTable('users'),
1240
            array(
1241
                'pw' => $hashedPassword,
1242
            ),
1243
            'id = %i',
1244
            $userInfo['id']
1245
        );
1246
    } elseif ($pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw']) === false) {
1247
        // Case where user is auth by LDAP but his password in Teampass is not synchronized
1248
        // For example when user has changed his password in AD.
1249
        // So we need to update it in Teampass and ask for private key re-encryption
1250
        DB::update(
1251
            prefixTable('users'),
1252
            [
1253
                'pw' => $hashedPassword,
1254
                //'special' => 'auth-pwd-change',
1255
            ],
1256
            'id = %i',
1257
            $userInfo['id']
1258
        );
1259
    }
1260
1261
    $userInfo['pw'] = $hashedPassword;
1262
    return [
1263
        'error' => false,
1264
        'message' => '',
1265
    ];
1266
}
1267
1268
/**
1269
 * Undocumented function.
1270
 *
1271
 * @param string|array|resource $dataReceived Received data
1272
 * @param string                $userInfo     Result of query
1273
 * @param array                 $SETTINGS     Teampass settings
1274
 *
1275
 * @return array
1276
 */
1277
function yubicoMFACheck($dataReceived, string $userInfo, array $SETTINGS): array
1278
{
1279
    // Load superGlobals
1280
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1281
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1282
    $sessionAdmin = $superGlobal->get('user_admin', 'SESSION');
1283
    $sessionUrl = $superGlobal->get('initial_url', 'SESSION');
1284
    $sessionPwdAttempts = $superGlobal->get('pwd_attempts', 'SESSION');
1285
    // Init
1286
    $yubico_key = htmlspecialchars_decode($dataReceived['yubico_key']);
1287
    $yubico_user_key = htmlspecialchars_decode($dataReceived['yubico_user_key']);
1288
    $yubico_user_id = htmlspecialchars_decode($dataReceived['yubico_user_id']);
1289
    if (empty($yubico_user_key) === false && empty($yubico_user_id) === false) {
1290
        // save the new yubico in user's account
1291
        DB::update(
1292
            prefixTable('users'),
1293
            [
1294
                'yubico_user_key' => $yubico_user_key,
1295
                'yubico_user_id' => $yubico_user_id,
1296
            ],
1297
            'id=%i',
1298
            $userInfo['id']
1299
        );
1300
    } else {
1301
        // Check existing yubico credentials
1302
        if ($userInfo['yubico_user_key'] === 'none' || $userInfo['yubico_user_id'] === 'none') {
1303
            return [
1304
                'error' => true,
1305
                'value' => '',
1306
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1307
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1308
                'pwd_attempts' => (int) $sessionPwdAttempts,
1309
                'error' => 'no_user_yubico_credentials',
1310
                'message' => '',
1311
                'proceedIdentification' => false,
1312
            ];
1313
        }
1314
        $yubico_user_key = $userInfo['yubico_user_key'];
1315
        $yubico_user_id = $userInfo['yubico_user_id'];
1316
    }
1317
1318
    // Now check yubico validity
1319
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Authentication/Yubico/Yubico.php';
1320
    $yubi = new Auth_Yubico($yubico_user_id, $yubico_user_key);
1321
    $auth = $yubi->verify($yubico_key);
1322
    //, null, null, null, 60
1323
1324
    if (PEAR::isError($auth)) {
1325
        return [
1326
            'error' => true,
1327
            'value' => '',
1328
            'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1329
            'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1330
            'pwd_attempts' => (int) $sessionPwdAttempts,
1331
            'error' => 'bad_user_yubico_credentials',
1332
            'message' => langHdl('yubico_bad_code'),
1333
            'proceedIdentification' => false,
1334
        ];
1335
    }
1336
1337
    return [
1338
        'error' => false,
1339
        'message' => '',
1340
        'proceedIdentification' => true,
1341
    ];
1342
}
1343
1344
/**
1345
 * Undocumented function.
1346
 *
1347
 * @param string $username      User name
1348
 * @param string $passwordClear User password in clear
1349
 * @param string $retLDAP       Received data from LDAP
1350
 * @param array  $SETTINGS      Teampass settings
1351
 *
1352
 * @return array
1353
 */
1354
function ldapCreateUser(string $username, string $passwordClear, string $retLDAP, array $SETTINGS): array
1355
{
1356
    // Generate user keys pair
1357
    $userKeys = generateUserKeys($passwordClear);
1358
    // Insert user in DB
1359
    DB::insert(
1360
        prefixTable('users'),
1361
        [
1362
            'login' => $username,
1363
            'pw' => $retLDAP['hashedPassword'],
1364
            'email' => isset($retLDAP['user_info_from_ad'][0]['mail'][0]) === false ? '' : $retLDAP['user_info_from_ad'][0]['mail'][0],
1365
            'name' => $retLDAP['user_info_from_ad'][0]['givenname'][0],
1366
            'lastname' => $retLDAP['user_info_from_ad'][0]['sn'][0],
1367
            'admin' => '0',
1368
            'gestionnaire' => '0',
1369
            'can_manage_all_users' => '0',
1370
            'personal_folder' => $SETTINGS['enable_pf_feature'] === '1' ? '1' : '0',
1371
            'fonction_id' => (empty($retLDAP['user_info_from_ad'][0]['commonGroupsLdapVsTeampass']) === false ? $retLDAP['user_info_from_ad'][0]['commonGroupsLdapVsTeampass'] . ';' : '') . (isset($SETTINGS['ldap_new_user_role']) === true ? $SETTINGS['ldap_new_user_role'] : '0'),
1372
            'groupes_interdits' => '',
1373
            'groupes_visibles' => '',
1374
            'last_pw_change' => (int) time(),
1375
            'user_language' => $SETTINGS['default_language'],
1376
            'encrypted_psk' => '',
1377
            '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,
1378
            'public_key' => $userKeys['public_key'],
1379
            'private_key' => $userKeys['private_key'],
1380
        ]
1381
    );
1382
    $newUserId = DB::insertId();
1383
    // Create personnal folder
1384
    if (isKeyExistingAndEqual('enable_pf_feature', 1, $SETTINGS) === true) {
1385
        DB::insert(
1386
            prefixTable('nested_tree'),
1387
            [
1388
                'parent_id' => '0',
1389
                'title' => $newUserId,
1390
                'bloquer_creation' => '0',
1391
                'bloquer_modification' => '0',
1392
                'personal_folder' => '1',
1393
            ]
1394
        );
1395
        // Rebuild tree
1396
        $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1397
        $tree->register();
1398
        $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1399
        $tree->rebuild();
1400
    }
1401
1402
    return [
1403
        'error' => false,
1404
        'message' => '',
1405
        'proceedIdentification' => true,
1406
        'user_initial_creation_through_ldap' => true,
1407
    ];
1408
}
1409
1410
/**
1411
 * Undocumented function.
1412
 *
1413
 * @param string                $username     Username
1414
 * @param array                 $userInfo     Result of query
1415
 * @param string|array|resource $dataReceived DataReceived
1416
 * @param array                 $SETTINGS     Teampass settings
1417
 *
1418
 * @return array
1419
 */
1420
function googleMFACheck(string $username, array $userInfo, $dataReceived, array $SETTINGS): array
1421
{
1422
    if (
1423
        isset($dataReceived['GACode']) === true
1424
        && empty($dataReceived['GACode']) === false
1425
    ) {
1426
        // Load superGlobals
1427
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1428
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1429
        $sessionAdmin = $superGlobal->get('user_admin', 'SESSION');
1430
        $sessionUrl = $superGlobal->get('initial_url', 'SESSION');
1431
        $sessionPwdAttempts = $superGlobal->get('pwd_attempts', 'SESSION');
1432
        // load library
1433
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Authentication/TwoFactorAuth/TwoFactorAuth.php';
1434
        // create new instance
1435
        $tfa = new Authentication\TwoFactorAuth\TwoFactorAuth($SETTINGS['ga_website_name']);
1436
        // Init
1437
        $firstTime = [];
1438
        // now check if it is the 1st time the user is using 2FA
1439
        if ($userInfo['ga_temporary_code'] !== 'none' && $userInfo['ga_temporary_code'] !== 'done') {
1440
            if ($userInfo['ga_temporary_code'] !== $dataReceived['GACode']) {
1441
                return [
1442
                    'error' => true,
1443
                    'message' => langHdl('ga_bad_code'),
1444
                    'proceedIdentification' => false,
1445
                    'mfaStatus' => '',
1446
                ];
1447
            }
1448
1449
            // If first time with MFA code
1450
            $proceedIdentification = false;
1451
            $mfaStatus = 'ga_temporary_code_correct';
1452
            $mfaMessage = langHdl('ga_flash_qr_and_login');
1453
            // generate new QR
1454
            $new_2fa_qr = $tfa->getQRCodeImageAsDataUri(
1455
                'Teampass - ' . $username,
1456
                $userInfo['ga']
1457
            );
1458
            // clear temporary code from DB
1459
            DB::update(
1460
                prefixTable('users'),
1461
                [
1462
                    'ga_temporary_code' => 'done',
1463
                ],
1464
                'id=%i',
1465
                $userInfo['id']
1466
            );
1467
            $firstTime = [
1468
                'value' => '<img src="' . $new_2fa_qr . '">',
1469
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : '',
1470
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1471
                'pwd_attempts' => (int) $sessionPwdAttempts,
1472
                'message' => $mfaMessage,
1473
                'mfaStatus' => $mfaStatus,
1474
            ];
1475
        } else {
1476
            // verify the user GA code
1477
            if ($tfa->verifyCode($userInfo['ga'], $dataReceived['GACode'])) {
1478
                $proceedIdentification = true;
1479
            } else {
1480
                return [
1481
                    'error' => true,
1482
                    'message' => langHdl('ga_bad_code'),
1483
                    'proceedIdentification' => false,
1484
                ];
1485
            }
1486
        }
1487
    } else {
1488
        return [
1489
            'error' => true,
1490
            'message' => langHdl('ga_bad_code'),
1491
            'proceedIdentification' => false,
1492
        ];
1493
    }
1494
1495
    return [
1496
        'error' => false,
1497
        'message' => '',
1498
        'proceedIdentification' => $proceedIdentification,
1499
        'firstTime' => $firstTime,
1500
    ];
1501
}
1502
1503
/**
1504
 * Undocumented function.
1505
 *
1506
 * @param string                $passwordClear Password in clear
1507
 * @param array|string          $userInfo      Array of user data
1508
 * @param array|string|resource $dataReceived  Received data
1509
 * @param string                $username      User name
1510
 * @param array                 $SETTINGS      Teampass settings
1511
 *
1512
 * @return bool
1513
 */
1514
function checkCredentials($passwordClear, $userInfo, $dataReceived, $username, $SETTINGS)
1515
{
1516
    // Set to false
1517
    $userPasswordVerified = false;
1518
    // load passwordLib library
1519
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1520
    $pwdlib = new SplClassLoader('PasswordLib', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1521
    $pwdlib->register();
1522
    $pwdlib = new PasswordLib\PasswordLib();
1523
    // Check if old encryption used
1524
    if (
1525
        crypt($passwordClear, $userInfo['pw']) === $userInfo['pw']
1526
        && empty($userInfo['pw']) === false
1527
    ) {
1528
        $userPasswordVerified = true;
1529
        //update user's password
1530
        $userInfo['pw'] = $pwdlib->createPasswordHash($passwordClear);
1531
        DB::update(
1532
            prefixTable('users'),
1533
            [
1534
                'pw' => $userInfo['pw'],
1535
            ],
1536
            'id=%i',
1537
            $userInfo['id']
1538
        );
1539
    }
1540
    //echo $passwordClear." - ".$userInfo['pw']." - ".$pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw'])." ;; ";
1541
    // check the given password
1542
    if ($userPasswordVerified !== true) {
1543
        if ($pwdlib->verifyPasswordHash($passwordClear, $userInfo['pw']) === true) {
1544
            $userPasswordVerified = true;
1545
        } else {
1546
            // 2.1.27.24 - manage passwords
1547
            if ($pwdlib->verifyPasswordHash(htmlspecialchars_decode($dataReceived['pw']), $userInfo['pw']) === true) {
1548
                // then the auth is correct but needs to be adapted in DB since change of encoding
1549
                $userInfo['pw'] = $pwdlib->createPasswordHash($passwordClear);
1550
                DB::update(
1551
                    prefixTable('users'),
1552
                    [
1553
                        'pw' => $userInfo['pw'],
1554
                    ],
1555
                    'id=%i',
1556
                    $userInfo['id']
1557
                );
1558
                $userPasswordVerified = true;
1559
            } else {
1560
                $userPasswordVerified = false;
1561
                logEvents(
1562
                    $SETTINGS,
1563
                    'failed_auth',
1564
                    'password_is_not_correct',
1565
                    '',
1566
                    '',
1567
                    stripslashes($username)
1568
                );
1569
            }
1570
        }
1571
    }
1572
1573
    return $userPasswordVerified;
1574
}
1575
1576
/**
1577
 * Undocumented function.
1578
 *
1579
 * @param bool   $enabled text1
1580
 * @param string $dbgFile text2
1581
 * @param string $text    text3
1582
 */
1583
function debugIdentify(bool $enabled, string $dbgFile, string $text): void
1584
{
1585
    if ($enabled === true) {
1586
        $fp = fopen($dbgFile, 'a');
1587
        if ($fp !== false) {
1588
            fwrite(
1589
                $fp,
1590
                $text
1591
            );
1592
        }
1593
    }
1594
}
1595
1596
1597
1598
function identifyGetUserCredentials(
1599
    array $SETTINGS,
1600
    string $serverPHPAuthUser,
1601
    string $serverPHPAuthPw,
1602
    string $userPassword,
1603
    string $userLogin
1604
): array
1605
{
1606
    if ((int) $SETTINGS['enable_http_request_login'] === 1
1607
        && $serverPHPAuthUser !== null
1608
        && (int) $SETTINGS['maintenance_mode'] === 1
1609
    ) {
1610
        if (strpos($serverPHPAuthUser, '@') !== false) {
1611
            return [
1612
                'username' => explode('@', $serverPHPAuthUser)[0],
1613
                'passwordClear' => $serverPHPAuthPw
1614
            ];
1615
        }
1616
        
1617
        if (strpos($serverPHPAuthUser, '\\') !== false) {
1618
            return [
1619
                'username' => explode('\\', $serverPHPAuthUser)[1],
1620
                'passwordClear' => $serverPHPAuthPw
1621
            ];
1622
        }
1623
1624
        return [
1625
            'username' => $serverPHPAuthPw,
1626
            'passwordClear' => $serverPHPAuthPw
1627
        ];
1628
    }
1629
    
1630
    return [
1631
        'username' => $userLogin,
1632
        'passwordClear' => $userPassword
1633
    ];
1634
}
1635
1636
1637
    
1638
1639
function identifyDoInitialChecks(
1640
    $SETTINGS,
1641
    int $sessionPwdAttempts,
1642
    string $username,
1643
    int $sessionAdmin,
1644
    string $sessionUrl,
1645
    string $user_2fa_selection
1646
): array
1647
{
1648
    // Brute force management
1649
    if ($sessionPwdAttempts > 3) {
1650
        // Load superGlobals
1651
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1652
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1653
        $superGlobal->put('next_possible_pwd_attempts', time() + 10, 'SESSION');
1654
        $superGlobal->put('pwd_attempts', 0, 'SESSION');
1655
1656
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($username), stripslashes($username));
1657
1658
        return [
1659
            'error' => true,
1660
            'array' => [
1661
                'value' => 'bruteforce_wait',
1662
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
1663
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1664
                'pwd_attempts' => 0,
1665
                'error' => true,
1666
                'message' => langHdl('error_bad_credentials_more_than_3_times'),
1667
            ]
1668
        ];
1669
    }
1670
1671
    // Check 2FA
1672
    if ((empty($user_2fa_selection) === true &&
1673
            isOneVarOfArrayEqualToValue(
1674
                [
1675
                    (int) $SETTINGS['yubico_authentication'],
1676
                    (int) $SETTINGS['google_authentication'],
1677
                    (int) $SETTINGS['duo']
1678
                ],
1679
                1
1680
            ) === true)
1681
        && ($username !== 'admin' || ((int) $SETTINGS['admin_2fa_required'] === 1 && $username === 'admin'))
1682
    ) {
1683
        //echo $SETTINGS['admin_2fa_required']." ;; ";
1684
        return [
1685
            'error' => true,
1686
            'array' => [
1687
                'value' => '2fa_not_set',
1688
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
1689
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1690
                'pwd_attempts' => (int) $sessionPwdAttempts,
1691
                'error' => '2fa_not_set',
1692
                'message' => langHdl('2fa_credential_not_correct'),
1693
            ]
1694
        ];
1695
    }
1696
1697
    // Check if user exists
1698
    $userInfo = DB::queryFirstRow(
1699
        'SELECT *
1700
        FROM ' . prefixTable('users') . ' WHERE login=%s',
1701
        $username
1702
    );
1703
    
1704
    // User doesn't exist then stop
1705
    if (DB::count() === 0) {
1706
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($username), stripslashes($username));
1707
1708
        return [
1709
            'error' => true,
1710
            'array' => [
1711
                'error' => 'user_not_exists',
1712
                'message' => langHdl('error_bad_credentials'),
1713
                'pwd_attempts' => (int) $sessionPwdAttempts,
1714
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
1715
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1716
            ]
1717
        ];
1718
    }
1719
1720
    // has user to be auth with mfa?
1721
    $userInfo['fonction_id'] = is_null($userInfo['fonction_id']) === true ? false : $userInfo['fonction_id'];
1722
    
1723
    // user should use MFA?
1724
    $userInfo['mfa_auth_requested'] = mfa_auth_requested(
1725
        (string) $userInfo['fonction_id'],
1726
        is_null($SETTINGS['mfa_for_roles']) === true ? '' : (string) $SETTINGS['mfa_for_roles']
1727
    );
1728
1729
    // Manage Maintenance mode
1730
    if ((int) $SETTINGS['maintenance_mode'] === 1 && (int) $userInfo['admin'] === 0) {
1731
        return [
1732
            'error' => true,
1733
            'array' => [
1734
                'value' => '',
1735
                'user_admin' => (int) $userInfo['admin'],
1736
                'initial_url' => '',
1737
                'pwd_attempts' => '',
1738
                'error' => 'maintenance_mode_enabled',
1739
                'message' => '',
1740
            ]
1741
        ];
1742
    }
1743
1744
    // If admin user then check if folder install exists
1745
    // if yes then refuse connection
1746
    if ((int) $userInfo['admin'] === 1 && is_dir('../install') === true) {
1747
        return [
1748
            'error' => true,
1749
            'array' => [
1750
                'value' => '',
1751
                'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
1752
                'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1753
                'pwd_attempts' => (int) $sessionPwdAttempts,
1754
                'error' => true,
1755
                'message' => langHdl('remove_install_folder'),
1756
            ]
1757
        ];
1758
    }
1759
1760
    // Return some usefull information about user
1761
    return [
1762
        'error' => false,
1763
        'user_mfa_mode' => $user_2fa_selection,
1764
        'userInfo' => $userInfo,
1765
    ];
1766
}
1767
1768
function identifyDoLDAPChecks(
1769
    $SETTINGS,
1770
    $userInfo,
1771
    string $username,
1772
    string $passwordClear,
1773
    int $sessionAdmin,
1774
    string $sessionUrl,
1775
    int $sessionPwdAttempts
1776
): array
1777
{
1778
    // Prepare LDAP connection if set up
1779
    if ((int) $SETTINGS['ldap_mode'] === 1
1780
        && $username !== 'admin'
1781
        && (string) $userInfo['auth_type'] === 'ldap'
1782
    ) {
1783
        $retLDAP = authenticateThroughAD(
1784
            $username,
1785
            $userInfo,
1786
            $passwordClear,
1787
            $SETTINGS
1788
        );
1789
        if ($retLDAP['error'] === true) {
1790
            return [
1791
                'error' => true,
1792
                'array' => [
1793
                    'value' => '',
1794
                    'user_admin' => isset($sessionAdmin) ? (int) $sessionAdmin : 0,
1795
                    'initial_url' => isset($sessionUrl) === true ? $sessionUrl : '',
1796
                    'pwd_attempts' => (int) $sessionPwdAttempts,
1797
                    'error' => true,
1798
                    'message' => "LDAP error: ".$retLDAP['message'],
1799
                ]
1800
            ];
1801
        }
1802
        return [
1803
            'error' => false,
1804
            'retLDAP' => $retLDAP,
1805
            'ldapConnection' => true,
1806
            'userPasswordVerified' => true,
1807
        ];
1808
    }
1809
1810
    // return if no addmin
1811
    return [
1812
        'error' => false,
1813
        'retLDAP' => [],
1814
        'ldapConnection' => false,
1815
        'userPasswordVerified' => false,
1816
    ];
1817
}
1818
1819
1820
function identifyDoMFAChecks(
1821
    $SETTINGS,
1822
    $userInfo,
1823
    $dataReceived,
1824
    $userInitialData,
1825
    string $username
1826
): array
1827
{    
1828
    if (
1829
        // ---------
1830
        // check GA code
1831
        // ---------
1832
        (int) $SETTINGS['google_authentication'] === 1
1833
        && ($username !== 'admin' || ((int) $SETTINGS['admin_2fa_required'] === 1 && $username === 'admin'))
1834
        && $userInitialData['user_mfa_mode'] === 'google'
1835
        && $userInfo['mfa_auth_requested'] === true
1836
    ) {
1837
        $ret = googleMFACheck(
1838
            $username,
1839
            $userInfo,
1840
            $dataReceived,
1841
            $SETTINGS
1842
        );
1843
        if ($ret['error'] !== false) {
1844
            logEvents($SETTINGS, 'failed_auth', 'wrong_mfa_code', '', stripslashes($username), stripslashes($username));
1845
            
1846
            return [
1847
                'error' => true,
1848
                'mfaData' => $ret,
1849
                'mfaQRCodeInfos' => $userInitialData['user_mfa_mode'] === 'google'
1850
                    && count($ret['firstTime']) > 0 ? true : false,
1851
            ];
1852
            // ---
1853
        }
1854
1855
        return [
1856
            'error' => false,
1857
            'mfaData' => $ret,
1858
            'mfaQRCodeInfos' => false,
1859
        ];
1860
1861
        // ---
1862
        // ---
1863
    } else if (
1864
        // ---------
1865
        // Check Yubico
1866
        // ---------
1867
        isKeyExistingAndEqual('yubico_authentication', 1, $SETTINGS) === true
1868
        && ((int) $userInfo['admin'] !== 1 || ((int) $SETTINGS['admin_2fa_required'] === 1 && (int) $userInfo['admin'] === 1))
1869
        && $userInitialData['user_mfa_mode'] === 'yubico'
1870
        && $userInfo['mfa_auth_requested'] === true
1871
    ) {
1872
        $ret = yubicoMFACheck(
1873
            $dataReceived,
1874
            $userInfo,
1875
            $SETTINGS
1876
        );
1877
        if ($ret['error'] !== '') {
1878
            return [
1879
                'error' => true,
1880
                'mfaData' => $ret,
1881
                'mfaQRCodeInfos' => false,
1882
            ];
1883
        }
1884
        
1885
        // ---
1886
        // ---
1887
    }
1888
1889
    return [
1890
        'error' => false,
1891
        'mfaData' => '',
1892
        'mfaQRCodeInfos' => false,
1893
    ];
1894
}
1895