Passed
Push — master ( 271d26...5c78a2 )
by Nils
12:33 queued 08:39
created

authenticateThroughAD()   F

Complexity

Conditions 18
Paths 72

Size

Total Lines 166
Code Lines 110

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 18
eloc 110
c 2
b 0
f 0
nc 72
nop 4
dl 0
loc 166
rs 3.8933

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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