Passed
Push — master ( 6045b4...ba5884 )
by Nils
06:23
created

setUserOnlyPersonalItemsEncryption()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 39
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 62
rs 9.296

How to fix   Long Method   

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 file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.queries.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2025 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use TeampassClasses\PasswordManager\PasswordManager;
33
use TeampassClasses\SessionManager\SessionManager;
34
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
35
use TeampassClasses\Language\Language;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use RobThree\Auth\TwoFactorAuth;
39
use EZimuel\PHPSecureSession;
40
use TeampassClasses\PerformChecks\PerformChecks;
41
use TeampassClasses\ConfigManager\ConfigManager;
42
use TeampassClasses\EmailService\EmailService;
43
use TeampassClasses\EmailService\EmailSettings;
44
45
// Load functions
46
require_once 'main.functions.php';
47
48
loadClasses('DB');
49
$session = SessionManager::getSession();
50
$request = SymfonyRequest::createFromGlobals();
51
$lang = new Language($session->get('user-language') ?? 'english');
52
53
// Load config
54
$configManager = new ConfigManager();
55
$SETTINGS = $configManager->getAllSettings();
56
57
// Do checks
58
// Instantiate the class with posted data
59
$checkUserAccess = new PerformChecks(
60
    dataSanitizer(
61
        [
62
            'type' => htmlspecialchars($request->request->get('type', ''), ENT_QUOTES, 'UTF-8'),
63
        ],
64
        [
65
            'type' => 'trim|escape',
66
        ],
67
    ),
68
    [
69
        'user_id' => returnIfSet($session->get('user-id'), null),
70
        'user_key' => returnIfSet($session->get('key'), null),
71
    ]
72
);
73
// Handle the case
74
echo $checkUserAccess->caseHandler();
75
if (
76
    ($checkUserAccess->userAccessPage('home') === false ||
77
    $checkUserAccess->checkSession() === false)
78
    && in_array(filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS), ['get_teampass_settings', 'ga_generate_qr']) === false
79
) {
80
    // Not allowed page
81
    $session->set('system-error_code', ERR_NOT_ALLOWED);
82
    include $SETTINGS['cpassman_dir'] . '/error.php';
83
    exit;
84
}
85
86
// Define Timezone
87
date_default_timezone_set($SETTINGS['timezone'] ?? 'UTC');
88
set_time_limit(600);
89
90
// DO CHECKS
91
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
92
if (
93
    isset($post_type) === true
94
    && ($post_type === 'ga_generate_qr'
95
        || $post_type === 'get_teampass_settings')
96
) {
97
    // continue
98
    mainQuery($SETTINGS);
99
} elseif (
100
    $session->has('user-id') && null !== $session->get('user-id')
101
    && $checkUserAccess->userAccessPage('home') === false
102
) {
103
    $session->set('system-error_code', ERR_NOT_ALLOWED); //not allowed page
104
    include __DIR__.'/../error.php';
105
    exit();
106
} elseif (($session->has('user-id') && null !== $session->get('user-id')
107
        && $session->get('key') !== null)
108
    || (isset($post_type) === true
109
        && null !== filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES))
110
) {
111
    // continue
112
    mainQuery($SETTINGS);
113
} else {
114
    $session->set('system-error_code', ERR_NOT_ALLOWED); //not allowed page
115
    include __DIR__.'/../error.php';
116
    exit();
117
}
118
119
// Includes
120
include_once __DIR__.'/../sources/main.functions.php';
121
122
/**
123
 * Undocumented function.
124
 */
125
function mainQuery(array $SETTINGS)
126
{
127
    header('Content-type: text/html; charset=utf-8');
128
    header('Cache-Control: no-cache');
129
    error_reporting(E_ERROR);
130
131
    // Load libraries
132
    loadClasses('DB');
133
134
    // User's language loading
135
    $session = SessionManager::getSession();
136
    $lang = new Language($session->get('user-language') ?? 'english');
137
    $request = SymfonyRequest::createFromGlobals();
138
139
    // Prepare POST variables
140
    $inputData = dataSanitizer(
141
        [
142
            'type' => $request->request->filter('type', '', FILTER_SANITIZE_SPECIAL_CHARS),
143
            'data' => $request->request->filter('data', '', FILTER_SANITIZE_SPECIAL_CHARS),
144
            'key' => $request->request->filter('key', '', FILTER_SANITIZE_SPECIAL_CHARS),
145
            'type_category' => $request->request->filter('type_category', '', FILTER_SANITIZE_SPECIAL_CHARS),
146
        ],
147
        [
148
            'type' => 'trim|escape',
149
            'data' => 'trim|escape',
150
            'key' => 'trim|escape',
151
            'type_category' => 'trim|escape',
152
        ]
153
    );
154
    
155
    // Check KEY
156
    if (isValueSetNullEmpty($inputData['key']) === true) {
157
        echo prepareExchangedData(
158
            array(
159
                'error' => true,
160
                'message' => $lang->get('key_is_not_correct'),
161
            ),
162
            'encode',
163
            $inputData['key']
164
        );
165
        return false;
166
    }
167
    // decrypt and retreive data in JSON format
168
    $dataReceived = empty($inputData['data']) === false ? prepareExchangedData(
169
        $inputData['data'],
170
        'decode'
171
    ) : '';
172
    
173
    switch ($inputData['type_category']) {
174
        case 'action_password':
175
            echo passwordHandler($inputData['type'], $dataReceived, $SETTINGS);
176
            break;
177
178
        case 'action_user':
179
            echo userHandler($inputData['type'], $dataReceived, $SETTINGS, $inputData['key']);
180
            break;
181
182
        case 'action_mail':
183
            echo mailHandler($inputData['type'], $dataReceived, $SETTINGS);
184
            break;
185
186
        case 'action_key':
187
            // deepcode ignore ServerLeak: All cases handled by keyHandler return an encrypted string that is sent back to the client
188
            echo keyHandler($inputData['type'], $dataReceived, $SETTINGS);
189
            break;
190
191
        case 'action_system':
192
            echo systemHandler($inputData['type'], $dataReceived, $SETTINGS);
193
            break;
194
195
        case 'action_utils':
196
            echo utilsHandler($inputData['type'], $dataReceived, $SETTINGS);
197
            break;
198
    }
199
    
200
}
201
202
/**
203
 * Handler for all password tasks
204
 *
205
 * @param string $post_type
206
 * @param array|null|string $dataReceived
207
 * @param array $SETTINGS
208
 * @return string
209
 */
210
function passwordHandler(string $post_type, /*php8 array|null|string*/ $dataReceived, array $SETTINGS): string
211
{
212
    $session = SessionManager::getSession();
213
    $lang = new Language($session->get('user-language') ?? 'english');
214
215
    switch ($post_type) {
216
        case 'change_pw'://action_password
217
            return changePassword(
218
                (string) filter_var($dataReceived['new_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
219
                isset($dataReceived['current_pw']) === true ? (string) filter_var($dataReceived['current_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '',
220
                (int) filter_var($dataReceived['complexity'], FILTER_SANITIZE_NUMBER_INT),
221
                (string) filter_var($dataReceived['change_request'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
222
                (int) $session->get('user-id'),
223
                $SETTINGS
224
            );
225
226
        /*
227
         * Change user's authentication password
228
         */
229
        case 'change_user_auth_password'://action_password
230
231
            // Check new password and confirm match server side
232
            if ($dataReceived['new_password'] !== $dataReceived['new_password_confirm']) {
233
                return prepareExchangedData(
234
                    array(
235
                        'error' => true,
236
                        'message' => $lang->get('error_bad_credentials'),
237
                    ),
238
                    'encode'
239
                );
240
            }
241
242
            // Check if new password is strong
243
            if (!isPasswordStrong($dataReceived['new_password'])) {
244
                return prepareExchangedData(
245
                    array(
246
                        'error' => true,
247
                        'message' => $lang->get('complexity_level_not_reached'),
248
                    ),
249
                    'encode'
250
                );
251
            }
252
253
            return changeUserAuthenticationPassword(
254
                (int) $session->get('user-id'),
255
                (string) filter_var($dataReceived['old_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
256
                (string) filter_var($dataReceived['new_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
257
                $SETTINGS
258
            );
259
260
        /*
261
         * User's authentication password in LDAP has changed
262
         */
263
        case 'change_user_ldap_auth_password'://action_password
264
            // Check if no_password_provided is set
265
            if (isset($dataReceived['no_password_provided']) && $dataReceived['no_password_provided'] === 1) {
266
                // Handle case where no password is provided
267
                // The action is that we will reset all personnal items keys
268
                return resetUserPersonalItemKeys(
269
                    (int) $session->get('user-id')
270
                );
271
            }
272
273
            // Users passwords are html escaped
274
            $userPassword = filter_var($dataReceived['current_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
275
276
            // Get current user hash
277
            $userHash = DB::queryFirstRow(
278
                "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
279
                $session->get('user-id')
280
            )['pw'];
281
282
            $passwordManager = new PasswordManager();
283
284
            // Verify provided user password
285
            if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
286
                return prepareExchangedData(
287
                    array(
288
                        'error' => true,
289
                        'message' => $lang->get('error_bad_credentials'),
290
                    ),
291
                    'encode'
292
                );
293
            }
294
295
            return /** @scrutinizer ignore-call */ changeUserLDAPAuthenticationPassword(
296
                (int) $session->get('user-id'),
297
                filter_var($dataReceived['previous_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
298
                filter_var($userPassword)
299
            );
300
301
        /*
302
         * test_current_user_password_is_correct
303
         */
304
        case 'test_current_user_password_is_correct'://action_password
305
            return isUserPasswordCorrect(
306
                (int) $session->get('user-id'),
307
                (string) filter_var($dataReceived['password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
308
                (string) filter_var($dataReceived['otp'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
309
                $SETTINGS
310
            );
311
312
        /*
313
         * Default case
314
         */
315
        default :
316
            return prepareExchangedData(
317
                array(
318
                    'error' => true,
319
                ),
320
                'encode'
321
            );
322
    }
323
}
324
325
/**
326
 * Handler for all user tasks
327
 *
328
 * @param string $post_type
329
 * @param array|null|string $dataReceived
330
 * @param array $SETTINGS
331
 * @param string $post_key
332
 * @return string
333
 */
334
function userHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS, string $post_key): string
335
{
336
    $session = SessionManager::getSession();
337
338
    // List of post types allowed to all users
339
    $all_users_can_access = [
340
        'get_user_info',
341
        'increase_session_time',
342
        'generate_password',
343
        'refresh_list_items_seen',
344
        'ga_generate_qr',
345
        'user_get_session_time',
346
        'save_user_location'
347
    ];
348
349
    // Default values
350
    $filtered_user_id = $session->get('user-id');
351
352
    // User can't manage users and requested type is administrative.
353
    if ((int) $session->get('user-admin') !== 1 &&
354
        (int) $session->get('user-manager') !== 1 &&
355
        (int) $session->get('user-can_manage_all_users') !== 1 &&
356
        !in_array($post_type, $all_users_can_access)) {
357
358
        return prepareExchangedData(
359
            array(
360
                'error' => true,
361
            ),
362
            'encode'
363
        );
364
    }
365
366
    if (isset($dataReceived['user_id'])) {
367
        // Get info about user to modify
368
        $targetUserInfos = DB::queryFirstRow(
369
            'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
370
            WHERE id = %i',
371
            $dataReceived['user_id']
372
        );
373
374
        if (
375
            // Administrator user
376
            (int) $session->get('user-admin') === 1
377
            // Manager of basic/ro users in this role
378
            || ((int) $session->get('user-manager') === 1
379
                && in_array($targetUserInfos['isAdministratedByRole'], $session->get('user-roles_array'))
380
                && (int) $targetUserInfos['admin'] !== 1
381
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
382
                && (int) $targetUserInfos['gestionnaire'] !== 1)
383
            // Manager of all basic/ro users
384
            || ((int) $session->get('user-can_manage_all_users') === 1
385
                && (int) $targetUserInfos['admin'] !== 1
386
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
387
                && (int) $targetUserInfos['gestionnaire'] !== 1)
388
        ) {
389
            // This user is allowed to modify other users.
390
            $filtered_user_id = $dataReceived['user_id'];
391
        }
392
    }
393
394
    switch ($post_type) {
395
        /*
396
        * Get info 
397
        */
398
        case 'get_user_info'://action_user
399
            return getUserInfo(
400
                (int) $filtered_user_id,
401
                $SETTINGS
402
            );
403
404
        /*
405
        * Increase the session time of User
406
        */
407
        case 'increase_session_time'://action_user
408
            return increaseSessionDuration(
409
                (int) filter_input(INPUT_POST, 'duration', FILTER_SANITIZE_NUMBER_INT)
410
            );
411
412
        /*
413
        * Generate a password generic
414
        */
415
        case 'generate_password'://action_user
416
            return generateGenericPassword(
417
                (int) filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT),
418
                (bool) filter_input(INPUT_POST, 'secure_pwd', FILTER_VALIDATE_BOOLEAN),
419
                (bool) filter_input(INPUT_POST, 'lowercase', FILTER_VALIDATE_BOOLEAN),
420
                (bool) filter_input(INPUT_POST, 'capitalize', FILTER_VALIDATE_BOOLEAN),
421
                (bool) filter_input(INPUT_POST, 'numerals', FILTER_VALIDATE_BOOLEAN),
422
                (bool) filter_input(INPUT_POST, 'symbols', FILTER_VALIDATE_BOOLEAN),
423
                $SETTINGS
424
            );
425
426
        /*
427
        * Refresh list of last items seen
428
        */
429
        case 'refresh_list_items_seen'://action_user
430
            if ($session->has('user-id') || (int) $session->get('user-id') && null !== $session->get('user-id') || (int) $session->get('user-id') > 0) {
431
                return refreshUserItemsSeenList(
432
                    $SETTINGS
433
                );
434
435
            } else {
436
                return json_encode(
437
                    array(
438
                        'error' => '',
439
                        'existing_suggestions' => 0,
440
                        'html_json' => '',
441
                    ),
442
                    JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
443
                );
444
            }
445
446
        /*
447
        * This will generate the QR Google Authenticator
448
        */
449
        case 'ga_generate_qr'://action_user
450
            return generateQRCode(
451
                (int) $filtered_user_id,
452
                (string) filter_var($dataReceived['demand_origin'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
453
                (string) filter_var($dataReceived['send_email'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
454
                (string) filter_var($dataReceived['login'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
455
                (string) filter_var($dataReceived['pwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
456
                (string) filter_var($dataReceived['token'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
457
                $SETTINGS
458
            );
459
460
        /*
461
        * This will set the user ready
462
        */
463
        case 'user_is_ready'://action_user
464
            return userIsReady(
465
                (int) $filtered_user_id,
466
                (string) $SETTINGS['cpassman_dir']
467
            );
468
469
        /*
470
        * This post type is used to check if the user session is still valid
471
        */
472
        case 'user_get_session_time'://action_user
473
            return userGetSessionTime(
474
                (int) $session->get('user-id'),
475
                (string) $SETTINGS['cpassman_dir'],
476
                (int) $SETTINGS['maximum_session_expiration_time'],
477
            );
478
479
        case 'save_user_location'://action_user
480
            return userSaveIp(
481
                (int) $session->get('user-id'),
482
                (string) filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
483
            );
484
485
        /*
486
        * Default case
487
        */
488
        default :
489
            return prepareExchangedData(
490
                array(
491
                    'error' => true,
492
                ),
493
                'encode'
494
            );
495
    }
496
}
497
498
/**
499
 * Handler for all mail tasks
500
 *
501
 * @param string $post_type
502
 * @param array|null|string $dataReceived
503
 * @param array $SETTINGS
504
 * @return string
505
 */
506
function mailHandler(string $post_type, /*php8 array|null|string */$dataReceived, array $SETTINGS): string
507
{
508
    $session = SessionManager::getSession();
509
510
    switch ($post_type) {
511
        /*
512
         * CASE
513
         * Send email
514
         */
515
        case 'mail_me'://action_mail
516
            // Get info about user to send email
517
            $data_user = DB::queryFirstRow(
518
                'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
519
                WHERE email = %s',
520
                filter_var($dataReceived['receipt'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
521
            );
522
523
            // Unknown email address
524
            if (!$data_user) {
525
                return prepareExchangedData(
526
                    array(
527
                        'error' => true,
528
                    ),
529
                    'encode'
530
                );
531
            }
532
533
            // Only administrators and managers can send mails
534
            if (
535
                // Administrator user
536
                (int) $session->get('user-admin') === 1
537
                // Manager of basic/ro users in this role
538
                || ((int) $session->get('user-manager') === 1
539
                    && in_array($data_user['isAdministratedByRole'], $session->get('user-roles_array'))
540
                    && (int) $data_user['admin'] !== 1
541
                    && (int) $data_user['can_manage_all_users'] !== 1
542
                    && (int) $data_user['gestionnaire'] !== 1)
543
                // Manager of all basic/ro users
544
                || ((int) $session->get('user-can_manage_all_users') === 1
545
                    && (int) $data_user['admin'] !== 1
546
                    && (int) $data_user['can_manage_all_users'] !== 1
547
                    && (int) $data_user['gestionnaire'] !== 1)
548
            ) {
549
                return sendMailToUser(
550
                    filter_var($dataReceived['receipt'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
551
                    $dataReceived['body'],
552
                    (string) filter_var($dataReceived['subject'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
553
                    (array) filter_var_array(
554
                        $dataReceived['pre_replace'],
555
                        FILTER_SANITIZE_FULL_SPECIAL_CHARS
556
                    ),
557
                    true
558
                );
559
            }
560
561
            return prepareExchangedData(
562
                array(
563
                    'error' => true,
564
                ),
565
                'encode'
566
            );
567
        /*
568
        * Send emails not sent
569
        */
570
        case 'send_waiting_emails'://mail
571
            // Administrative task
572
            if ((int) $session->get('user-admin') !== 1) {
573
                return prepareExchangedData(
574
                    array(
575
                        'error' => true,
576
                    ),
577
                    'encode'
578
                );
579
            }
580
581
            sendEmailsNotSent(
582
                $SETTINGS
583
            );
584
            return prepareExchangedData(
585
                array(
586
                    'error' => false,
587
                    'message' => 'mail_sent',
588
                ),
589
                'encode'
590
            );
591
592
        /*
593
        * Default case
594
        */
595
        default :
596
            return prepareExchangedData(
597
                array(
598
                    'error' => true,
599
                ),
600
                'encode'
601
            );
602
    }
603
}
604
605
/**
606
 * Handler for all key related tasks
607
 *
608
 * @param string $post_type
609
 * @param array|null|string $dataReceived
610
 * @param array $SETTINGS
611
 * @return string
612
 */
613
function keyHandler(string $post_type, $dataReceived, array $SETTINGS): string
614
{
615
    $session = SessionManager::getSession();
616
    $lang = new Language($session->get('user-language') ?? 'english');
617
618
    // List of post types allowed to all users
619
    $all_users_can_access = [
620
        'change_private_key_encryption_password',
621
        'user_new_keys_generation',
622
        'user_recovery_keys_download',
623
        'generate_temporary_encryption_key'
624
    ];
625
626
    $individual_user_can_perform = [
627
        'user_psk_reencryption',
628
        'change_private_key_encryption_password',
629
        'user_only_personal_items_encryption'
630
    ];
631
632
    // Default values
633
    $filtered_user_id = $session->get('user-id');
634
635
    if (isset($dataReceived['user_id'])) {
636
        // Get info about user to modify
637
        $targetUserInfos = DB::queryFirstRow(
638
            'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
639
            WHERE id = %i',
640
            $dataReceived['user_id']
641
        );
642
        
643
        if (
644
            (
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ((int)$session->get('use...session->get('user-id'), Probably Intended Meaning: (int)$session->get('user...ession->get('user-id'))
Loading history...
645
                (
646
                    // Administrator user
647
                    (int) $session->get('user-admin') === 1
648
                    // Manager of basic/ro users in this role
649
                    || ((int) $session->get('user-manager') === 1
650
                        && in_array($targetUserInfos['isAdministratedByRole'], $session->get('user-roles_array'))
651
                        && (int) $targetUserInfos['admin'] !== 1
652
                        && (int) $targetUserInfos['can_manage_all_users'] !== 1
653
                        && (int) $targetUserInfos['gestionnaire'] !== 1)
654
                    // Manager of all basic/ro users
655
                    || ((int) $session->get('user-can_manage_all_users') === 1
656
                        && (int) $targetUserInfos['admin'] !== 1
657
                        && (int) $targetUserInfos['can_manage_all_users'] !== 1
658
                        && (int) $targetUserInfos['gestionnaire'] !== 1)
659
                ) && in_array($post_type, $all_users_can_access)
660
            )
661
            || (in_array($post_type, $individual_user_can_perform) && $dataReceived['user_id'] === $session->get('user-id'))
662
        ) {
663
            // This user is allowed to modify other users.
664
            // Or this user is allowed to perform an action on his account.
665
            $filtered_user_id = $dataReceived['user_id'];
666
        } else {
667
            // User can't manage users and requested type is administrative.
668
            return prepareExchangedData(
669
                array(
670
                    'error' => true,
671
                ),
672
                'encode'
673
            ); 
674
        }
675
    }
676
677
    switch ($post_type) {
678
        /*
679
         * Generate a temporary encryption key for user
680
         */
681
        case 'generate_temporary_encryption_key'://action_key
682
            return generateOneTimeCode(
683
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT)
684
            );
685
686
        /*
687
         * user_sharekeys_reencryption_next
688
         */
689
        case 'user_sharekeys_reencryption_next'://action_key
690
            return continueReEncryptingUserSharekeys(
691
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
692
                (bool) filter_var($dataReceived['self_change'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
693
                (string) filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
694
                (int) filter_var($dataReceived['start'], FILTER_SANITIZE_NUMBER_INT),
695
                (int) filter_var($dataReceived['length'], FILTER_SANITIZE_NUMBER_INT),
696
                $SETTINGS
697
            );
698
699
        /*
700
         * user_psk_reencryption
701
         */
702
        case 'user_psk_reencryption'://action_key
703
            return migrateTo3_DoUserPersonalItemsEncryption(
704
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
705
                (int) filter_var($dataReceived['start'], FILTER_SANITIZE_NUMBER_INT),
706
                (int) filter_var($dataReceived['length'], FILTER_SANITIZE_NUMBER_INT),
707
                (int) filter_var($dataReceived['counterItemsToTreat'], FILTER_SANITIZE_NUMBER_INT),
708
                (string) filter_var($dataReceived['userPsk'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
709
                $SETTINGS
710
            );
711
712
        /*
713
         * User's public/private keys change
714
         */
715
        case 'change_private_key_encryption_password'://action_key
716
717
            // Users passwords are html escaped
718
            $newPassword = filter_var($dataReceived['new_code'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
719
720
            // Get current user hash
721
            $userHash = DB::queryFirstRow(
722
                "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
723
                $session->get('user-id')
724
            )['pw'];
725
726
            $passwordManager = new PasswordManager();
727
728
            // Verify provided user password
729
            if (!$passwordManager->verifyPassword($userHash, $newPassword)) {
730
                return prepareExchangedData(
731
                    array(
732
                        'error' => true,
733
                        'message' => $lang->get('error_bad_credentials'),
734
                    ),
735
                    'encode'
736
                );
737
            }
738
739
            return changePrivateKeyEncryptionPassword(
740
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
741
                (string) $dataReceived['current_code'],
742
                (string) $newPassword,
743
                (string) filter_var($dataReceived['action_type'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
744
                $SETTINGS
745
            );
746
747
        /*
748
         * Launch user keys change on his demand
749
         */
750
        case 'user_new_keys_generation'://action_key
751
752
            // Users passwords are html escaped
753
            $userPassword = filter_var($dataReceived['user_pwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
754
755
            // Don't generate new user password -> verify it
756
            if ($dataReceived['generate_user_new_password'] !== true) {
757
758
                // Get current user hash
759
                $userHash = DB::queryFirstRow(
760
                    "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
761
                    $session->get('user-id')
762
                )['pw'];
763
764
                $passwordManager = new PasswordManager();
765
766
                // Verify provided user password
767
                if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
768
                    return prepareExchangedData(
769
                        array(
770
                            'error' => true,
771
                            'message' => $lang->get('error_bad_credentials'),
772
                        ),
773
                        'encode'
774
                    );
775
                }
776
            }
777
778
            return handleUserKeys(
779
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
780
                (string) $userPassword,
781
                (int) isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : NUMBER_ITEMS_IN_BATCH,
782
                (string) filter_var($dataReceived['encryption_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
783
                (bool) filter_var($dataReceived['delete_existing_keys'], FILTER_VALIDATE_BOOLEAN),
784
                (bool) filter_var($dataReceived['send_email_to_user'], FILTER_VALIDATE_BOOLEAN),
785
                (bool) filter_var($dataReceived['encrypt_with_user_pwd'], FILTER_VALIDATE_BOOLEAN),
786
                (bool) isset($dataReceived['generate_user_new_password']) === true ? filter_var($dataReceived['generate_user_new_password'], FILTER_VALIDATE_BOOLEAN) : false,
787
                (string) filter_var($dataReceived['email_body'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
788
                (bool) filter_var($dataReceived['user_self_change'], FILTER_VALIDATE_BOOLEAN),
789
                (string) filter_var($dataReceived['recovery_public_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
790
                (string) filter_var($dataReceived['recovery_private_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
791
                (bool) isset($dataReceived['user_has_to_encrypt_personal_items_after']) === true ? filter_var($dataReceived['user_has_to_encrypt_personal_items_after'], FILTER_VALIDATE_BOOLEAN) : false,
792
            );
793
794
        /*
795
         * Launch user recovery download
796
         */
797
        case 'user_recovery_keys_download'://action_key
798
            // Validate user password on local and LDAP accounts before download
799
            if ($session->get('user-auth_type') !== 'oauth2') {
800
                // Users passwords are html escaped
801
                $userPassword = filter_var($dataReceived['password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
802
803
                // Get current user hash
804
                $userHash = DB::queryFirstRow(
805
                    "SELECT pw FROM " . prefixtable('users') . " WHERE id = %i;",
806
                    $session->get('user-id')
807
                )['pw'];
808
809
                $passwordManager = new PasswordManager();
810
811
                // Verify provided user password
812
                if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
813
                    return prepareExchangedData(
814
                        array(
815
                            'error' => true,
816
                            'message' => $lang->get('error_bad_credentials'),
817
                        ),
818
                        'encode'
819
                    );
820
                }
821
            }
822
823
            return handleUserRecoveryKeysDownload(
824
                (int) $filtered_user_id,
825
                (array) $SETTINGS,
826
            );
827
828
        case 'user_only_personal_items_encryption': //action_key
829
            return setUserOnlyPersonalItemsEncryption(                
830
                (string) filter_var($dataReceived['userPreviousPwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
831
                (string) filter_var($dataReceived['userCurrentPwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
832
                (int) filter_var($dataReceived['userId'], FILTER_SANITIZE_NUMBER_INT),
833
            );
834
835
        /*
836
         * Default case
837
         */
838
        default :
839
            return prepareExchangedData(
840
                array(
841
                    'error' => true,
842
                ),
843
                'encode'
844
            );
845
    }
846
}
847
848
/**
849
 * Handler for all system tasks
850
 *
851
 * @param string $post_type
852
 * @param array|null|string $dataReceived
853
 * @param array $SETTINGS
854
 * @return string
855
 */
856
function systemHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS): string
857
{
858
    $session = SessionManager::getSession();
859
    switch ($post_type) {
860
        /*
861
        * How many items for this user
862
        */
863
        case 'get_number_of_items_to_treat'://action_system
864
            return getNumberOfItemsToTreat(
865
                (int) filter_var($dataReceived['user_id'], FILTER_SANITIZE_NUMBER_INT),
866
                $SETTINGS
867
            );
868
869
        /*
870
         * Sending statistics
871
         */
872
        case 'sending_statistics'://action_system
873
            sendingStatistics(
874
                $SETTINGS
875
            );
876
            return prepareExchangedData(
877
                array(
878
                    'error' => false,
879
                ),
880
                'encode'
881
            );
882
883
         /*
884
         * Generate BUG report
885
         */
886
        case 'generate_bug_report'://action_system
887
888
            // Only administrators can see this confidential informations.
889
            if ((int) $session->get('user-admin') !== 1) {
890
                return prepareExchangedData(
891
                    array(
892
                        'error' => false,
893
                    ),
894
                    'encode'
895
                );
896
            }
897
898
            return generateBugReport(
899
                (array) $dataReceived,
900
                $SETTINGS
901
            );
902
903
        /*
904
         * get_teampass_settings
905
         */
906
        case 'get_teampass_settings'://action_system
907
908
            // Encrypt data to return
909
            return prepareExchangedData(
910
                array_intersect_key(
911
                    $SETTINGS, 
912
                    array(
913
                        'ldap_user_attribute' => '',
914
                        'enable_pf_feature' => '',
915
                        'clipboard_life_duration' => '',
916
                        'enable_favourites' => '',
917
                        'copy_to_clipboard_small_icons' => '',
918
                        'enable_attachment_encryption' => '',
919
                        'google_authentication' => '',
920
                        'agses_authentication_enabled' => '',
921
                        'yubico_authentication' => '',
922
                        'duo' => '',
923
                        'personal_saltkey_security_level' => '',
924
                        'enable_tasks_manager' => '',
925
                        'insert_manual_entry_item_history' => '',
926
                        'show_item_data' => '',
927
                    )
928
                ),
929
                'encode'
930
            );
931
932
        /*
933
         * Generates a TOKEN with CRYPT
934
         */
935
        case 'save_token'://action_system
936
            $token = GenerateCryptKey(
937
                null !== filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT) ? (int) filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT) : 20,
938
                null !== filter_input(INPUT_POST, 'secure', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ? filter_input(INPUT_POST, 'secure', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : false,
939
                null !== filter_input(INPUT_POST, 'numeric', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ? filter_input(INPUT_POST, 'numeric', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : false,
940
                null !== filter_input(INPUT_POST, 'capital', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ? filter_input(INPUT_POST, 'capital', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : false,
941
                null !== filter_input(INPUT_POST, 'symbols', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ? filter_input(INPUT_POST, 'symbols', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : false,
942
                null !== filter_input(INPUT_POST, 'lowercase', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ? filter_input(INPUT_POST, 'lowercase', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : false
943
            );
944
            
945
            // store in DB
946
            DB::insert(
947
                prefixTable('tokens'),
948
                array(
949
                    'user_id' => (int) $session->get('user-id'),
950
                    'token' => $token,
951
                    'reason' => filter_input(INPUT_POST, 'reason', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
952
                    'creation_timestamp' => time(),
953
                    'end_timestamp' => time() + filter_input(INPUT_POST, 'duration', FILTER_SANITIZE_NUMBER_INT), // in secs
954
                )
955
            );
956
957
            return '[{"token" : "' . $token . '"}]';
958
959
        /*
960
        * Default case
961
        */
962
        default :
963
            return prepareExchangedData(
964
                array(
965
                    'error' => true,
966
                ),
967
                'encode'
968
            );
969
    }
970
}
971
972
973
function utilsHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS): string
974
{
975
    switch ($post_type) {
976
        /*
977
         * generate_an_otp
978
         */
979
        case 'generate_an_otp'://action_utils
980
            return generateAnOTP(
981
                (string) filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
982
                (bool) filter_var($dataReceived['with_qrcode'], FILTER_VALIDATE_BOOLEAN),
983
                (string) filter_var($dataReceived['secret_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
984
            );
985
986
987
        /*
988
         * Default case
989
         */
990
        default :
991
            return prepareExchangedData(
992
                array(
993
                    'error' => true,
994
                ),
995
                'encode'
996
            );
997
    }
998
}
999
1000
/**
1001
 * Permits to set the user ready
1002
 *
1003
 * @param integer $userid
1004
 * @param string $dir
1005
 * @return string
1006
 */
1007
function userIsReady(int $userid, string $dir): string
1008
{
1009
    DB::update(
1010
        prefixTable('users'),
1011
        array(
1012
            'is_ready_for_usage' => 1,
1013
        ),
1014
        'id = %i',
1015
        $userid
1016
    );
1017
1018
    // Send back
1019
    return prepareExchangedData(
1020
        array(
1021
            'error' => false,
1022
        ),
1023
        'encode'
1024
    ); 
1025
}
1026
1027
1028
/**
1029
 * Permits to set the user ready
1030
 *
1031
 * @param integer $userid
1032
 * @return string
1033
 */
1034
function userGetSessionTime(int $userid, string $dir, int $maximum_session_expiration_time): string
1035
{
1036
    $session = SessionManager::getSession();
1037
    // Send back
1038
    return prepareExchangedData(
1039
        array(
1040
            'error' => false,
1041
            'timestamp' => $session->get('user-session_duration'),
1042
            'max_time_to_add' => intdiv((($maximum_session_expiration_time*60) - ((int) $session->get('user-session_duration') - time())), 60),
1043
            'max_session_duration' => $maximum_session_expiration_time,
1044
        ),
1045
        'encode'
1046
    ); 
1047
}
1048
1049
/**
1050
 * Save the user's IP
1051
 *
1052
 * @param integer $userID
1053
 * @param string $action
1054
 * @return string
1055
 */
1056
function userSaveIp(int $userID, string $action): string
1057
{
1058
    if ($action === 'perform') {
1059
        DB::update(
1060
            prefixTable('users'),
1061
            array(
1062
                'user_ip' => getClientIpServer(),
1063
                'user_ip_lastdate' => time(),
1064
            ),
1065
            'id = %i',
1066
            $userID
1067
        );
1068
    }
1069
1070
    return prepareExchangedData(
1071
        array(
1072
            'error' => false,
1073
        ),
1074
        'encode'
1075
    );
1076
}
1077
1078
/**
1079
 * Provides the number of items
1080
 *
1081
 * @param int   $userId     User ID
1082
 * @param array $SETTINGS   TeampassSettings
1083
 *
1084
 * @return string
1085
 */
1086
function getNumberOfItemsToTreat(
1087
    int $userId,
1088
    array $SETTINGS
1089
): string
1090
{
1091
    // get number of items
1092
    DB::queryFirstRow(
1093
        'SELECT increment_id
1094
        FROM ' . prefixTable('sharekeys_items') .
1095
        ' WHERE user_id = %i',
1096
        $userId
1097
    );
1098
1099
    // Send back
1100
    return prepareExchangedData(
1101
        array(
1102
            'error' => false,
1103
            'nbItems' => DB::count(),
1104
        ),
1105
        'encode'
1106
    );
1107
}
1108
1109
1110
/**
1111
 * 
1112
 */
1113
function changePassword(
1114
    string $post_new_password,
1115
    string $post_current_password,
1116
    int $post_password_complexity,
1117
    string $post_change_request,
1118
    int $post_user_id,
1119
    array $SETTINGS
1120
): string
1121
{
1122
    $session = SessionManager::getSession();
1123
    
1124
    // Create password hash
1125
    $passwordManager = new PasswordManager();
1126
    $post_new_password_hashed = $passwordManager->hashPassword($post_new_password);
1127
1128
    // Load user's language
1129
    $lang = new Language($session->get('user-language') ?? 'english');
1130
1131
    // User has decided to change is PW
1132
    if ($post_change_request === 'reset_user_password_expected'
1133
        || $post_change_request === 'user_decides_to_change_password'
1134
    ) {
1135
        // Check that current user is correct
1136
        if ((int) $post_user_id !== (int) $session->get('user-id')) {
1137
            return prepareExchangedData(
1138
                array(
1139
                    'error' => true,
1140
                    'message' => $lang->get('error_not_allowed_to'),
1141
                ),
1142
                'encode'
1143
            );
1144
        }
1145
1146
        // check if expected security level is reached
1147
        $dataUser = DB::queryFirstRow(
1148
            'SELECT *
1149
            FROM ' . prefixTable('users') . ' WHERE id = %i',
1150
            $post_user_id
1151
        );
1152
1153
        // check if badly written
1154
        $dataUser['fonction_id'] = array_filter(
1155
            explode(',', str_replace(';', ',', $dataUser['fonction_id']))
1156
        );
1157
        $dataUser['fonction_id'] = implode(',', $dataUser['fonction_id']);
1158
        DB::update(
1159
            prefixTable('users'),
1160
            array(
1161
                'fonction_id' => $dataUser['fonction_id'],
1162
            ),
1163
            'id = %i',
1164
            $post_user_id
1165
        );
1166
1167
        if (empty($dataUser['fonction_id']) === false) {
1168
            $data = DB::queryFirstRow(
1169
                'SELECT complexity
1170
                FROM ' . prefixTable('roles_title') . '
1171
                WHERE id IN (' . $dataUser['fonction_id'] . ')
1172
                ORDER BY complexity DESC'
1173
            );
1174
        } else {
1175
            // In case user has no roles yet
1176
            $data = array();
1177
            $data['complexity'] = 0;
1178
        }
1179
1180
        if ((int) $post_password_complexity < (int) $data['complexity']) {
1181
            return prepareExchangedData(
1182
                array(
1183
                    'error' => true,
1184
                    'message' => '<div style="margin:10px 0 10px 15px;">' . $lang->get('complexity_level_not_reached') . '.<br>' .
1185
                        $lang->get('expected_complexity_level') . ': <b>' . TP_PW_COMPLEXITY[$data['complexity']][1] . '</b></div>',
1186
                ),
1187
                'encode'
1188
            );
1189
        }
1190
1191
        // Check that the 2 passwords are differents
1192
        if ($post_current_password === $post_new_password) {
1193
            return prepareExchangedData(
1194
                array(
1195
                    'error' => true,
1196
                    'message' => $lang->get('password_already_used'),
1197
                ),
1198
                'encode'
1199
            );
1200
        }
1201
1202
        // update sessions
1203
        $session->set('user-last_pw_change', mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')));
1204
        $session->set('user-validite_pw', 1);
1205
1206
        // BEfore updating, check that the pwd is correct
1207
        if ($passwordManager->verifyPassword($post_new_password_hashed, $post_new_password) === true && empty($dataUser['private_key']) === false) {
1208
            $special_action = 'none';
1209
            if ($post_change_request === 'reset_user_password_expected') {
1210
                $session->set('user-private_key', decryptPrivateKey($post_current_password, $dataUser['private_key']));
1211
            }
1212
1213
            // update DB
1214
            DB::update(
1215
                prefixTable('users'),
1216
                array(
1217
                    'pw' => $post_new_password_hashed,
1218
                    'last_pw_change' => mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')),
1219
                    'last_pw' => $post_current_password,
1220
                    'special' => $special_action,
1221
                    'private_key' => encryptPrivateKey($post_new_password, $session->get('user-private_key')),
1222
                ),
1223
                'id = %i',
1224
                $post_user_id
1225
            );
1226
            // update LOG
1227
            logEvents($SETTINGS, 'user_mngt', 'at_user_pwd_changed', (string) $session->get('user-id'), $session->get('user-login'), $post_user_id);
1228
1229
            // Send back
1230
            return prepareExchangedData(
1231
                array(
1232
                    'error' => false,
1233
                    'message' => '',
1234
                ),
1235
                'encode'
1236
            );
1237
        }
1238
        // Send back
1239
        return prepareExchangedData(
1240
            array(
1241
                'error' => true,
1242
                'message' => $lang->get('pw_hash_not_correct'),
1243
            ),
1244
            'encode'
1245
        );
1246
    }
1247
    return prepareExchangedData(
1248
        array(
1249
            'error' => true,
1250
            'message' => $lang->get('error_not_allowed_to'),
1251
        ),
1252
        'encode'
1253
    );
1254
}
1255
1256
function generateQRCode(
1257
    $post_id,
1258
    $post_demand_origin,
1259
    $post_send_mail,
1260
    $post_login,
1261
    $post_pwd,
1262
    $post_token,
1263
    array $SETTINGS
1264
): string
1265
{
1266
    // Load user's language
1267
    $session = SessionManager::getSession();
1268
    $lang = new Language($session->get('user-language') ?? 'english');
1269
1270
    // is this allowed by setting
1271
    if (isKeyExistingAndEqual('ga_reset_by_user', 0, $SETTINGS) === true
1272
        && (null === $post_demand_origin || $post_demand_origin !== 'users_management_list')
1273
    ) {
1274
        // User cannot ask for a new code
1275
        return prepareExchangedData(
1276
            array(
1277
                'error' => true,
1278
                'message' => "113 ".$lang->get('error_not_allowed_to')." - ".isKeyExistingAndEqual('ga_reset_by_user', 1, $SETTINGS),
1279
            ),
1280
            'encode'
1281
        );
1282
    }
1283
    
1284
    // Check if user exists
1285
    if (isValueSetNullEmpty($post_id) === true) {
1286
        // Get data about user
1287
        $dataUser = DB::queryFirstRow(
1288
            'SELECT id, email, pw
1289
            FROM ' . prefixTable('users') . '
1290
            WHERE login = %s',
1291
            $post_login
1292
        );
1293
    } else {
1294
        $dataUser = DB::queryFirstRow(
1295
            'SELECT id, login, email, pw
1296
            FROM ' . prefixTable('users') . '
1297
            WHERE id = %i',
1298
            $post_id
1299
        );
1300
        $post_login = $dataUser['login'];
1301
    }
1302
    // Get number of returned users
1303
    $counter = DB::count();
1304
1305
    // Do treatment
1306
    if ($counter === 0) {
1307
        // Not a registered user !
1308
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($post_login), stripslashes($post_login));
1309
        return prepareExchangedData(
1310
            array(
1311
                'error' => true,
1312
                'message' => $lang->get('no_user'),
1313
                'tst' => 1,
1314
            ),
1315
            'encode'
1316
        );
1317
    }
1318
1319
    $passwordManager = new PasswordManager();
1320
    if (
1321
        isSetArrayOfValues([$post_pwd, $dataUser['pw']]) === true
1322
        && $passwordManager->verifyPassword($dataUser['pw'], $post_pwd) === false
1323
        && $post_demand_origin !== 'users_management_list'
1324
    ) {
1325
        // checked the given password
1326
        logEvents($SETTINGS, 'failed_auth', 'password_is_not_correct', '', stripslashes($post_login), stripslashes($post_login));
1327
        return prepareExchangedData(
1328
            array(
1329
                'error' => true,
1330
                'message' => $lang->get('no_user'),
1331
                'tst' => $post_demand_origin,
1332
            ),
1333
            'encode'
1334
        );
1335
    }
1336
    
1337
    if (empty($dataUser['email']) === true) {
1338
        return prepareExchangedData(
1339
            array(
1340
                'error' => true,
1341
                'message' => $lang->get('no_email_set'),
1342
            ),
1343
            'encode'
1344
        );
1345
    }
1346
1347
    // Check if token already used
1348
    $dataToken = DB::queryFirstRow(
1349
        'SELECT end_timestamp, reason
1350
        FROM ' . prefixTable('tokens') . '
1351
        WHERE token = %s AND user_id = %i',
1352
        $post_token,
1353
        $dataUser['id']
1354
    );
1355
    $tokenId = '';
1356
    if (DB::count() > 0 && is_null($dataToken['end_timestamp']) === false && $dataToken['reason'] === 'auth_qr_code') {
1357
        // This token has already been used
1358
        return prepareExchangedData(
1359
            array(
1360
                'error' => true,
1361
                'message' => 'TOKEN already used',//$lang->get('no_email_set'),
1362
            ),
1363
            'encode'
1364
        );
1365
    } elseif(DB::count() === 0) {
1366
        // Store token for this action
1367
        DB::insert(
1368
            prefixTable('tokens'),
1369
            array(
1370
                'user_id' => (int) $dataUser['id'],
1371
                'token' => $post_token,
1372
                'reason' => 'auth_qr_code',
1373
                'creation_timestamp' => time(),
1374
            )
1375
        );
1376
        $tokenId = DB::insertId();
1377
    }
1378
    
1379
    // generate new GA user code
1380
    $tfa = new TwoFactorAuth($SETTINGS['ga_website_name']);
1381
    $gaSecretKey = $tfa->createSecret();
1382
    $gaTemporaryCode = GenerateCryptKey(12, false, true, true, false, true);
1383
1384
    DB::update(
1385
        prefixTable('users'),
1386
        [
1387
            'ga' => $gaSecretKey,
1388
            'ga_temporary_code' => $gaTemporaryCode,
1389
        ],
1390
        'id = %i',
1391
        $dataUser['id']
1392
    );
1393
1394
    // Log event
1395
    logEvents($SETTINGS, 'user_connection', 'at_2fa_google_code_send_by_email', (string) $dataUser['id'], stripslashes($post_login), stripslashes($post_login));
1396
1397
    // Update token status
1398
    DB::update(
1399
        prefixTable('tokens'),
1400
        [
1401
            'end_timestamp' => time(),
1402
        ],
1403
        'id = %i',
1404
        $tokenId
1405
    );
1406
1407
    // send mail?
1408
    if ((int) $post_send_mail === 1) {
1409
        prepareSendingEmail(
1410
            $lang->get('email_ga_subject'),
1411
            str_replace(
1412
                '#2FACode#',
1413
                $gaTemporaryCode,
1414
                $lang->get('email_ga_text')
1415
            ),
1416
            $dataUser['email']
1417
        );
1418
1419
        // send back
1420
        return prepareExchangedData(
1421
            array(
1422
                'error' => false,
1423
                'message' => $post_send_mail,
1424
                'email' => $dataUser['email'],
1425
                'email_result' => str_replace(
1426
                    '#email#',
1427
                    '<b>' . obfuscateEmail($dataUser['email']) . '</b>',
1428
                    addslashes($lang->get('admin_email_result_ok'))
1429
                ),
1430
            ),
1431
            'encode'
1432
        );
1433
    }
1434
    
1435
    // send back
1436
    return prepareExchangedData(
1437
        array(
1438
            'error' => false,
1439
            'message' => '',
1440
            'email' => $dataUser['email'],
1441
            'email_result' => str_replace(
1442
                '#email#',
1443
                '<b>' . obfuscateEmail($dataUser['email']) . '</b>',
1444
                addslashes($lang->get('admin_email_result_ok'))
1445
            ),
1446
        ),
1447
        'encode'
1448
    );
1449
}
1450
1451
function sendEmailsNotSent(
1452
    array $SETTINGS
1453
)
1454
{
1455
    $emailSettings = new EmailSettings($SETTINGS);
1456
    $emailService = new EmailService();
1457
1458
    if (isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true) {
1459
        $row = DB::queryFirstRow(
1460
            'SELECT valeur FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s',
1461
            'cron',
1462
            'sending_emails'
1463
        );
1464
1465
        if ((int) (time() - $row['valeur']) >= 300 || (int) $row['valeur'] === 0) {
1466
            $rows = DB::query(
1467
                'SELECT *
1468
                FROM ' . prefixTable('emails') .
1469
                ' WHERE status != %s',
1470
                'sent'
1471
            );
1472
            foreach ($rows as $record) {
1473
                // Send email
1474
                $ret = json_decode(
1475
                    $emailService->sendMail(
1476
                        $record['subject'],
1477
                        $record['body'],
1478
                        $record['receivers'],
1479
                        $emailSettings
1480
                    ),
1481
                    true
1482
                );
1483
1484
                // update item_id in files table
1485
                DB::update(
1486
                    prefixTable('emails'),
1487
                    array(
1488
                        'status' => $ret['error'] === 'error_mail_not_send' ? 'not_sent' : 'sent',
1489
                    ),
1490
                    'timestamp = %s',
1491
                    $record['timestamp']
1492
                );
1493
            }
1494
        }
1495
        // update cron time
1496
        DB::update(
1497
            prefixTable('misc'),
1498
            array(
1499
                'valeur' => time(),
1500
                'updated_at' => time(),
1501
            ),
1502
            'intitule = %s AND type = %s',
1503
            'sending_emails',
1504
            'cron'
1505
        );
1506
    }
1507
}
1508
1509
1510
function refreshUserItemsSeenList(
1511
    array $SETTINGS
1512
): string
1513
{
1514
    $session = SessionManager::getSession();
1515
1516
    // get list of last items seen
1517
    $arr_html = array();
1518
    $rows = DB::query(
1519
        'SELECT i.id AS id, i.label AS label, i.id_tree AS id_tree, l.date, i.perso AS perso, i.restricted_to AS restricted
1520
        FROM ' . prefixTable('log_items') . ' AS l
1521
        RIGHT JOIN ' . prefixTable('items') . ' AS i ON (l.id_item = i.id)
1522
        WHERE l.action = %s AND l.id_user = %i
1523
        ORDER BY l.date DESC
1524
        LIMIT 0, 100',
1525
        'at_shown',
1526
        $session->get('user-id')
1527
    );
1528
    if (DB::count() > 0) {
1529
        foreach ($rows as $record) {
1530
            if (in_array($record['id']->id, array_column($arr_html, 'id')) === false) {
1531
                array_push(
1532
                    $arr_html,
1533
                    array(
1534
                        'id' => $record['id'],
1535
                        'label' => htmlspecialchars(stripslashes(htmlspecialchars_decode($record['label'], ENT_QUOTES)), ENT_QUOTES),
1536
                        'tree_id' => $record['id_tree'],
1537
                        'perso' => $record['perso'],
1538
                        'restricted' => $record['restricted'],
1539
                    )
1540
                );
1541
                if (count($arr_html) >= (int) $SETTINGS['max_latest_items']) {
1542
                    break;
1543
                }
1544
            }
1545
        }
1546
    }
1547
1548
    // get wainting suggestions
1549
    $nb_suggestions_waiting = 0;
1550
    if (isKeyExistingAndEqual('enable_suggestion', 1, $SETTINGS) === true
1551
        && ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1)
1552
    ) {
1553
        DB::query('SELECT * FROM ' . prefixTable('suggestion'));
1554
        $nb_suggestions_waiting = DB::count();
1555
    }
1556
1557
    return json_encode(
1558
        array(
1559
            'error' => '',
1560
            'existing_suggestions' => $nb_suggestions_waiting,
1561
            'html_json' => $arr_html,
1562
        ),
1563
        JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1564
    );
1565
}
1566
1567
function sendingStatistics(
1568
    array $SETTINGS
1569
): void
1570
{
1571
    $session = SessionManager::getSession();
1572
    if (
1573
        isSetArrayOfValues([$SETTINGS['send_statistics_items'], $SETTINGS['send_stats_time']]) === true
1574
        && isKeyExistingAndEqual('send_stats', 1, $SETTINGS) === true
1575
        && (int) ($SETTINGS['send_stats_time'] + TP_ONE_DAY_SECONDS) > time()
1576
    ) {
1577
        // get statistics data
1578
        $stats_data = getStatisticsData($SETTINGS);
1579
1580
        // get statistics items to share
1581
        $statsToSend = [];
1582
        $statsToSend['ip'] = $_SERVER['SERVER_ADDR'];
1583
        $statsToSend['timestamp'] = time();
1584
        foreach (array_filter(explode(';', $SETTINGS['send_statistics_items'])) as $data) {
1585
            if ($data === 'stat_languages') {
1586
                $tmp = '';
1587
                foreach ($stats_data[$data] as $key => $value) {
1588
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1589
                }
1590
                $statsToSend[$data] = $tmp;
1591
            } elseif ($data === 'stat_country') {
1592
                $tmp = '';
1593
                foreach ($stats_data[$data] as $key => $value) {
1594
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1595
                }
1596
                $statsToSend[$data] = $tmp;
1597
            } else {
1598
                $statsToSend[$data] = $stats_data[$data];
1599
            }
1600
        }
1601
1602
        // connect to Teampass Statistics database
1603
        $link2 = new MeekroDB(
1604
            'teampass.pw',
1605
            'teampass_user',
1606
            'ZMlEfRzKzFLZNzie',
1607
            'teampass_followup',
1608
            '3306',
1609
            'utf8'
1610
        );
1611
1612
        $link2->insert(
1613
            'statistics',
1614
            $statsToSend
1615
        );
1616
1617
        // update table misc with current timestamp
1618
        DB::update(
1619
            prefixTable('misc'),
1620
            array(
1621
                'valeur' => time(),
1622
                'updated_at' => time(),
1623
            ),
1624
            'type = %s AND intitule = %s',
1625
            'admin',
1626
            'send_stats_time'
1627
        );
1628
1629
        //permits to test only once by session
1630
        $session->set('system-send_stats_done', 1);
1631
        $SETTINGS['send_stats_time'] = time();
1632
    }
1633
}
1634
1635
function generateBugReport(
1636
    array $data,
1637
    array $SETTINGS
1638
): string
1639
{
1640
    $config_exclude_vars = array(
1641
        'bck_script_passkey',
1642
        'email_smtp_server',
1643
        'email_auth_username',
1644
        'email_auth_pwd',
1645
        'email_from',
1646
        'onthefly-restore-key',
1647
        'onthefly-backup-key',
1648
        'ldap_password',
1649
        'ldap_hosts',
1650
        'proxy_ip',
1651
        'ldap_bind_passwd',
1652
        'syslog_host',
1653
        'duo_akey',
1654
        'duo_ikey',
1655
        'duo_skey',
1656
        'duo_host',
1657
        'oauth2_client_id',
1658
        'oauth2_tenant_id',
1659
        'oauth2_client_secret',
1660
        'oauth2_client_token',
1661
        'oauth2_client_endpoint',
1662
    );
1663
1664
    // Load user's language
1665
    $session = SessionManager::getSession();
1666
    $lang = new Language($session->get('user-language') ?? 'english');
1667
1668
    // Read config file
1669
    $list_of_options = '';
1670
    $url_found = '';
1671
    $anonym_url = '';
1672
    $sortedSettings = $SETTINGS;
1673
    ksort($sortedSettings);
1674
1675
    foreach ($sortedSettings as $key => $value) {
1676
        // Identify url to anonymize it
1677
        if ($key === 'cpassman_url' && empty($url_found) === true) {
1678
            $url_found = $value;
1679
            if (empty($url_found) === false) {
1680
                $tmp = parse_url($url_found);
1681
                $anonym_url = $tmp['scheme'] . '://<anonym_url>' . (isset($tmp['path']) === true ? $tmp['path'] : '');
1682
                $value = $anonym_url;
1683
            } else {
1684
                $value = '';
1685
            }
1686
        }
1687
1688
        // Anonymize all urls
1689
        if (empty($anonym_url) === false) {
1690
            $value = str_replace($url_found, $anonym_url, (string) $value);
1691
        }
1692
1693
        // Clear some vars
1694
        foreach ($config_exclude_vars as $var) {
1695
            if ($key === $var) {
1696
                $value = '<removed>';
1697
            }
1698
        }
1699
1700
        // Complete line to display
1701
        $list_of_options .= "'$key' => '$value'\n";
1702
    }
1703
1704
    // Get error
1705
    $err = error_get_last();
1706
1707
    // Get 10 latest errors in Teampass
1708
    $teampass_errors = '';
1709
    $rows = DB::query(
1710
        'SELECT label, date AS error_date
1711
        FROM ' . prefixTable('log_system') . "
1712
        WHERE `type` LIKE 'error'
1713
        ORDER BY `date` DESC
1714
        LIMIT 0, 10"
1715
    );
1716
    if (DB::count() > 0) {
1717
        foreach ($rows as $record) {
1718
            if (empty($teampass_errors) === true) {
1719
                $teampass_errors = ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1720
            } else {
1721
                $teampass_errors .= ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1722
            }
1723
        }
1724
    }
1725
1726
    if (defined('DB_PASSWD_CLEAR') === false) {
1727
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD));
1728
    }
1729
    $link = mysqli_connect(DB_HOST, DB_USER, DB_PASSWD_CLEAR, DB_NAME, (int) DB_PORT, null);
1730
1731
    // Now prepare text
1732
    $txt = '### Page on which it happened
1733
' . $data['current_page'] . '
1734
1735
### Steps to reproduce
1736
1.
1737
2.
1738
3.
1739
1740
### Expected behaviour
1741
Tell us what should happen
1742
1743
1744
### Actual behaviour
1745
Tell us what happens instead
1746
1747
### Server configuration
1748
**Operating system**: ' . php_uname() . '
1749
1750
**Web server:** ' . $_SERVER['SERVER_SOFTWARE'] . '
1751
1752
**Database:** ' . ($link === false ? $lang->get('undefined') : mysqli_get_server_info($link)) . '
1753
1754
**PHP version:** ' . PHP_VERSION . '
1755
1756
**Teampass version:** ' . TP_VERSION . '.' . TP_VERSION_MINOR . '
1757
1758
**Teampass configuration variables:**
1759
```
1760
' . $list_of_options . '
1761
```
1762
1763
**Updated from an older Teampass or fresh install:**
1764
1765
### Client configuration
1766
1767
**Browser:** ' . $data['browser_name'] . ' - ' . $data['browser_version'] . '
1768
1769
**Operating system:** ' . $data['os'] . ' - ' . $data['os_archi'] . 'bits
1770
1771
### Logs
1772
1773
#### Web server error log
1774
```
1775
' . $err['message'] . ' - ' . $err['file'] . ' (' . $err['line'] . ')
1776
```
1777
1778
#### Teampass 10 last system errors
1779
```
1780
' . $teampass_errors . '
1781
```
1782
1783
#### Log from the web-browser developer console (CTRL + SHIFT + i)
1784
```
1785
Insert the log here and especially the answer of the query that failed.
1786
```
1787
';
1788
1789
    return prepareExchangedData(
1790
        array(
1791
            'html' => $txt,
1792
            'error' => '',
1793
        ),
1794
        'encode'
1795
    );
1796
}
1797
1798
/**
1799
 * Check that the user password is valid
1800
 *
1801
 * @param integer $post_user_id
1802
 * @param string $post_user_password
1803
 * @param string $post_user_otp
1804
 * @param array $SETTINGS
1805
 * @return string
1806
 */
1807
function isUserPasswordCorrect(
1808
    int $post_user_id,
1809
    string $post_user_password,
1810
    string $post_user_otp,
1811
    array $SETTINGS
1812
): string
1813
{
1814
    $session = SessionManager::getSession();
1815
    // Load user's language
1816
    $lang = new Language($session->get('user-language') ?? 'english');
1817
    
1818
    if (isUserIdValid($post_user_id) === true) {
1819
        // Check if user exists
1820
        $userInfo = DB::queryFirstRow(
1821
            'SELECT public_key, private_key, pw, auth_type
1822
            FROM ' . prefixTable('users') . '
1823
            WHERE id = %i',
1824
            $post_user_id
1825
        );
1826
        if (DB::count() > 0 && empty($userInfo['private_key']) === false) {
1827
            // Get itemKey from current user
1828
            // Get one item
1829
            $currentUserKey = DB::queryFirstRow(
1830
                'SELECT object_id, share_key, increment_id
1831
                FROM ' . prefixTable('sharekeys_items') . ' AS si
1832
                INNER JOIN ' . prefixTable('items') . ' AS i ON  (i.id = si.object_id)
1833
                INNER JOIN ' . prefixTable('nested_tree') . ' AS nt ON  (i.id_tree = nt.id)
1834
                WHERE user_id = %i AND nt.personal_folder = %i',
1835
                $post_user_id,
1836
                0
1837
            );
1838
            
1839
            if (DB::count() === 0) {
1840
                // This user has no items
1841
                // let's consider no items in DB
1842
                return prepareExchangedData(
1843
                    array(
1844
                        'error' => false,
1845
                        'message' => '',
1846
                        'debug' => '',
1847
                    ),
1848
                    'encode'
1849
                );
1850
            }
1851
            
1852
            if ($currentUserKey !== null) {
1853
                // Decrypt itemkey with user key
1854
                // use old password to decrypt private_key
1855
                $possibleKeys = [
1856
                    $post_user_password,
1857
                    $post_user_otp,
1858
                ];
1859
1860
                $privateKeyDecrypted = null;
1861
1862
                foreach ($possibleKeys as $key) {
1863
                    $result = decryptPrivateKey($key, $userInfo['private_key']);
1864
                    
1865
                    // If decryption is ok
1866
                    if (is_string($result) && $result !== '') {
1867
                        $privateKeyDecrypted = $result;
1868
                        break;
1869
                    }
1870
                }
1871
1872
                if ($privateKeyDecrypted === null) {
1873
                    // No key is ok
1874
                    return prepareExchangedData(
1875
                        array(
1876
                            'error' => true,
1877
                            'message' => $lang->get('password_is_not_correct'),
1878
                        ),
1879
                        'encode'
1880
                    );
1881
                }
1882
1883
                $session->set('user-private_key', $privateKeyDecrypted);
1884
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $privateKeyDecrypted);
1885
1886
                if (empty(base64_decode($itemKey)) === false) {
1887
                    // GOOD password
1888
                    return prepareExchangedData(
1889
                        array(
1890
                            'error' => false,
1891
                            'message' => '',
1892
                            'debug' => '',
1893
                        ),
1894
                        'encode'
1895
                    );
1896
                }
1897
            }
1898
1899
            // use the password check
1900
            $passwordManager = new PasswordManager();
1901
            if ($passwordManager->verifyPassword($userInfo['pw'], $post_user_password) === true) {
1902
                // GOOD password
1903
                return prepareExchangedData(
1904
                    array(
1905
                        'error' => false,
1906
                        'message' => '',
1907
                        'debug' => '',
1908
                    ),
1909
                    'encode'
1910
                );
1911
            }
1912
        }
1913
    }
1914
1915
    return prepareExchangedData(
1916
        array(
1917
            'error' => true,
1918
            'message' => $lang->get('password_is_not_correct'),
1919
        ),
1920
        'encode'
1921
    );
1922
}
1923
1924
function changePrivateKeyEncryptionPassword(
1925
    int $post_user_id,
1926
    string $post_current_code,
1927
    string $post_new_code,
1928
    string $post_action_type,
1929
    array $SETTINGS
1930
): string
1931
{
1932
    $session = SessionManager::getSession();
1933
    // Load user's language
1934
    $lang = new Language($session->get('user-language') ?? 'english');
1935
    
1936
    if (empty($post_new_code) === true) {
1937
        // no user password
1938
        return prepareExchangedData(
1939
            array(
1940
                'error' => true,
1941
                'message' => $lang->get('error_bad_credentials'),
1942
                'debug' => '',
1943
            ),
1944
            'encode'
1945
        );
1946
    }
1947
1948
    if (isUserIdValid($post_user_id) === true) {
1949
        // Get user info
1950
        $userData = DB::queryFirstRow(
1951
            'SELECT private_key
1952
            FROM ' . prefixTable('users') . '
1953
            WHERE id = %i',
1954
            $post_user_id
1955
        );
1956
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
1957
            if ($post_action_type === 'encrypt_privkey_with_user_password') {
1958
                // Here the user has his private key encrypted with an OTC.
1959
                // We need to encrypt it with his real password
1960
                $privateKey = decryptPrivateKey($post_new_code, $userData['private_key']);
1961
                $hashedPrivateKey = encryptPrivateKey($post_current_code, $privateKey);
1962
            } else {
1963
                $privateKey = decryptPrivateKey($post_current_code, $userData['private_key']);
1964
                $hashedPrivateKey = encryptPrivateKey($post_new_code, $privateKey);
1965
            }
1966
1967
            // Should fail here to avoid break user private key.
1968
            if (strlen($privateKey) === 0 || strlen($hashedPrivateKey) < 30) {
1969
                if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
1970
                    error_log("Error reencrypt user private key. User ID: {$post_user_id}, Given OTP: '{$post_current_code}'");
1971
                }
1972
                return prepareExchangedData(
1973
                    array(
1974
                        'error' => true,
1975
                        'message' => $lang->get('error_otp_secret'),
1976
                        'debug' => '',
1977
                    ),
1978
                    'encode'
1979
                );
1980
            }
1981
1982
            // Update user account
1983
            DB::update(
1984
                prefixTable('users'),
1985
                array(
1986
                    'private_key' => $hashedPrivateKey,
1987
                    'special' => 'none',
1988
                    'otp_provided' => 1,
1989
                ),
1990
                'id = %i',
1991
                $post_user_id
1992
            );
1993
1994
            $session->set('user-private_key', $privateKey);
1995
        }
1996
1997
        // Return
1998
        return prepareExchangedData(
1999
            array(
2000
                'error' => false,
2001
                'message' => '',
2002
            ),
2003
            'encode'
2004
        );
2005
    }
2006
    
2007
    return prepareExchangedData(
2008
        array(
2009
            'error' => true,
2010
            'message' => $lang->get('error_no_user'),
2011
            'debug' => '',
2012
        ),
2013
        'encode'
2014
    );
2015
}
2016
2017
function initializeUserPassword(
2018
    int $post_user_id,
2019
    string $post_special,
2020
    string $post_user_password,
2021
    bool $post_self_change,
2022
    array $SETTINGS
2023
): string
2024
{
2025
    // Load user's language
2026
    $session = SessionManager::getSession();
2027
    $lang = new Language($session->get('user-language') ?? 'english');
2028
    
2029
    if (isUserIdValid($post_user_id) === true) {
2030
        // Get user info
2031
        $userData = DB::queryFirstRow(
2032
            'SELECT email, auth_type, login
2033
            FROM ' . prefixTable('users') . '
2034
            WHERE id = %i',
2035
            $post_user_id
2036
        );
2037
        if (DB::count() > 0 && empty($userData['email']) === false) {
2038
            // If user pwd is empty then generate a new one and send it to user
2039
            if (empty($post_user_password) === true) {
2040
                // Generate new password
2041
                $post_user_password = generateQuickPassword();
2042
            }
2043
2044
            // If LDAP enabled, then
2045
            // check that this password is correct
2046
            $continue = true;
2047
            if ($userData['auth_type'] === 'ldap' && (int) $SETTINGS['ldap_mode'] === 1) {
2048
                $continue = ldapCheckUserPassword(
2049
                    $userData['login'],
2050
                    $post_user_password,
2051
                    $SETTINGS
2052
                );
2053
            }
2054
2055
            if ($continue === true) {
2056
                // Only change if email is successfull
2057
                $passwordManager = new PasswordManager();
2058
                // GEnerate new keys
2059
                $userKeys = generateUserKeys($post_user_password);
2060
2061
                // Update user account
2062
                DB::update(
2063
                    prefixTable('users'),
2064
                    array(
2065
                        'special' => $post_special,
2066
                        'pw' => $passwordManager->hashPassword($post_user_password),
2067
                        'public_key' => $userKeys['public_key'],
2068
                        'private_key' => $userKeys['private_key'],
2069
                        'last_pw_change' => time(),
2070
                    ),
2071
                    'id = %i',
2072
                    $post_user_id
2073
                );
2074
2075
                // Return
2076
                return prepareExchangedData(
2077
                    array(
2078
                        'error' => false,
2079
                        'message' => '',
2080
                        'user_pwd' => $post_user_password,
2081
                        'user_email' => $userData['email'],
2082
                    ),
2083
                    'encode'
2084
                );
2085
            }
2086
            // Return error
2087
            return prepareExchangedData(
2088
                array(
2089
                    'error' => true,
2090
                    'message' => $lang->get('no_email_set'),
2091
                    'debug' => '',
2092
                    'self_change' => $post_self_change,
2093
                ),
2094
                'encode'
2095
            );
2096
        }
2097
2098
        // Error
2099
        return prepareExchangedData(
2100
            array(
2101
                'error' => true,
2102
                'message' => $lang->get('no_email_set'),
2103
                'debug' => '',
2104
            ),
2105
            'encode'
2106
        );
2107
    }
2108
    
2109
    return prepareExchangedData(
2110
        array(
2111
            'error' => true,
2112
            'message' => $lang->get('error_no_user'),
2113
            'debug' => '',
2114
        ),
2115
        'encode'
2116
    );
2117
}
2118
2119
function generateOneTimeCode(
2120
    int $userId
2121
): string
2122
{
2123
    // Load user's language
2124
    $session = SessionManager::getSession();
2125
    $lang = new Language($session->get('user-language') ?? 'english');
2126
2127
    if (isUserIdValid($userId) === true) {
2128
        // Get user info
2129
        $userData = DB::queryFirstRow(
2130
            'SELECT email, auth_type, login
2131
            FROM ' . prefixTable('users') . '
2132
            WHERE id = %i',
2133
            $userId
2134
        );
2135
        if (DB::count() > 0 && empty($userData['email']) === false) {
2136
            // Generate pwd
2137
            $password = generateQuickPassword();
2138
2139
            // GEnerate new keys
2140
            $userKeys = generateUserKeys($password);
2141
2142
            // Save in DB
2143
            DB::update(
2144
                prefixTable('users'),
2145
                array(
2146
                    'public_key' => $userKeys['public_key'],
2147
                    'private_key' => $userKeys['private_key'],
2148
                    'special' => 'generate-keys',
2149
                ),
2150
                'id=%i',
2151
                $userId
2152
            );
2153
2154
            return prepareExchangedData(
2155
                array(
2156
                    'error' => false,
2157
                    'message' => '',
2158
                    'code' => $password,
2159
                    'visible_otp' => ADMIN_VISIBLE_OTP_ON_LDAP_IMPORT,
2160
                ),
2161
                'encode'
2162
            );
2163
        }
2164
        
2165
        return prepareExchangedData(
2166
            array(
2167
                'error' => true,
2168
                'message' => $lang->get('no_email_set'),
2169
            ),
2170
            'encode'
2171
        );
2172
    }
2173
        
2174
    return prepareExchangedData(
2175
        array(
2176
            'error' => true,
2177
            'message' => $lang->get('error_no_user'),
2178
        ),
2179
        'encode'
2180
    );
2181
}
2182
2183
function startReEncryptingUserSharekeys(
2184
    int $post_user_id,
2185
    bool $post_self_change,
2186
    array $SETTINGS
2187
): string
2188
{
2189
    // Load user's language
2190
    $session = SessionManager::getSession();
2191
    $lang = new Language($session->get('user-language') ?? 'english');
2192
    
2193
    if (isUserIdValid($post_user_id) === true) {
2194
        // Check if user exists
2195
        DB::queryFirstRow(
2196
            'SELECT *
2197
            FROM ' . prefixTable('users') . '
2198
            WHERE id = %i',
2199
            $post_user_id
2200
        );
2201
        if (DB::count() > 0) {
2202
            // CLear old sharekeys
2203
            if ($post_self_change === false) {
2204
                deleteUserObjetsKeys($post_user_id, $SETTINGS);
2205
            }
2206
2207
            // Continu with next step
2208
            return prepareExchangedData(
2209
                array(
2210
                    'error' => false,
2211
                    'message' => '',
2212
                    'step' => 'step1',
2213
                    'userId' => $post_user_id,
2214
                    'start' => 0,
2215
                    'self_change' => $post_self_change,
2216
                ),
2217
                'encode'
2218
            );
2219
        }
2220
        // Nothing to do
2221
        return prepareExchangedData(
2222
            array(
2223
                'error' => true,
2224
                'message' => $lang->get('error_no_user'),
2225
            ),
2226
            'encode'
2227
        );
2228
    }
2229
2230
    return prepareExchangedData(
2231
        array(
2232
            'error' => true,
2233
            'message' => $lang->get('error_no_user'),
2234
        ),
2235
        'encode'
2236
    );
2237
}
2238
2239
/**
2240
 * Permits to encrypt user's keys
2241
 *
2242
 * @param integer $post_user_id
2243
 * @param boolean $post_self_change
2244
 * @param string $post_action
2245
 * @param integer $post_start
2246
 * @param integer $post_length
2247
 * @param array $SETTINGS
2248
 * @return string
2249
 */
2250
function continueReEncryptingUserSharekeys(
2251
    int     $post_user_id,
2252
    bool    $post_self_change,
2253
    string  $post_action,
2254
    int     $post_start,
2255
    int     $post_length,
2256
    array   $SETTINGS
2257
): string
2258
{
2259
    // Load user's language
2260
    $session = SessionManager::getSession();
2261
    $lang = new Language($session->get('user-language') ?? 'english');
2262
    
2263
    if (isUserIdValid($post_user_id) === true) {
2264
        // Check if user exists
2265
        $userInfo = DB::queryFirstRow(
2266
            'SELECT public_key
2267
            FROM ' . prefixTable('users') . '
2268
            WHERE id = %i',
2269
            $post_user_id
2270
        );
2271
        if (isset($userInfo['public_key']) === true) {
2272
            $return = [];
2273
2274
            // WHAT STEP TO PERFORM?
2275
            if ($post_action === 'step0') {
2276
                // CLear old sharekeys
2277
                if ($post_self_change === false) {
2278
                    deleteUserObjetsKeys($post_user_id, $SETTINGS);
2279
                }
2280
2281
                $return['post_action'] = 'step10';
2282
            }
2283
            
2284
            // STEP 1 - ITEMS
2285
            elseif ($post_action === 'step10') {
2286
                $return = continueReEncryptingUserSharekeysStep10(
2287
                    $post_user_id,
2288
                    $post_self_change,
2289
                    $post_action,
2290
                    $post_start,
2291
                    $post_length,
2292
                    $userInfo['public_key'],
2293
                    $SETTINGS
2294
                );
2295
            }
2296
2297
            // STEP 2 - LOGS
2298
            elseif ($post_action === 'step20') {
2299
                $return = continueReEncryptingUserSharekeysStep20(
2300
                    $post_user_id,
2301
                    $post_self_change,
2302
                    $post_action,
2303
                    $post_start,
2304
                    $post_length,
2305
                    $userInfo['public_key'],
2306
                    $SETTINGS
2307
                );
2308
            }
2309
2310
            // STEP 3 - FIELDS
2311
            elseif ($post_action === 'step30') {
2312
                $return = continueReEncryptingUserSharekeysStep30(
2313
                    $post_user_id,
2314
                    $post_self_change,
2315
                    $post_action,
2316
                    $post_start,
2317
                    $post_length,
2318
                    $userInfo['public_key'],
2319
                    $SETTINGS
2320
                );
2321
            }
2322
            
2323
            // STEP 4 - SUGGESTIONS
2324
            elseif ($post_action === 'step40') {
2325
                $return = continueReEncryptingUserSharekeysStep40(
2326
                    $post_user_id,
2327
                    $post_self_change,
2328
                    $post_action,
2329
                    $post_start,
2330
                    $post_length,
2331
                    $userInfo['public_key'],
2332
                    $SETTINGS
2333
                );
2334
            }
2335
            
2336
            // STEP 5 - FILES
2337
            elseif ($post_action === 'step50') {
2338
                $return = continueReEncryptingUserSharekeysStep50(
2339
                    $post_user_id,
2340
                    $post_self_change,
2341
                    $post_action,
2342
                    $post_start,
2343
                    $post_length,
2344
                    $userInfo['public_key'],
2345
                    $SETTINGS
2346
                );
2347
            }
2348
            
2349
            // STEP 6 - PERSONAL ITEMS
2350
            elseif ($post_action === 'step60') {
2351
                $return = continueReEncryptingUserSharekeysStep60(
2352
                    $post_user_id,
2353
                    $post_self_change,
2354
                    $post_action,
2355
                    $post_start,
2356
                    $post_length,
2357
                    $userInfo['public_key'],
2358
                    $SETTINGS
2359
                );
2360
            }
2361
            
2362
            // Continu with next step
2363
            return prepareExchangedData(
2364
                array(
2365
                    'error' => false,
2366
                    'message' => '',
2367
                    'step' => isset($return['post_action']) === true ? $return['post_action'] : '',
2368
                    'start' => isset($return['next_start']) === true ? $return['next_start'] : 0,
2369
                    'userId' => $post_user_id,
2370
                    'self_change' => $post_self_change,
2371
                ),
2372
                'encode'
2373
            );
2374
        }
2375
        
2376
        // Nothing to do
2377
        return prepareExchangedData(
2378
            array(
2379
                'error' => false,
2380
                'message' => '',
2381
                'step' => 'finished',
2382
                'start' => 0,
2383
                'userId' => $post_user_id,
2384
                'self_change' => $post_self_change,
2385
            ),
2386
            'encode'
2387
        );
2388
    }
2389
    
2390
    // Nothing to do
2391
    return prepareExchangedData(
2392
        array(
2393
            'error' => true,
2394
            'message' => $lang->get('error_no_user'),
2395
            'extra' => $post_user_id,
2396
        ),
2397
        'encode'
2398
    );
2399
}
2400
2401
function continueReEncryptingUserSharekeysStep10(
2402
    int $post_user_id,
2403
    bool $post_self_change,
2404
    string $post_action,
2405
    int $post_start,
2406
    int $post_length,
2407
    string $user_public_key,
2408
    array $SETTINGS
2409
): array 
2410
{
2411
    $session = SessionManager::getSession();
2412
    // Loop on items
2413
    $rows = DB::query(
2414
        'SELECT id, pw
2415
        FROM ' . prefixTable('items') . '
2416
        WHERE perso = 0
2417
        LIMIT ' . $post_start . ', ' . $post_length
2418
    );
2419
    foreach ($rows as $record) {
2420
        // Get itemKey from current user
2421
        $currentUserKey = DB::queryFirstRow(
2422
            'SELECT share_key, increment_id
2423
            FROM ' . prefixTable('sharekeys_items') . '
2424
            WHERE object_id = %i AND user_id = %i',
2425
            $record['id'],
2426
            $session->get('user-id')
2427
        );
2428
2429
        // do we have any input? (#3481)
2430
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2431
            continue;
2432
        }
2433
2434
        // Decrypt itemkey with admin key
2435
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2436
        
2437
        // Encrypt Item key
2438
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2439
        
2440
        // Save the key in DB
2441
        if ($post_self_change === false) {
2442
            DB::insert(
2443
                prefixTable('sharekeys_items'),
2444
                array(
2445
                    'object_id' => (int) $record['id'],
2446
                    'user_id' => (int) $post_user_id,
2447
                    'share_key' => $share_key_for_item,
2448
                )
2449
            );
2450
        } else {
2451
            // Get itemIncrement from selected user
2452
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2453
                $currentUserKey = DB::queryFirstRow(
2454
                    'SELECT increment_id
2455
                    FROM ' . prefixTable('sharekeys_items') . '
2456
                    WHERE object_id = %i AND user_id = %i',
2457
                    $record['id'],
2458
                    $post_user_id
2459
                );
2460
2461
                if (DB::count() > 0) {
2462
                    // NOw update
2463
                    DB::update(
2464
                        prefixTable('sharekeys_items'),
2465
                        array(
2466
                            'share_key' => $share_key_for_item,
2467
                        ),
2468
                        'increment_id = %i',
2469
                        $currentUserKey['increment_id']
2470
                    );
2471
                } else {
2472
                    DB::insert(
2473
                        prefixTable('sharekeys_items'),
2474
                        array(
2475
                            'object_id' => (int) $record['id'],
2476
                            'user_id' => (int) $post_user_id,
2477
                            'share_key' => $share_key_for_item,
2478
                        )
2479
                    );
2480
                }
2481
            }
2482
        }
2483
    }
2484
2485
    // SHould we change step?
2486
    DB::query(
2487
        'SELECT *
2488
        FROM ' . prefixTable('items') . '
2489
        WHERE perso = 0'
2490
    );
2491
2492
    $next_start = (int) $post_start + (int) $post_length;
2493
    return [
2494
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2495
        'post_action' => $next_start > DB::count() ? 'step20' : 'step10',
2496
    ];
2497
}
2498
2499
function continueReEncryptingUserSharekeysStep20(
2500
    int $post_user_id,
2501
    bool $post_self_change,
2502
    string $post_action,
2503
    int $post_start,
2504
    int $post_length,
2505
    string $user_public_key,
2506
    array $SETTINGS
2507
): array
2508
{
2509
    $session = SessionManager::getSession();
2510
    // Loop on logs
2511
    $rows = DB::query(
2512
        'SELECT increment_id
2513
        FROM ' . prefixTable('log_items') . '
2514
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"
2515
        LIMIT ' . $post_start . ', ' . $post_length
2516
    );
2517
    foreach ($rows as $record) {
2518
        // Get itemKey from current user
2519
        $currentUserKey = DB::queryFirstRow(
2520
            'SELECT share_key
2521
            FROM ' . prefixTable('sharekeys_logs') . '
2522
            WHERE object_id = %i AND user_id = %i',
2523
            $record['increment_id'],
2524
            $session->get('user-id')
2525
        );
2526
2527
        // do we have any input? (#3481)
2528
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2529
            continue;
2530
        }
2531
2532
        // Decrypt itemkey with admin key
2533
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2534
2535
        // Encrypt Item key
2536
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2537
2538
        // Save the key in DB
2539
        if ($post_self_change === false) {
2540
            DB::insert(
2541
                prefixTable('sharekeys_logs'),
2542
                array(
2543
                    'object_id' => (int) $record['increment_id'],
2544
                    'user_id' => (int) $post_user_id,
2545
                    'share_key' => $share_key_for_item,
2546
                )
2547
            );
2548
        } else {
2549
            // Get itemIncrement from selected user
2550
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2551
                $currentUserKey = DB::queryFirstRow(
2552
                    'SELECT increment_id
2553
                    FROM ' . prefixTable('sharekeys_items') . '
2554
                    WHERE object_id = %i AND user_id = %i',
2555
                    $record['id'],
2556
                    $post_user_id
2557
                );
2558
            }
2559
2560
            // NOw update
2561
            DB::update(
2562
                prefixTable('sharekeys_logs'),
2563
                array(
2564
                    'share_key' => $share_key_for_item,
2565
                ),
2566
                'increment_id = %i',
2567
                $currentUserKey['increment_id']
2568
            );
2569
        }
2570
    }
2571
2572
    // SHould we change step?
2573
    DB::query(
2574
        'SELECT increment_id
2575
        FROM ' . prefixTable('log_items') . '
2576
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"'
2577
    );
2578
2579
    $next_start = (int) $post_start + (int) $post_length;
2580
    return [
2581
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2582
        'post_action' => $next_start > DB::count() ? 'step30' : 'step20',
2583
    ];
2584
}
2585
2586
function continueReEncryptingUserSharekeysStep30(
2587
    int $post_user_id,
2588
    bool $post_self_change,
2589
    string $post_action,
2590
    int $post_start,
2591
    int $post_length,
2592
    string $user_public_key,
2593
    array $SETTINGS
2594
): array
2595
{
2596
    $session = SessionManager::getSession();
2597
    // Loop on fields
2598
    $rows = DB::query(
2599
        'SELECT id
2600
        FROM ' . prefixTable('categories_items') . '
2601
        WHERE encryption_type = "teampass_aes"
2602
        LIMIT ' . $post_start . ', ' . $post_length
2603
    );
2604
    foreach ($rows as $record) {
2605
        // Get itemKey from current user
2606
        $currentUserKey = DB::queryFirstRow(
2607
            'SELECT share_key
2608
            FROM ' . prefixTable('sharekeys_fields') . '
2609
            WHERE object_id = %i AND user_id = %i',
2610
            $record['id'],
2611
            $session->get('user-id')
2612
        );
2613
2614
        // do we have any input? (#3481)
2615
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2616
            continue;
2617
        }
2618
2619
        // Decrypt itemkey with admin key
2620
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2621
2622
        // Encrypt Item key
2623
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2624
2625
        // Save the key in DB
2626
        if ($post_self_change === false) {
2627
            DB::insert(
2628
                prefixTable('sharekeys_fields'),
2629
                array(
2630
                    'object_id' => (int) $record['id'],
2631
                    'user_id' => (int) $post_user_id,
2632
                    'share_key' => $share_key_for_item,
2633
                )
2634
            );
2635
        } else {
2636
            // Get itemIncrement from selected user
2637
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2638
                $currentUserKey = DB::queryFirstRow(
2639
                    'SELECT increment_id
2640
                    FROM ' . prefixTable('sharekeys_items') . '
2641
                    WHERE object_id = %i AND user_id = %i',
2642
                    $record['id'],
2643
                    $post_user_id
2644
                );
2645
            }
2646
2647
            // NOw update
2648
            DB::update(
2649
                prefixTable('sharekeys_fields'),
2650
                array(
2651
                    'share_key' => $share_key_for_item,
2652
                ),
2653
                'increment_id = %i',
2654
                $currentUserKey['increment_id']
2655
            );
2656
        }
2657
    }
2658
2659
    // SHould we change step?
2660
    DB::query(
2661
        'SELECT *
2662
        FROM ' . prefixTable('categories_items') . '
2663
        WHERE encryption_type = "teampass_aes"'
2664
    );
2665
2666
    $next_start = (int) $post_start + (int) $post_length;
2667
    return [
2668
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2669
        'post_action' => $next_start > DB::count() ? 'step40' : 'step30',
2670
    ];
2671
}
2672
2673
function continueReEncryptingUserSharekeysStep40(
2674
    int $post_user_id,
2675
    bool $post_self_change,
2676
    string $post_action,
2677
    int $post_start,
2678
    int $post_length,
2679
    string $user_public_key,
2680
    array $SETTINGS
2681
): array
2682
{
2683
    $session = SessionManager::getSession();
2684
    // Loop on suggestions
2685
    $rows = DB::query(
2686
        'SELECT id
2687
        FROM ' . prefixTable('suggestion') . '
2688
        LIMIT ' . $post_start . ', ' . $post_length
2689
    );
2690
    foreach ($rows as $record) {
2691
        // Get itemKey from current user
2692
        $currentUserKey = DB::queryFirstRow(
2693
            'SELECT share_key
2694
            FROM ' . prefixTable('sharekeys_suggestions') . '
2695
            WHERE object_id = %i AND user_id = %i',
2696
            $record['id'],
2697
            $session->get('user-id')
2698
        );
2699
2700
        // do we have any input? (#3481)
2701
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2702
            continue;
2703
        }
2704
2705
        // Decrypt itemkey with admin key
2706
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2707
2708
        // Encrypt Item key
2709
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2710
2711
        // Save the key in DB
2712
        if ($post_self_change === false) {
2713
            DB::insert(
2714
                prefixTable('sharekeys_suggestions'),
2715
                array(
2716
                    'object_id' => (int) $record['id'],
2717
                    'user_id' => (int) $post_user_id,
2718
                    'share_key' => $share_key_for_item,
2719
                )
2720
            );
2721
        } else {
2722
            // Get itemIncrement from selected user
2723
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2724
                $currentUserKey = DB::queryFirstRow(
2725
                    'SELECT increment_id
2726
                    FROM ' . prefixTable('sharekeys_items') . '
2727
                    WHERE object_id = %i AND user_id = %i',
2728
                    $record['id'],
2729
                    $post_user_id
2730
                );
2731
            }
2732
2733
            // NOw update
2734
            DB::update(
2735
                prefixTable('sharekeys_suggestions'),
2736
                array(
2737
                    'share_key' => $share_key_for_item,
2738
                ),
2739
                'increment_id = %i',
2740
                $currentUserKey['increment_id']
2741
            );
2742
        }
2743
    }
2744
2745
    // SHould we change step?
2746
    DB::query(
2747
        'SELECT *
2748
        FROM ' . prefixTable('suggestion')
2749
    );
2750
2751
    $next_start = (int) $post_start + (int) $post_length;
2752
    return [
2753
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2754
        'post_action' => $next_start > DB::count() ? 'step50' : 'step40',
2755
    ];
2756
}
2757
2758
function continueReEncryptingUserSharekeysStep50(
2759
    int $post_user_id,
2760
    bool $post_self_change,
2761
    string $post_action,
2762
    int $post_start,
2763
    int $post_length,
2764
    string $user_public_key,
2765
    array $SETTINGS
2766
): array
2767
{
2768
    $session = SessionManager::getSession();
2769
    // Loop on files
2770
    $rows = DB::query(
2771
        'SELECT id
2772
        FROM ' . prefixTable('files') . '
2773
        WHERE status = "' . TP_ENCRYPTION_NAME . '"
2774
        LIMIT ' . $post_start . ', ' . $post_length
2775
    ); //aes_encryption
2776
    foreach ($rows as $record) {
2777
        // Get itemKey from current user
2778
        $currentUserKey = DB::queryFirstRow(
2779
            'SELECT share_key
2780
            FROM ' . prefixTable('sharekeys_files') . '
2781
            WHERE object_id = %i AND user_id = %i',
2782
            $record['id'],
2783
            $session->get('user-id')
2784
        );
2785
2786
        // do we have any input? (#3481)
2787
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2788
            continue;
2789
        }
2790
2791
        // Decrypt itemkey with admin key
2792
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2793
2794
        // Encrypt Item key
2795
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2796
2797
        // Save the key in DB
2798
        if ($post_self_change === false) {
2799
            DB::insert(
2800
                prefixTable('sharekeys_files'),
2801
                array(
2802
                    'object_id' => (int) $record['id'],
2803
                    'user_id' => (int) $post_user_id,
2804
                    'share_key' => $share_key_for_item,
2805
                )
2806
            );
2807
        } else {
2808
            // Get itemIncrement from selected user
2809
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2810
                $currentUserKey = DB::queryFirstRow(
2811
                    'SELECT increment_id
2812
                    FROM ' . prefixTable('sharekeys_items') . '
2813
                    WHERE object_id = %i AND user_id = %i',
2814
                    $record['id'],
2815
                    $post_user_id
2816
                );
2817
            }
2818
2819
            // NOw update
2820
            DB::update(
2821
                prefixTable('sharekeys_files'),
2822
                array(
2823
                    'share_key' => $share_key_for_item,
2824
                ),
2825
                'increment_id = %i',
2826
                $currentUserKey['increment_id']
2827
            );
2828
        }
2829
    }
2830
2831
    // SHould we change step?
2832
    DB::query(
2833
        'SELECT *
2834
        FROM ' . prefixTable('files') . '
2835
        WHERE status = "' . TP_ENCRYPTION_NAME . '"'
2836
    );
2837
2838
    $next_start = (int) $post_start + (int) $post_length;
2839
    return [
2840
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2841
        'post_action' => $next_start > DB::count() ? 'step60' : 'step50',
2842
    ];
2843
}
2844
2845
function continueReEncryptingUserSharekeysStep60(
2846
    int $post_user_id,
2847
    bool $post_self_change,
2848
    string $post_action,
2849
    int $post_start,
2850
    int $post_length,
2851
    string $user_public_key,
2852
    array $SETTINGS
2853
): array
2854
{
2855
    $session = SessionManager::getSession();
2856
    // IF USER IS NOT THE SAME
2857
    if ((int) $post_user_id === (int) $session->get('user-id')) {
2858
        return [
2859
            'next_start' => 0,
2860
            'post_action' => 'finished',
2861
        ];
2862
    }
2863
    
2864
    // Loop on persoanl items
2865
    if (count($session->get('user-personal_folders')) > 0) {
2866
        $rows = DB::query(
2867
            'SELECT id, pw
2868
            FROM ' . prefixTable('items') . '
2869
            WHERE perso = 1 AND id_tree IN %ls AND encryption_type = %s
2870
            LIMIT ' . $post_start . ', ' . $post_length,
2871
            $session->get('user-personal_folders'),
2872
            "defuse"
2873
        );
2874
        foreach ($rows as $record) {
2875
            // Get itemKey from current user
2876
            $currentUserKey = DB::queryFirstRow(
2877
                'SELECT share_key, increment_id
2878
                FROM ' . prefixTable('sharekeys_items') . '
2879
                WHERE object_id = %i AND user_id = %i',
2880
                $record['id'],
2881
                $session->get('user-id')
2882
            );
2883
2884
            // Decrypt itemkey with admin key
2885
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2886
2887
            // Encrypt Item key
2888
            $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2889
2890
            // Save the key in DB
2891
            if ($post_self_change === false) {
2892
                DB::insert(
2893
                    prefixTable('sharekeys_items'),
2894
                    array(
2895
                        'object_id' => (int) $record['id'],
2896
                        'user_id' => (int) $post_user_id,
2897
                        'share_key' => $share_key_for_item,
2898
                    )
2899
                );
2900
            } else {
2901
                // Get itemIncrement from selected user
2902
                if ((int) $post_user_id !== (int) $session->get('user-id')) {
2903
                    $currentUserKey = DB::queryFirstRow(
2904
                        'SELECT increment_id
2905
                        FROM ' . prefixTable('sharekeys_items') . '
2906
                        WHERE object_id = %i AND user_id = %i',
2907
                        $record['id'],
2908
                        $post_user_id
2909
                    );
2910
                }
2911
2912
                // NOw update
2913
                DB::update(
2914
                    prefixTable('sharekeys_items'),
2915
                    array(
2916
                        'share_key' => $share_key_for_item,
2917
                    ),
2918
                    'increment_id = %i',
2919
                    $currentUserKey['increment_id']
2920
                );
2921
            }
2922
        }
2923
    }
2924
2925
    // SHould we change step?
2926
    DB::query(
2927
        'SELECT *
2928
        FROM ' . prefixTable('items') . '
2929
        WHERE perso = 0'
2930
    );
2931
2932
    $next_start = (int) $post_start + (int) $post_length;
2933
    return [
2934
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2935
        'post_action' => $next_start > DB::count() ? 'finished' : 'step60',
2936
    ];
2937
}
2938
2939
function migrateTo3_DoUserPersonalItemsEncryption(
2940
    int $post_user_id,
2941
    int $post_start,
2942
    int $post_length,
2943
    int $post_counterItemsToTreat,
2944
    string $post_user_psk,
2945
    array $SETTINGS
2946
) {
2947
    $next_step = 'psk';
2948
    
2949
    $session = SessionManager::getSession();
2950
    $lang = new Language($session->get('user-language') ?? 'english');
2951
    
2952
    if (isUserIdValid($post_user_id) === true) {
2953
        // Check if user exists
2954
        $userInfo = DB::queryFirstRow(
2955
            'SELECT public_key, encrypted_psk
2956
            FROM ' . prefixTable('users') . '
2957
            WHERE id = %i',
2958
            $post_user_id
2959
        );
2960
        if (DB::count() > 0) {
2961
            // check if psk is correct.
2962
            if (empty($userInfo['encrypted_psk']) === false) {//echo $post_user_psk." ;; ".$userInfo['encrypted_psk']." ;; ";
2963
                $user_key_encoded = defuse_validate_personal_key(
2964
                    html_entity_decode($post_user_psk), // convert tspecial string back to their original characters due to FILTER_SANITIZE_FULL_SPECIAL_CHARS
2965
                    $userInfo['encrypted_psk']
2966
                );
2967
2968
                if (strpos($user_key_encoded, "Error ") !== false) {
2969
                    return prepareExchangedData(
2970
                        array(
2971
                            'error' => true,
2972
                            'message' => $lang->get('bad_psk'),
2973
                        ),
2974
                        'encode'
2975
                    );
2976
                }
2977
2978
                // Get number of user's personal items with no AES encryption
2979
                if ($post_counterItemsToTreat === -1) {
2980
                    DB::query(
2981
                        'SELECT id
2982
                        FROM ' . prefixTable('items') . '
2983
                        WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
2984
                        $session->get('user-personal_folders'),
2985
                        'teampass_aes'
2986
                    );
2987
                    $countUserPersonalItems = DB::count();
2988
                } else {
2989
                    $countUserPersonalItems = $post_counterItemsToTreat;
2990
                }
2991
2992
                // Loop on persoanl items
2993
                $rows = DB::query(
2994
                    'SELECT id, pw
2995
                    FROM ' . prefixTable('items') . '
2996
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s
2997
                    LIMIT ' . $post_length,
2998
                    $session->get('user-personal_folders'),
2999
                    'teampass_aes'
3000
                );
3001
                foreach ($rows as $record) {
3002
                    // Decrypt with Defuse
3003
                    $passwd = cryption(
3004
                        $record['pw'],
3005
                        $user_key_encoded,
3006
                        'decrypt',
3007
                        $SETTINGS
3008
                    );
3009
3010
                    // Encrypt with Object Key
3011
                    $cryptedStuff = doDataEncryption(html_entity_decode($passwd['string']));
3012
3013
                    // Store new password in DB
3014
                    DB::update(
3015
                        prefixTable('items'),
3016
                        array(
3017
                            'pw' => $cryptedStuff['encrypted'],
3018
                            'encryption_type' => 'teampass_aes',
3019
                        ),
3020
                        'id = %i',
3021
                        $record['id']
3022
                    );
3023
3024
                    // Insert in DB the new object key for this item by user
3025
                    DB::insert(
3026
                        prefixTable('sharekeys_items'),
3027
                        array(
3028
                            'object_id' => (int) $record['id'],
3029
                            'user_id' => (int) $post_user_id,
3030
                            'share_key' => encryptUserObjectKey($cryptedStuff['objectKey'], $userInfo['public_key']),
3031
                        )
3032
                    );
3033
3034
3035
                    // Does this item has Files?
3036
                    // Loop on files
3037
                    $rows = DB::query(
3038
                        'SELECT id, file
3039
                        FROM ' . prefixTable('files') . '
3040
                        WHERE status != %s
3041
                        AND id_item = %i',
3042
                        TP_ENCRYPTION_NAME,
3043
                        $record['id']
3044
                    );
3045
                    //aes_encryption
3046
                    foreach ($rows as $record2) {
3047
                        // Now decrypt the file
3048
                        prepareFileWithDefuse(
3049
                            'decrypt',
3050
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'],
3051
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'] . '.delete',
3052
                            $post_user_psk
3053
                        );
3054
3055
                        // Encrypt the file
3056
                        $encryptedFile = encryptFile($record2['file'] . '.delete', $SETTINGS['path_to_upload_folder']);
3057
3058
                        DB::update(
3059
                            prefixTable('files'),
3060
                            array(
3061
                                'file' => $encryptedFile['fileHash'],
3062
                                'status' => TP_ENCRYPTION_NAME,
3063
                            ),
3064
                            'id = %i',
3065
                            $record2['id']
3066
                        );
3067
3068
                        // Save key
3069
                        DB::insert(
3070
                            prefixTable('sharekeys_files'),
3071
                            array(
3072
                                'object_id' => (int) $record2['id'],
3073
                                'user_id' => (int) $session->get('user-id'),
3074
                                'share_key' => encryptUserObjectKey($encryptedFile['objectKey'], $session->get('user-public_key')),
3075
                            )
3076
                        );
3077
3078
                        // Unlink original file
3079
                        unlink($SETTINGS['path_to_upload_folder'] . '/' . $record2['file']);
3080
                    }
3081
                }
3082
3083
                // SHould we change step?
3084
                $next_start = (int) $post_start + (int) $post_length;
3085
                DB::query(
3086
                    'SELECT id
3087
                    FROM ' . prefixTable('items') . '
3088
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
3089
                    $session->get('user-personal_folders'),
3090
                    'teampass_aes'
3091
                );
3092
                if (DB::count() === 0 || ($next_start - $post_length) >= $countUserPersonalItems) {
3093
                    // Now update user
3094
                    DB::update(
3095
                        prefixTable('users'),
3096
                        array(
3097
                            'special' => 'none',
3098
                            'upgrade_needed' => 0,
3099
                            'encrypted_psk' => '',
3100
                        ),
3101
                        'id = %i',
3102
                        $post_user_id
3103
                    );
3104
3105
                    $next_step = 'finished';
3106
                    $next_start = 0;
3107
                }
3108
3109
                // Continu with next step
3110
                return prepareExchangedData(
3111
                    array(
3112
                        'error' => false,
3113
                        'message' => '',
3114
                        'step' => $next_step,
3115
                        'start' => $next_start,
3116
                        'userId' => $post_user_id
3117
                    ),
3118
                    'encode'
3119
                );
3120
            }
3121
        }
3122
        
3123
        // Nothing to do
3124
        return prepareExchangedData(
3125
            array(
3126
                'error' => true,
3127
                'message' => $lang->get('error_no_user'),
3128
            ),
3129
            'encode'
3130
        );
3131
    }
3132
    
3133
    // Nothing to do
3134
    return prepareExchangedData(
3135
        array(
3136
            'error' => true,
3137
            'message' => $lang->get('error_no_user'),
3138
        ),
3139
        'encode'
3140
    );
3141
}
3142
3143
3144
function getUserInfo(
3145
    int $post_user_id,
3146
    array $SETTINGS
3147
)
3148
{
3149
    // Load user's language
3150
    $session = SessionManager::getSession();
3151
    $lang = new Language($session->get('user-language') ?? 'english');
3152
    
3153
    if (isUserIdValid($post_user_id) === true) {
3154
        // Get user info
3155
        $userData = DB::queryFirstRow(
3156
            'SELECT special, auth_type, is_ready_for_usage, ongoing_process_id, otp_provided, keys_recovery_time
3157
            FROM ' . prefixTable('users') . '
3158
            WHERE id = %i',
3159
            $post_user_id
3160
        );
3161
        if (DB::count() > 0) {
3162
            return prepareExchangedData(
3163
                array(
3164
                    'error' => false,
3165
                    'message' => '',
3166
                    'queryResults' => $userData,
3167
                ),
3168
                'encode'
3169
            );
3170
        }
3171
    }
3172
    return prepareExchangedData(
3173
        array(
3174
            'error' => true,
3175
            'message' => $lang->get('error_no_user'),
3176
        ),
3177
        'encode'
3178
    );
3179
}
3180
3181
/**
3182
 * Change user auth password
3183
 *
3184
 * @param integer $post_user_id
3185
 * @param string $post_current_pwd
3186
 * @param string $post_new_pwd
3187
 * @param array $SETTINGS
3188
 * @return string
3189
 */
3190
function changeUserAuthenticationPassword(
3191
    int $post_user_id,
3192
    string $post_current_pwd,
3193
    string $post_new_pwd,
3194
    array $SETTINGS
3195
)
3196
{
3197
    $session = SessionManager::getSession();
3198
    $lang = new Language($session->get('user-language') ?? 'english');
3199
 
3200
    if (isUserIdValid($post_user_id) === true) {
3201
        // Get user info
3202
        $userData = DB::queryFirstRow(
3203
            'SELECT auth_type, login, private_key
3204
            FROM ' . prefixTable('users') . '
3205
            WHERE id = %i',
3206
            $post_user_id
3207
        );
3208
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
3209
            // Now check if current password is correct
3210
            // For this, just check if it is possible to decrypt the privatekey
3211
            // And compare it to the one in session
3212
            try {
3213
                $privateKey = decryptPrivateKey($post_current_pwd, $userData['private_key']);
3214
            } catch (Exception $e) {
3215
                return prepareExchangedData(
3216
                    array(
3217
                        'error' => true,
3218
                        'message' => $lang->get('bad_password'),
3219
                    ),
3220
                    'encode'
3221
                );
3222
            }
3223
3224
            $lang = new Language($session->get('user-language') ?? 'english');
3225
3226
            if ($session->get('user-private_key') === $privateKey) {
3227
                // Encrypt it with new password
3228
                $hashedPrivateKey = encryptPrivateKey($post_new_pwd, $privateKey);
3229
3230
                // Generate new hash for auth password
3231
                $passwordManager = new PasswordManager();
3232
3233
                // Prepare variables
3234
                $newPw = $passwordManager->hashPassword($post_new_pwd);
3235
3236
                // Update user account
3237
                DB::update(
3238
                    prefixTable('users'),
3239
                    array(
3240
                        'private_key' => $hashedPrivateKey,
3241
                        'pw' => $newPw,
3242
                        'special' => 'none',
3243
                        'last_pw_change' => time(),
3244
                    ),
3245
                    'id = %i',
3246
                    $post_user_id
3247
                );
3248
3249
                $session->set('user-private_key', $privateKey);
3250
3251
                return prepareExchangedData(
3252
                    array(
3253
                        'error' => false,
3254
                        'message' => $lang->get('done'),'',
3255
                    ),
3256
                    'encode'
3257
                );
3258
            }
3259
            
3260
            // ERROR
3261
            return prepareExchangedData(
3262
                array(
3263
                    'error' => true,
3264
                    'message' => $lang->get('bad_password'),
3265
                ),
3266
                'encode'
3267
            );
3268
        }
3269
    }
3270
        
3271
    return prepareExchangedData(
3272
        array(
3273
            'error' => true,
3274
            'message' => $lang->get('error_no_user'),
3275
        ),
3276
        'encode'
3277
    );
3278
}
3279
3280
/**
3281
 * Change user LDAP auth password
3282
 *
3283
 * @param integer $post_user_id
3284
 * @param string $post_previous_pwd
3285
 * @param string $post_current_pwd
3286
 * @param string $post_action_type
3287
 * @return string
3288
 */            
3289
function changeUserLDAPAuthenticationPassword(
3290
    int $post_user_id,
3291
    string $post_previous_pwd,
3292
    string $post_current_pwd
3293
)
3294
{
3295
    $session = SessionManager::getSession();
3296
    // Load user's language
3297
    $lang = new Language($session->get('user-language') ?? 'english');
3298
    
3299
    if (isUserIdValid($post_user_id) === true) {
3300
        // Get user info
3301
        $userData = DB::queryFirstRow(
3302
            'SELECT u.auth_type, u.login, u.private_key, u.special
3303
            FROM ' . prefixTable('users') . ' AS u
3304
            WHERE u.id = %i',
3305
            $post_user_id
3306
        );
3307
3308
        if (DB::count() > 0) {
3309
            // 
3310
            if ($userData['auth_type'] === 'ldap' && empty($userData['private_key']) === false && $userData['special'] === 'encrypt_personal_items') {
3311
                // Now check if current password is correct (only if not ldap)
3312
                if ($userData['special'] === 'auth-pwd-change') {
3313
                    // As it is a change for an LDAP user
3314
                    
3315
                    // Now check if current password is correct
3316
                    // For this, just check if it is possible to decrypt the privatekey
3317
                    // And compare it to the one in session
3318
                    $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3319
3320
                    // Encrypt it with new password
3321
                    $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3322
3323
                    // Update user account
3324
                    DB::update(
3325
                        prefixTable('users'),
3326
                        array(
3327
                            'private_key' => $hashedPrivateKey,
3328
                            'special' => 'none',
3329
                        ),
3330
                        'id = %i',
3331
                        $post_user_id
3332
                    );
3333
3334
                    $session->set('user-private_key', $privateKey);
3335
3336
                    return prepareExchangedData(
3337
                        array(
3338
                            'error' => false,
3339
                            'message' => $lang->get('done'),'',
3340
                        ),
3341
                        'encode'
3342
                    );
3343
                }
3344
3345
                // For this, just check if it is possible to decrypt the privatekey
3346
                // And try to decrypt one existing key
3347
                $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3348
3349
                if (empty($privateKey) === true) {
3350
                    return prepareExchangedData(
3351
                        array(
3352
                            'error' => true,
3353
                            'message' => $lang->get('password_is_not_correct'),
3354
                        ),
3355
                        'encode'
3356
                    );
3357
                }
3358
                // Get one itemKey from current user
3359
                $currentUserKey = DB::queryFirstRow(
3360
                    'SELECT ski.share_key, ski.increment_id, l.id_user
3361
                    FROM ' . prefixTable('sharekeys_items') . ' AS ski
3362
                    INNER JOIN ' . prefixTable('log_items') . ' AS l ON ski.object_id = l.id_item
3363
                    WHERE ski.user_id = %i
3364
                    ORDER BY RAND()
3365
                    LIMIT 1',
3366
                    $post_user_id
3367
                );
3368
3369
                if (is_countable($currentUserKey) && count($currentUserKey) > 0) {
3370
                    // Decrypt itemkey with user key
3371
                    // use old password to decrypt private_key
3372
                    $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $privateKey);
3373
                    
3374
                    if (empty(base64_decode($itemKey)) === false) {
3375
                        // GOOD password
3376
                        // Encrypt it with current password
3377
                        $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3378
                        
3379
                        // Update user account
3380
                        DB::update(
3381
                            prefixTable('users'),
3382
                            array(
3383
                                'private_key' => $hashedPrivateKey,
3384
                                'special' => 'none',
3385
                            ),
3386
                            'id = %i',
3387
                            $post_user_id
3388
                        );
3389
                        
3390
                        $lang = new Language($session->get('user-language') ?? 'english');
3391
                        $session->set('user-private_key', $privateKey);
3392
3393
                        return prepareExchangedData(
3394
                            array(
3395
                                'error' => false,
3396
                                'message' => $lang->get('done'),
3397
                            ),
3398
                            'encode'
3399
                        );
3400
                    }
3401
                }
3402
                
3403
                // ERROR
3404
                return prepareExchangedData(
3405
                    array(
3406
                        'error' => true,
3407
                        'message' => $lang->get('bad_password'),
3408
                    ),
3409
                    'encode'
3410
                );
3411
            } elseif ($userData['special'] === 'encrypt_personal_items') {
3412
                // We need to find a valid previous private key
3413
                $validPreviousKey = findValidPreviousPrivateKey(
3414
                    $post_previous_pwd,
3415
                    $post_user_id
3416
                );
3417
3418
                if ($validPreviousKey['private_key'] !== null) {
3419
                    // Decrypt all personal items with this key
3420
                    // Launch the re-encryption process for personal items
3421
                    // Create process
3422
                    DB::insert(
3423
                        prefixTable('background_tasks'),
3424
                        array(
3425
                            'created_at' => time(),
3426
                            'process_type' => 'create_user_keys',
3427
                            'arguments' => json_encode([
3428
                                'new_user_id' => (int) $post_user_id,
3429
                                'new_user_pwd' => cryption($post_previous_pwd, '','encrypt')['string'],
3430
                                'new_user_private_key' => cryption($validPreviousKey['private_key'], '','encrypt')['string'],
3431
                                'send_email' => 0,
3432
                                'otp_provided_new_value' => 0,
3433
                                'user_self_change' => 1,
3434
                                'only_personal_items' => 1,
3435
                            ]),
3436
                        )
3437
                    );
3438
                    $processId = DB::insertId();
3439
3440
                    // Create tasks
3441
                    createUserTasks($processId, NUMBER_ITEMS_IN_BATCH);
3442
3443
                    // update user's new status
3444
                    DB::update(
3445
                        prefixTable('users'),
3446
                        [
3447
                            'is_ready_for_usage' => 1,
3448
                            'otp_provided' => 1,
3449
                            'ongoing_process_id' => $processId,
3450
                            'special' => 'none',
3451
                        ],
3452
                        'id=%i',
3453
                        $post_user_id
3454
                    );
3455
3456
                    return prepareExchangedData(
3457
                        array(
3458
                            'error' => false,
3459
                            'message' => $lang->get('done'),
3460
                        ),
3461
                        'encode'
3462
                    );
3463
                }
3464
                return prepareExchangedData(
3465
                    array(
3466
                        'error' => true,
3467
                        'message' => $lang->get('no_previous_valide_private_key'),
3468
                    ),
3469
                    'encode'
3470
                );
3471
            }
3472
        }
3473
    }
3474
3475
    // ERROR
3476
    return prepareExchangedData(
3477
        array(
3478
            'error' => true,
3479
            'message' => $lang->get('error_no_user'),
3480
        ),
3481
        'encode'
3482
    );
3483
}
3484
3485
/**
3486
 * Try to find a valid previous private key by testing all previous keys
3487
 * until one is able to decrypt the share_key of one item
3488
 * @param string $previousPassword
3489
 * @param int $userId
3490
 * @return array|null
3491
 */
3492
function findValidPreviousPrivateKey($previousPassword, $userId) {
3493
    // Retrieve all user's private keys in descending order (most recent first)
3494
    $privateKeys = DB::query(
3495
        "SELECT 
3496
            id,
3497
            private_key,
3498
            created_at
3499
        FROM " . prefixTable('user_private_keys') . "
3500
        WHERE user_id = %i
3501
        ORDER BY created_at DESC, id DESC",
3502
        $userId
3503
    );
3504
    
3505
    if (empty($privateKeys)) {
3506
        return null;
3507
    }
3508
    
3509
    // Loop through all private keys
3510
    foreach ($privateKeys as $row) {
3511
        $encryptedPrivateKey = $row['private_key'];
3512
        
3513
        // Attempt to decrypt the private key with the previous password
3514
        $privateKey = decryptPrivateKey($previousPassword, $encryptedPrivateKey);
3515
        
3516
        // If decryption succeeded (key not null)
3517
        if ($privateKey !== null) {
3518
            // Select one personal item share_key to test decryption
3519
            $currentUserItemKey = DB::queryFirstRow(
3520
                'SELECT si.share_key, si.increment_id, l.id_user, i.perso
3521
                FROM ' . prefixTable('sharekeys_items') . ' AS si
3522
                INNER JOIN ' . prefixTable('log_items') . ' AS l ON si.object_id = l.id_item
3523
                INNER JOIN ' . prefixTable('items') . ' AS i ON i.id = l.id_item
3524
                WHERE si.user_id = %i AND i.perso = 1 AND si.share_key != ""
3525
                ORDER BY RAND()
3526
                LIMIT 1',
3527
                $userId
3528
            );
3529
3530
            if (is_countable($currentUserItemKey) && count($currentUserItemKey) > 0) {
3531
                // Decrypt itemkey with user key
3532
                // use old password to decrypt private_key
3533
                $itemKey = decryptUserObjectKey($currentUserItemKey['share_key'], $privateKey);
3534
                if (empty(base64_decode($itemKey)) === false) {                
3535
                    return [
3536
                        'private_key' => $privateKey
3537
                    ];
3538
                }
3539
            }
3540
        }
3541
    }
3542
    
3543
    return null;
3544
}
3545
3546
/**
3547
 * Change user LDAP auth password
3548
 *
3549
 * @param integer $post_user_id
3550
 * @param string $post_current_pwd
3551
 * @param string $post_new_pwd
3552
 * @param array $SETTINGS
3553
 * @return string
3554
 */
3555
function increaseSessionDuration(
3556
    int $duration
3557
): string
3558
{
3559
    $session = SessionManager::getSession();
3560
    // check if session is not already expired.
3561
    if ($session->get('user-session_duration') > time()) {
3562
        // Calculate end of session
3563
        $session->set('user-session_duration', (int) $session->get('user-session_duration') + $duration);
3564
        // Update table
3565
        DB::update(
3566
            prefixTable('users'),
3567
            array(
3568
                'session_end' => $session->get('user-session_duration'),
3569
            ),
3570
            'id = %i',
3571
            $session->get('user-id')
3572
        );
3573
        // Return data
3574
        return '[{"new_value":"' . $session->get('user-session_duration') . '"}]';
3575
    }
3576
    
3577
    return '[{"new_value":"expired"}]';
3578
}
3579
3580
/**
3581
 * Generate an OTP secret and QR code
3582
 * @param string $label
3583
 * @param bool $with_qrcode
3584
 * @param string $secretKey
3585
 * @return string
3586
 */
3587
function generateAnOTP(string $label, bool $with_qrcode = false, string $secretKey = ''): string
3588
{
3589
    // generate new secret
3590
    $tfa = new TwoFactorAuth();
3591
    if ($secretKey === '') {
3592
        $secretKey = $tfa->createSecret();
3593
    }
3594
3595
    // generate new QR
3596
    if ($with_qrcode === true) {
3597
        $qrcode = $tfa->getQRCodeImageAsDataUri(
3598
            $label,
3599
            $secretKey
3600
        );
3601
    }
3602
3603
    // ERROR
3604
    return prepareExchangedData(
3605
        array(
3606
            'error' => false,
3607
            'message' => '',
3608
            'secret' => $secretKey,
3609
            'qrcode' => $qrcode ?? '',
3610
        ),
3611
        'encode'
3612
    );
3613
}
3614
3615
/**
3616
 * Reset all personal items keys for a user
3617
 * @param int $userId
3618
 * @return string
3619
 */
3620
function resetUserPersonalItemKeys(int $userId): string
3621
{
3622
    $personalItems = DB::query(
3623
        'SELECT i.id, i.pw, s.share_key, s.increment_id
3624
        FROM ' . prefixTable('items') . ' i
3625
        INNER JOIN ' . prefixTable('sharekeys_items') . ' s ON i.id = s.object_id
3626
        WHERE i.perso = %i
3627
        AND s.user_id = %i',
3628
        1,
3629
        $userId
3630
    );
3631
3632
    if (is_countable($personalItems) && count($personalItems) > 0) {
3633
        // Reset the keys for each personal item
3634
        foreach ($personalItems as $item) {
3635
            DB::update(
3636
                prefixTable('sharekeys_items'),
3637
                array('share_key' => ''),
3638
                'increment_id = %i',
3639
                $item['increment_id']
3640
            );
3641
        }
3642
3643
        // Update user special status
3644
                DB::update(
3645
                    prefixTable('users'),
3646
                    array(
3647
                        'special' => 'none',
3648
                    ),
3649
                    'id = %i',
3650
                    $userId
3651
                );
3652
    }
3653
3654
    return prepareExchangedData(
3655
        array(
3656
            'error' => false,
3657
        ),
3658
        'encode'
3659
    );
3660
}
3661
3662
/**
3663
 * Set user only personal items encryption
3664
 * @param string $userPreviousPwd
3665
 * @param string $userCurrentPwd
3666
 * @param int $userId
3667
 * @return string
3668
 */
3669
function setUserOnlyPersonalItemsEncryption(string $userPreviousPwd, string $userCurrentPwd, int $userId): string
3670
{
3671
    $session = SessionManager::getSession();
3672
    $lang = new Language($session->get('user-language') ?? 'english');
3673
3674
    // We need to find a valid previous private key
3675
    $validPreviousKey = findValidPreviousPrivateKey(
3676
        $userPreviousPwd,
3677
        $userId
3678
    );
3679
    if ($validPreviousKey['private_key'] !== null) {
3680
        // Decrypt all personal items with this key
3681
        // Launch the re-encryption process for personal items
3682
        // Create process
3683
        DB::insert(
3684
            prefixTable('background_tasks'),
3685
            array(
3686
                'created_at' => time(),
3687
                'process_type' => 'create_user_keys',
3688
                'arguments' => json_encode([
3689
                    'new_user_id' => (int) $userId,
3690
                    'new_user_pwd' => cryption($userCurrentPwd, '','encrypt')['string'],
3691
                    'new_user_private_key' => cryption($validPreviousKey['private_key'], '','encrypt')['string'],
3692
                    'send_email' => 0,
3693
                    'otp_provided_new_value' => 0,
3694
                    'user_self_change' => 1,
3695
                    'only_personal_items' => 1,
3696
                ]),
3697
            )
3698
        );
3699
        $processId = DB::insertId();
3700
3701
        // Create tasks
3702
        createUserTasks($processId, NUMBER_ITEMS_IN_BATCH);
3703
3704
        // update user's new status
3705
        DB::update(
3706
            prefixTable('users'),
3707
            [
3708
                'is_ready_for_usage' => 1,
3709
                'otp_provided' => 1,
3710
                'ongoing_process_id' => $processId,
3711
                'special' => 'none',
3712
            ],
3713
            'id=%i',
3714
            $userId
3715
        );
3716
3717
        return prepareExchangedData(
3718
            array(
3719
                'error' => false,
3720
                'message' => $lang->get('done'),
3721
            ),
3722
            'encode'
3723
        );
3724
    }
3725
    return prepareExchangedData(
3726
        array(
3727
            'error' => true,
3728
            'message' => $lang->get('no_previous_valide_private_key'),
3729
        ),
3730
        'encode'
3731
    );
3732
}