Passed
Push — master ( ba5884...e5060c )
by Nils
06:02
created

setUserOnlyPersonalItemsEncryption()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 116
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 65
c 1
b 0
f 0
nc 3
nop 4
dl 0
loc 116
rs 8.7636

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
                (bool) filter_var($dataReceived['skipPasswordChange'], FILTER_VALIDATE_BOOLEAN),
833
                (int) filter_var($dataReceived['userId'], FILTER_SANITIZE_NUMBER_INT),
834
            );
835
836
        /*
837
         * Default case
838
         */
839
        default :
840
            return prepareExchangedData(
841
                array(
842
                    'error' => true,
843
                ),
844
                'encode'
845
            );
846
    }
847
}
848
849
/**
850
 * Handler for all system tasks
851
 *
852
 * @param string $post_type
853
 * @param array|null|string $dataReceived
854
 * @param array $SETTINGS
855
 * @return string
856
 */
857
function systemHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS): string
858
{
859
    $session = SessionManager::getSession();
860
    switch ($post_type) {
861
        /*
862
        * How many items for this user
863
        */
864
        case 'get_number_of_items_to_treat'://action_system
865
            return getNumberOfItemsToTreat(
866
                (int) filter_var($dataReceived['user_id'], FILTER_SANITIZE_NUMBER_INT),
867
                $SETTINGS
868
            );
869
870
        /*
871
         * Sending statistics
872
         */
873
        case 'sending_statistics'://action_system
874
            sendingStatistics(
875
                $SETTINGS
876
            );
877
            return prepareExchangedData(
878
                array(
879
                    'error' => false,
880
                ),
881
                'encode'
882
            );
883
884
         /*
885
         * Generate BUG report
886
         */
887
        case 'generate_bug_report'://action_system
888
889
            // Only administrators can see this confidential informations.
890
            if ((int) $session->get('user-admin') !== 1) {
891
                return prepareExchangedData(
892
                    array(
893
                        'error' => false,
894
                    ),
895
                    'encode'
896
                );
897
            }
898
899
            return generateBugReport(
900
                (array) $dataReceived,
901
                $SETTINGS
902
            );
903
904
        /*
905
         * get_teampass_settings
906
         */
907
        case 'get_teampass_settings'://action_system
908
909
            // Encrypt data to return
910
            return prepareExchangedData(
911
                array_intersect_key(
912
                    $SETTINGS, 
913
                    array(
914
                        'ldap_user_attribute' => '',
915
                        'enable_pf_feature' => '',
916
                        'clipboard_life_duration' => '',
917
                        'enable_favourites' => '',
918
                        'copy_to_clipboard_small_icons' => '',
919
                        'enable_attachment_encryption' => '',
920
                        'google_authentication' => '',
921
                        'agses_authentication_enabled' => '',
922
                        'yubico_authentication' => '',
923
                        'duo' => '',
924
                        'personal_saltkey_security_level' => '',
925
                        'enable_tasks_manager' => '',
926
                        'insert_manual_entry_item_history' => '',
927
                        'show_item_data' => '',
928
                    )
929
                ),
930
                'encode'
931
            );
932
933
        /*
934
         * Generates a TOKEN with CRYPT
935
         */
936
        case 'save_token'://action_system
937
            $token = GenerateCryptKey(
938
                null !== filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT) ? (int) filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT) : 20,
939
                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,
940
                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,
941
                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,
942
                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,
943
                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
944
            );
945
            
946
            // store in DB
947
            DB::insert(
948
                prefixTable('tokens'),
949
                array(
950
                    'user_id' => (int) $session->get('user-id'),
951
                    'token' => $token,
952
                    'reason' => filter_input(INPUT_POST, 'reason', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
953
                    'creation_timestamp' => time(),
954
                    'end_timestamp' => time() + filter_input(INPUT_POST, 'duration', FILTER_SANITIZE_NUMBER_INT), // in secs
955
                )
956
            );
957
958
            return '[{"token" : "' . $token . '"}]';
959
960
        /*
961
        * Default case
962
        */
963
        default :
964
            return prepareExchangedData(
965
                array(
966
                    'error' => true,
967
                ),
968
                'encode'
969
            );
970
    }
971
}
972
973
974
function utilsHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS): string
975
{
976
    switch ($post_type) {
977
        /*
978
         * generate_an_otp
979
         */
980
        case 'generate_an_otp'://action_utils
981
            return generateAnOTP(
982
                (string) filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
983
                (bool) filter_var($dataReceived['with_qrcode'], FILTER_VALIDATE_BOOLEAN),
984
                (string) filter_var($dataReceived['secret_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
985
            );
986
987
988
        /*
989
         * Default case
990
         */
991
        default :
992
            return prepareExchangedData(
993
                array(
994
                    'error' => true,
995
                ),
996
                'encode'
997
            );
998
    }
999
}
1000
1001
/**
1002
 * Permits to set the user ready
1003
 *
1004
 * @param integer $userid
1005
 * @param string $dir
1006
 * @return string
1007
 */
1008
function userIsReady(int $userid, string $dir): string
1009
{
1010
    DB::update(
1011
        prefixTable('users'),
1012
        array(
1013
            'is_ready_for_usage' => 1,
1014
        ),
1015
        'id = %i',
1016
        $userid
1017
    );
1018
1019
    // Send back
1020
    return prepareExchangedData(
1021
        array(
1022
            'error' => false,
1023
        ),
1024
        'encode'
1025
    ); 
1026
}
1027
1028
1029
/**
1030
 * Permits to set the user ready
1031
 *
1032
 * @param integer $userid
1033
 * @return string
1034
 */
1035
function userGetSessionTime(int $userid, string $dir, int $maximum_session_expiration_time): string
1036
{
1037
    $session = SessionManager::getSession();
1038
    // Send back
1039
    return prepareExchangedData(
1040
        array(
1041
            'error' => false,
1042
            'timestamp' => $session->get('user-session_duration'),
1043
            'max_time_to_add' => intdiv((($maximum_session_expiration_time*60) - ((int) $session->get('user-session_duration') - time())), 60),
1044
            'max_session_duration' => $maximum_session_expiration_time,
1045
        ),
1046
        'encode'
1047
    ); 
1048
}
1049
1050
/**
1051
 * Save the user's IP
1052
 *
1053
 * @param integer $userID
1054
 * @param string $action
1055
 * @return string
1056
 */
1057
function userSaveIp(int $userID, string $action): string
1058
{
1059
    if ($action === 'perform') {
1060
        DB::update(
1061
            prefixTable('users'),
1062
            array(
1063
                'user_ip' => getClientIpServer(),
1064
                'user_ip_lastdate' => time(),
1065
            ),
1066
            'id = %i',
1067
            $userID
1068
        );
1069
    }
1070
1071
    return prepareExchangedData(
1072
        array(
1073
            'error' => false,
1074
        ),
1075
        'encode'
1076
    );
1077
}
1078
1079
/**
1080
 * Provides the number of items
1081
 *
1082
 * @param int   $userId     User ID
1083
 * @param array $SETTINGS   TeampassSettings
1084
 *
1085
 * @return string
1086
 */
1087
function getNumberOfItemsToTreat(
1088
    int $userId,
1089
    array $SETTINGS
1090
): string
1091
{
1092
    // get number of items
1093
    DB::queryFirstRow(
1094
        'SELECT increment_id
1095
        FROM ' . prefixTable('sharekeys_items') .
1096
        ' WHERE user_id = %i',
1097
        $userId
1098
    );
1099
1100
    // Send back
1101
    return prepareExchangedData(
1102
        array(
1103
            'error' => false,
1104
            'nbItems' => DB::count(),
1105
        ),
1106
        'encode'
1107
    );
1108
}
1109
1110
1111
/**
1112
 * 
1113
 */
1114
function changePassword(
1115
    string $post_new_password,
1116
    string $post_current_password,
1117
    int $post_password_complexity,
1118
    string $post_change_request,
1119
    int $post_user_id,
1120
    array $SETTINGS
1121
): string
1122
{
1123
    $session = SessionManager::getSession();
1124
    
1125
    // Create password hash
1126
    $passwordManager = new PasswordManager();
1127
    $post_new_password_hashed = $passwordManager->hashPassword($post_new_password);
1128
1129
    // Load user's language
1130
    $lang = new Language($session->get('user-language') ?? 'english');
1131
1132
    // User has decided to change is PW
1133
    if ($post_change_request === 'reset_user_password_expected'
1134
        || $post_change_request === 'user_decides_to_change_password'
1135
    ) {
1136
        // Check that current user is correct
1137
        if ((int) $post_user_id !== (int) $session->get('user-id')) {
1138
            return prepareExchangedData(
1139
                array(
1140
                    'error' => true,
1141
                    'message' => $lang->get('error_not_allowed_to'),
1142
                ),
1143
                'encode'
1144
            );
1145
        }
1146
1147
        // check if expected security level is reached
1148
        $dataUser = DB::queryFirstRow(
1149
            'SELECT *
1150
            FROM ' . prefixTable('users') . ' WHERE id = %i',
1151
            $post_user_id
1152
        );
1153
1154
        // check if badly written
1155
        $dataUser['fonction_id'] = array_filter(
1156
            explode(',', str_replace(';', ',', $dataUser['fonction_id']))
1157
        );
1158
        $dataUser['fonction_id'] = implode(',', $dataUser['fonction_id']);
1159
        DB::update(
1160
            prefixTable('users'),
1161
            array(
1162
                'fonction_id' => $dataUser['fonction_id'],
1163
            ),
1164
            'id = %i',
1165
            $post_user_id
1166
        );
1167
1168
        if (empty($dataUser['fonction_id']) === false) {
1169
            $data = DB::queryFirstRow(
1170
                'SELECT complexity
1171
                FROM ' . prefixTable('roles_title') . '
1172
                WHERE id IN (' . $dataUser['fonction_id'] . ')
1173
                ORDER BY complexity DESC'
1174
            );
1175
        } else {
1176
            // In case user has no roles yet
1177
            $data = array();
1178
            $data['complexity'] = 0;
1179
        }
1180
1181
        if ((int) $post_password_complexity < (int) $data['complexity']) {
1182
            return prepareExchangedData(
1183
                array(
1184
                    'error' => true,
1185
                    'message' => '<div style="margin:10px 0 10px 15px;">' . $lang->get('complexity_level_not_reached') . '.<br>' .
1186
                        $lang->get('expected_complexity_level') . ': <b>' . TP_PW_COMPLEXITY[$data['complexity']][1] . '</b></div>',
1187
                ),
1188
                'encode'
1189
            );
1190
        }
1191
1192
        // Check that the 2 passwords are differents
1193
        if ($post_current_password === $post_new_password) {
1194
            return prepareExchangedData(
1195
                array(
1196
                    'error' => true,
1197
                    'message' => $lang->get('password_already_used'),
1198
                ),
1199
                'encode'
1200
            );
1201
        }
1202
1203
        // update sessions
1204
        $session->set('user-last_pw_change', mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')));
1205
        $session->set('user-validite_pw', 1);
1206
1207
        // BEfore updating, check that the pwd is correct
1208
        if ($passwordManager->verifyPassword($post_new_password_hashed, $post_new_password) === true && empty($dataUser['private_key']) === false) {
1209
            $special_action = 'none';
1210
            if ($post_change_request === 'reset_user_password_expected') {
1211
                $session->set('user-private_key', decryptPrivateKey($post_current_password, $dataUser['private_key']));
1212
            }
1213
1214
            // update DB
1215
            DB::update(
1216
                prefixTable('users'),
1217
                array(
1218
                    'pw' => $post_new_password_hashed,
1219
                    'last_pw_change' => mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')),
1220
                    'last_pw' => $post_current_password,
1221
                    'special' => $special_action,
1222
                    'private_key' => encryptPrivateKey($post_new_password, $session->get('user-private_key')),
1223
                ),
1224
                'id = %i',
1225
                $post_user_id
1226
            );
1227
            // update LOG
1228
            logEvents($SETTINGS, 'user_mngt', 'at_user_pwd_changed', (string) $session->get('user-id'), $session->get('user-login'), $post_user_id);
1229
1230
            // Send back
1231
            return prepareExchangedData(
1232
                array(
1233
                    'error' => false,
1234
                    'message' => '',
1235
                ),
1236
                'encode'
1237
            );
1238
        }
1239
        // Send back
1240
        return prepareExchangedData(
1241
            array(
1242
                'error' => true,
1243
                'message' => $lang->get('pw_hash_not_correct'),
1244
            ),
1245
            'encode'
1246
        );
1247
    }
1248
    return prepareExchangedData(
1249
        array(
1250
            'error' => true,
1251
            'message' => $lang->get('error_not_allowed_to'),
1252
        ),
1253
        'encode'
1254
    );
1255
}
1256
1257
function generateQRCode(
1258
    $post_id,
1259
    $post_demand_origin,
1260
    $post_send_mail,
1261
    $post_login,
1262
    $post_pwd,
1263
    $post_token,
1264
    array $SETTINGS
1265
): string
1266
{
1267
    // Load user's language
1268
    $session = SessionManager::getSession();
1269
    $lang = new Language($session->get('user-language') ?? 'english');
1270
1271
    // is this allowed by setting
1272
    if (isKeyExistingAndEqual('ga_reset_by_user', 0, $SETTINGS) === true
1273
        && (null === $post_demand_origin || $post_demand_origin !== 'users_management_list')
1274
    ) {
1275
        // User cannot ask for a new code
1276
        return prepareExchangedData(
1277
            array(
1278
                'error' => true,
1279
                'message' => "113 ".$lang->get('error_not_allowed_to')." - ".isKeyExistingAndEqual('ga_reset_by_user', 1, $SETTINGS),
1280
            ),
1281
            'encode'
1282
        );
1283
    }
1284
    
1285
    // Check if user exists
1286
    if (isValueSetNullEmpty($post_id) === true) {
1287
        // Get data about user
1288
        $dataUser = DB::queryFirstRow(
1289
            'SELECT id, email, pw
1290
            FROM ' . prefixTable('users') . '
1291
            WHERE login = %s',
1292
            $post_login
1293
        );
1294
    } else {
1295
        $dataUser = DB::queryFirstRow(
1296
            'SELECT id, login, email, pw
1297
            FROM ' . prefixTable('users') . '
1298
            WHERE id = %i',
1299
            $post_id
1300
        );
1301
        $post_login = $dataUser['login'];
1302
    }
1303
    // Get number of returned users
1304
    $counter = DB::count();
1305
1306
    // Do treatment
1307
    if ($counter === 0) {
1308
        // Not a registered user !
1309
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($post_login), stripslashes($post_login));
1310
        return prepareExchangedData(
1311
            array(
1312
                'error' => true,
1313
                'message' => $lang->get('no_user'),
1314
                'tst' => 1,
1315
            ),
1316
            'encode'
1317
        );
1318
    }
1319
1320
    $passwordManager = new PasswordManager();
1321
    if (
1322
        isSetArrayOfValues([$post_pwd, $dataUser['pw']]) === true
1323
        && $passwordManager->verifyPassword($dataUser['pw'], $post_pwd) === false
1324
        && $post_demand_origin !== 'users_management_list'
1325
    ) {
1326
        // checked the given password
1327
        logEvents($SETTINGS, 'failed_auth', 'password_is_not_correct', '', stripslashes($post_login), stripslashes($post_login));
1328
        return prepareExchangedData(
1329
            array(
1330
                'error' => true,
1331
                'message' => $lang->get('no_user'),
1332
                'tst' => $post_demand_origin,
1333
            ),
1334
            'encode'
1335
        );
1336
    }
1337
    
1338
    if (empty($dataUser['email']) === true) {
1339
        return prepareExchangedData(
1340
            array(
1341
                'error' => true,
1342
                'message' => $lang->get('no_email_set'),
1343
            ),
1344
            'encode'
1345
        );
1346
    }
1347
1348
    // Check if token already used
1349
    $dataToken = DB::queryFirstRow(
1350
        'SELECT end_timestamp, reason
1351
        FROM ' . prefixTable('tokens') . '
1352
        WHERE token = %s AND user_id = %i',
1353
        $post_token,
1354
        $dataUser['id']
1355
    );
1356
    $tokenId = '';
1357
    if (DB::count() > 0 && is_null($dataToken['end_timestamp']) === false && $dataToken['reason'] === 'auth_qr_code') {
1358
        // This token has already been used
1359
        return prepareExchangedData(
1360
            array(
1361
                'error' => true,
1362
                'message' => 'TOKEN already used',//$lang->get('no_email_set'),
1363
            ),
1364
            'encode'
1365
        );
1366
    } elseif(DB::count() === 0) {
1367
        // Store token for this action
1368
        DB::insert(
1369
            prefixTable('tokens'),
1370
            array(
1371
                'user_id' => (int) $dataUser['id'],
1372
                'token' => $post_token,
1373
                'reason' => 'auth_qr_code',
1374
                'creation_timestamp' => time(),
1375
            )
1376
        );
1377
        $tokenId = DB::insertId();
1378
    }
1379
    
1380
    // generate new GA user code
1381
    $tfa = new TwoFactorAuth($SETTINGS['ga_website_name']);
1382
    $gaSecretKey = $tfa->createSecret();
1383
    $gaTemporaryCode = GenerateCryptKey(12, false, true, true, false, true);
1384
1385
    DB::update(
1386
        prefixTable('users'),
1387
        [
1388
            'ga' => $gaSecretKey,
1389
            'ga_temporary_code' => $gaTemporaryCode,
1390
        ],
1391
        'id = %i',
1392
        $dataUser['id']
1393
    );
1394
1395
    // Log event
1396
    logEvents($SETTINGS, 'user_connection', 'at_2fa_google_code_send_by_email', (string) $dataUser['id'], stripslashes($post_login), stripslashes($post_login));
1397
1398
    // Update token status
1399
    DB::update(
1400
        prefixTable('tokens'),
1401
        [
1402
            'end_timestamp' => time(),
1403
        ],
1404
        'id = %i',
1405
        $tokenId
1406
    );
1407
1408
    // send mail?
1409
    if ((int) $post_send_mail === 1) {
1410
        prepareSendingEmail(
1411
            $lang->get('email_ga_subject'),
1412
            str_replace(
1413
                '#2FACode#',
1414
                $gaTemporaryCode,
1415
                $lang->get('email_ga_text')
1416
            ),
1417
            $dataUser['email']
1418
        );
1419
1420
        // send back
1421
        return prepareExchangedData(
1422
            array(
1423
                'error' => false,
1424
                'message' => $post_send_mail,
1425
                'email' => $dataUser['email'],
1426
                'email_result' => str_replace(
1427
                    '#email#',
1428
                    '<b>' . obfuscateEmail($dataUser['email']) . '</b>',
1429
                    addslashes($lang->get('admin_email_result_ok'))
1430
                ),
1431
            ),
1432
            'encode'
1433
        );
1434
    }
1435
    
1436
    // send back
1437
    return prepareExchangedData(
1438
        array(
1439
            'error' => false,
1440
            'message' => '',
1441
            'email' => $dataUser['email'],
1442
            'email_result' => str_replace(
1443
                '#email#',
1444
                '<b>' . obfuscateEmail($dataUser['email']) . '</b>',
1445
                addslashes($lang->get('admin_email_result_ok'))
1446
            ),
1447
        ),
1448
        'encode'
1449
    );
1450
}
1451
1452
function sendEmailsNotSent(
1453
    array $SETTINGS
1454
)
1455
{
1456
    $emailSettings = new EmailSettings($SETTINGS);
1457
    $emailService = new EmailService();
1458
1459
    if (isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true) {
1460
        $row = DB::queryFirstRow(
1461
            'SELECT valeur FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s',
1462
            'cron',
1463
            'sending_emails'
1464
        );
1465
1466
        if ((int) (time() - $row['valeur']) >= 300 || (int) $row['valeur'] === 0) {
1467
            $rows = DB::query(
1468
                'SELECT *
1469
                FROM ' . prefixTable('emails') .
1470
                ' WHERE status != %s',
1471
                'sent'
1472
            );
1473
            foreach ($rows as $record) {
1474
                // Send email
1475
                $ret = json_decode(
1476
                    $emailService->sendMail(
1477
                        $record['subject'],
1478
                        $record['body'],
1479
                        $record['receivers'],
1480
                        $emailSettings
1481
                    ),
1482
                    true
1483
                );
1484
1485
                // update item_id in files table
1486
                DB::update(
1487
                    prefixTable('emails'),
1488
                    array(
1489
                        'status' => $ret['error'] === 'error_mail_not_send' ? 'not_sent' : 'sent',
1490
                    ),
1491
                    'timestamp = %s',
1492
                    $record['timestamp']
1493
                );
1494
            }
1495
        }
1496
        // update cron time
1497
        DB::update(
1498
            prefixTable('misc'),
1499
            array(
1500
                'valeur' => time(),
1501
                'updated_at' => time(),
1502
            ),
1503
            'intitule = %s AND type = %s',
1504
            'sending_emails',
1505
            'cron'
1506
        );
1507
    }
1508
}
1509
1510
1511
function refreshUserItemsSeenList(
1512
    array $SETTINGS
1513
): string
1514
{
1515
    $session = SessionManager::getSession();
1516
1517
    // get list of last items seen
1518
    $arr_html = array();
1519
    $rows = DB::query(
1520
        '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
1521
        FROM ' . prefixTable('log_items') . ' AS l
1522
        RIGHT JOIN ' . prefixTable('items') . ' AS i ON (l.id_item = i.id)
1523
        WHERE l.action = %s AND l.id_user = %i
1524
        ORDER BY l.date DESC
1525
        LIMIT 0, 100',
1526
        'at_shown',
1527
        $session->get('user-id')
1528
    );
1529
    if (DB::count() > 0) {
1530
        foreach ($rows as $record) {
1531
            if (in_array($record['id']->id, array_column($arr_html, 'id')) === false) {
1532
                array_push(
1533
                    $arr_html,
1534
                    array(
1535
                        'id' => $record['id'],
1536
                        'label' => htmlspecialchars(stripslashes(htmlspecialchars_decode($record['label'], ENT_QUOTES)), ENT_QUOTES),
1537
                        'tree_id' => $record['id_tree'],
1538
                        'perso' => $record['perso'],
1539
                        'restricted' => $record['restricted'],
1540
                    )
1541
                );
1542
                if (count($arr_html) >= (int) $SETTINGS['max_latest_items']) {
1543
                    break;
1544
                }
1545
            }
1546
        }
1547
    }
1548
1549
    // get wainting suggestions
1550
    $nb_suggestions_waiting = 0;
1551
    if (isKeyExistingAndEqual('enable_suggestion', 1, $SETTINGS) === true
1552
        && ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1)
1553
    ) {
1554
        DB::query('SELECT * FROM ' . prefixTable('suggestion'));
1555
        $nb_suggestions_waiting = DB::count();
1556
    }
1557
1558
    return json_encode(
1559
        array(
1560
            'error' => '',
1561
            'existing_suggestions' => $nb_suggestions_waiting,
1562
            'html_json' => $arr_html,
1563
        ),
1564
        JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1565
    );
1566
}
1567
1568
function sendingStatistics(
1569
    array $SETTINGS
1570
): void
1571
{
1572
    $session = SessionManager::getSession();
1573
    if (
1574
        isSetArrayOfValues([$SETTINGS['send_statistics_items'], $SETTINGS['send_stats_time']]) === true
1575
        && isKeyExistingAndEqual('send_stats', 1, $SETTINGS) === true
1576
        && (int) ($SETTINGS['send_stats_time'] + TP_ONE_DAY_SECONDS) > time()
1577
    ) {
1578
        // get statistics data
1579
        $stats_data = getStatisticsData($SETTINGS);
1580
1581
        // get statistics items to share
1582
        $statsToSend = [];
1583
        $statsToSend['ip'] = $_SERVER['SERVER_ADDR'];
1584
        $statsToSend['timestamp'] = time();
1585
        foreach (array_filter(explode(';', $SETTINGS['send_statistics_items'])) as $data) {
1586
            if ($data === 'stat_languages') {
1587
                $tmp = '';
1588
                foreach ($stats_data[$data] as $key => $value) {
1589
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1590
                }
1591
                $statsToSend[$data] = $tmp;
1592
            } elseif ($data === 'stat_country') {
1593
                $tmp = '';
1594
                foreach ($stats_data[$data] as $key => $value) {
1595
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1596
                }
1597
                $statsToSend[$data] = $tmp;
1598
            } else {
1599
                $statsToSend[$data] = $stats_data[$data];
1600
            }
1601
        }
1602
1603
        // connect to Teampass Statistics database
1604
        $link2 = new MeekroDB(
1605
            'teampass.pw',
1606
            'teampass_user',
1607
            'ZMlEfRzKzFLZNzie',
1608
            'teampass_followup',
1609
            '3306',
1610
            'utf8'
1611
        );
1612
1613
        $link2->insert(
1614
            'statistics',
1615
            $statsToSend
1616
        );
1617
1618
        // update table misc with current timestamp
1619
        DB::update(
1620
            prefixTable('misc'),
1621
            array(
1622
                'valeur' => time(),
1623
                'updated_at' => time(),
1624
            ),
1625
            'type = %s AND intitule = %s',
1626
            'admin',
1627
            'send_stats_time'
1628
        );
1629
1630
        //permits to test only once by session
1631
        $session->set('system-send_stats_done', 1);
1632
        $SETTINGS['send_stats_time'] = time();
1633
    }
1634
}
1635
1636
function generateBugReport(
1637
    array $data,
1638
    array $SETTINGS
1639
): string
1640
{
1641
    $config_exclude_vars = array(
1642
        'bck_script_passkey',
1643
        'email_smtp_server',
1644
        'email_auth_username',
1645
        'email_auth_pwd',
1646
        'email_from',
1647
        'onthefly-restore-key',
1648
        'onthefly-backup-key',
1649
        'ldap_password',
1650
        'ldap_hosts',
1651
        'proxy_ip',
1652
        'ldap_bind_passwd',
1653
        'syslog_host',
1654
        'duo_akey',
1655
        'duo_ikey',
1656
        'duo_skey',
1657
        'duo_host',
1658
        'oauth2_client_id',
1659
        'oauth2_tenant_id',
1660
        'oauth2_client_secret',
1661
        'oauth2_client_token',
1662
        'oauth2_client_endpoint',
1663
    );
1664
1665
    // Load user's language
1666
    $session = SessionManager::getSession();
1667
    $lang = new Language($session->get('user-language') ?? 'english');
1668
1669
    // Read config file
1670
    $list_of_options = '';
1671
    $url_found = '';
1672
    $anonym_url = '';
1673
    $sortedSettings = $SETTINGS;
1674
    ksort($sortedSettings);
1675
1676
    foreach ($sortedSettings as $key => $value) {
1677
        // Identify url to anonymize it
1678
        if ($key === 'cpassman_url' && empty($url_found) === true) {
1679
            $url_found = $value;
1680
            if (empty($url_found) === false) {
1681
                $tmp = parse_url($url_found);
1682
                $anonym_url = $tmp['scheme'] . '://<anonym_url>' . (isset($tmp['path']) === true ? $tmp['path'] : '');
1683
                $value = $anonym_url;
1684
            } else {
1685
                $value = '';
1686
            }
1687
        }
1688
1689
        // Anonymize all urls
1690
        if (empty($anonym_url) === false) {
1691
            $value = str_replace($url_found, $anonym_url, (string) $value);
1692
        }
1693
1694
        // Clear some vars
1695
        foreach ($config_exclude_vars as $var) {
1696
            if ($key === $var) {
1697
                $value = '<removed>';
1698
            }
1699
        }
1700
1701
        // Complete line to display
1702
        $list_of_options .= "'$key' => '$value'\n";
1703
    }
1704
1705
    // Get error
1706
    $err = error_get_last();
1707
1708
    // Get 10 latest errors in Teampass
1709
    $teampass_errors = '';
1710
    $rows = DB::query(
1711
        'SELECT label, date AS error_date
1712
        FROM ' . prefixTable('log_system') . "
1713
        WHERE `type` LIKE 'error'
1714
        ORDER BY `date` DESC
1715
        LIMIT 0, 10"
1716
    );
1717
    if (DB::count() > 0) {
1718
        foreach ($rows as $record) {
1719
            if (empty($teampass_errors) === true) {
1720
                $teampass_errors = ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1721
            } else {
1722
                $teampass_errors .= ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1723
            }
1724
        }
1725
    }
1726
1727
    if (defined('DB_PASSWD_CLEAR') === false) {
1728
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD));
1729
    }
1730
    $link = mysqli_connect(DB_HOST, DB_USER, DB_PASSWD_CLEAR, DB_NAME, (int) DB_PORT, null);
1731
1732
    // Now prepare text
1733
    $txt = '### Page on which it happened
1734
' . $data['current_page'] . '
1735
1736
### Steps to reproduce
1737
1.
1738
2.
1739
3.
1740
1741
### Expected behaviour
1742
Tell us what should happen
1743
1744
1745
### Actual behaviour
1746
Tell us what happens instead
1747
1748
### Server configuration
1749
**Operating system**: ' . php_uname() . '
1750
1751
**Web server:** ' . $_SERVER['SERVER_SOFTWARE'] . '
1752
1753
**Database:** ' . ($link === false ? $lang->get('undefined') : mysqli_get_server_info($link)) . '
1754
1755
**PHP version:** ' . PHP_VERSION . '
1756
1757
**Teampass version:** ' . TP_VERSION . '.' . TP_VERSION_MINOR . '
1758
1759
**Teampass configuration variables:**
1760
```
1761
' . $list_of_options . '
1762
```
1763
1764
**Updated from an older Teampass or fresh install:**
1765
1766
### Client configuration
1767
1768
**Browser:** ' . $data['browser_name'] . ' - ' . $data['browser_version'] . '
1769
1770
**Operating system:** ' . $data['os'] . ' - ' . $data['os_archi'] . 'bits
1771
1772
### Logs
1773
1774
#### Web server error log
1775
```
1776
' . $err['message'] . ' - ' . $err['file'] . ' (' . $err['line'] . ')
1777
```
1778
1779
#### Teampass 10 last system errors
1780
```
1781
' . $teampass_errors . '
1782
```
1783
1784
#### Log from the web-browser developer console (CTRL + SHIFT + i)
1785
```
1786
Insert the log here and especially the answer of the query that failed.
1787
```
1788
';
1789
1790
    return prepareExchangedData(
1791
        array(
1792
            'html' => $txt,
1793
            'error' => '',
1794
        ),
1795
        'encode'
1796
    );
1797
}
1798
1799
/**
1800
 * Check that the user password is valid
1801
 *
1802
 * @param integer $post_user_id
1803
 * @param string $post_user_password
1804
 * @param string $post_user_otp
1805
 * @param array $SETTINGS
1806
 * @return string
1807
 */
1808
function isUserPasswordCorrect(
1809
    int $post_user_id,
1810
    string $post_user_password,
1811
    string $post_user_otp,
1812
    array $SETTINGS
1813
): string
1814
{
1815
    $session = SessionManager::getSession();
1816
    // Load user's language
1817
    $lang = new Language($session->get('user-language') ?? 'english');
1818
    
1819
    if (isUserIdValid($post_user_id) === true) {
1820
        // Check if user exists
1821
        $userInfo = DB::queryFirstRow(
1822
            'SELECT public_key, private_key, pw, auth_type
1823
            FROM ' . prefixTable('users') . '
1824
            WHERE id = %i',
1825
            $post_user_id
1826
        );
1827
        if (DB::count() > 0 && empty($userInfo['private_key']) === false) {
1828
            // Get itemKey from current user
1829
            // Get one item
1830
            $currentUserKey = DB::queryFirstRow(
1831
                'SELECT object_id, share_key, increment_id
1832
                FROM ' . prefixTable('sharekeys_items') . ' AS si
1833
                INNER JOIN ' . prefixTable('items') . ' AS i ON  (i.id = si.object_id)
1834
                INNER JOIN ' . prefixTable('nested_tree') . ' AS nt ON  (i.id_tree = nt.id)
1835
                WHERE user_id = %i AND nt.personal_folder = %i',
1836
                $post_user_id,
1837
                0
1838
            );
1839
            
1840
            if (DB::count() === 0) {
1841
                // This user has no items
1842
                // let's consider no items in DB
1843
                return prepareExchangedData(
1844
                    array(
1845
                        'error' => false,
1846
                        'message' => '',
1847
                        'debug' => '',
1848
                    ),
1849
                    'encode'
1850
                );
1851
            }
1852
            
1853
            if ($currentUserKey !== null) {
1854
                // Decrypt itemkey with user key
1855
                // use old password to decrypt private_key
1856
                $possibleKeys = [
1857
                    $post_user_password,
1858
                    $post_user_otp,
1859
                ];
1860
1861
                $privateKeyDecrypted = null;
1862
1863
                foreach ($possibleKeys as $key) {
1864
                    $result = decryptPrivateKey($key, $userInfo['private_key']);
1865
                    
1866
                    // If decryption is ok
1867
                    if (is_string($result) && $result !== '') {
1868
                        $privateKeyDecrypted = $result;
1869
                        break;
1870
                    }
1871
                }
1872
1873
                if ($privateKeyDecrypted === null) {
1874
                    // No key is ok
1875
                    return prepareExchangedData(
1876
                        array(
1877
                            'error' => true,
1878
                            'message' => $lang->get('password_is_not_correct'),
1879
                        ),
1880
                        'encode'
1881
                    );
1882
                }
1883
1884
                $session->set('user-private_key', $privateKeyDecrypted);
1885
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $privateKeyDecrypted);
1886
1887
                if (empty(base64_decode($itemKey)) === false) {
1888
                    // GOOD password
1889
                    return prepareExchangedData(
1890
                        array(
1891
                            'error' => false,
1892
                            'message' => '',
1893
                            'debug' => '',
1894
                        ),
1895
                        'encode'
1896
                    );
1897
                }
1898
            }
1899
1900
            // use the password check
1901
            $passwordManager = new PasswordManager();
1902
            if ($passwordManager->verifyPassword($userInfo['pw'], $post_user_password) === true) {
1903
                // GOOD password
1904
                return prepareExchangedData(
1905
                    array(
1906
                        'error' => false,
1907
                        'message' => '',
1908
                        'debug' => '',
1909
                    ),
1910
                    'encode'
1911
                );
1912
            }
1913
        }
1914
    }
1915
1916
    return prepareExchangedData(
1917
        array(
1918
            'error' => true,
1919
            'message' => $lang->get('password_is_not_correct'),
1920
        ),
1921
        'encode'
1922
    );
1923
}
1924
1925
function changePrivateKeyEncryptionPassword(
1926
    int $post_user_id,
1927
    string $post_current_code,
1928
    string $post_new_code,
1929
    string $post_action_type,
1930
    array $SETTINGS
1931
): string
1932
{
1933
    $session = SessionManager::getSession();
1934
    // Load user's language
1935
    $lang = new Language($session->get('user-language') ?? 'english');
1936
    
1937
    if (empty($post_new_code) === true) {
1938
        // no user password
1939
        return prepareExchangedData(
1940
            array(
1941
                'error' => true,
1942
                'message' => $lang->get('error_bad_credentials'),
1943
                'debug' => '',
1944
            ),
1945
            'encode'
1946
        );
1947
    }
1948
1949
    if (isUserIdValid($post_user_id) === true) {
1950
        // Get user info
1951
        $userData = DB::queryFirstRow(
1952
            'SELECT private_key
1953
            FROM ' . prefixTable('users') . '
1954
            WHERE id = %i',
1955
            $post_user_id
1956
        );
1957
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
1958
            if ($post_action_type === 'encrypt_privkey_with_user_password') {
1959
                // Here the user has his private key encrypted with an OTC.
1960
                // We need to encrypt it with his real password
1961
                $privateKey = decryptPrivateKey($post_new_code, $userData['private_key']);
1962
                $hashedPrivateKey = encryptPrivateKey($post_current_code, $privateKey);
1963
            } else {
1964
                $privateKey = decryptPrivateKey($post_current_code, $userData['private_key']);
1965
                $hashedPrivateKey = encryptPrivateKey($post_new_code, $privateKey);
1966
            }
1967
1968
            // Should fail here to avoid break user private key.
1969
            if (strlen($privateKey) === 0 || strlen($hashedPrivateKey) < 30) {
1970
                if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
1971
                    error_log("Error reencrypt user private key. User ID: {$post_user_id}, Given OTP: '{$post_current_code}'");
1972
                }
1973
                return prepareExchangedData(
1974
                    array(
1975
                        'error' => true,
1976
                        'message' => $lang->get('error_otp_secret'),
1977
                        'debug' => '',
1978
                    ),
1979
                    'encode'
1980
                );
1981
            }
1982
1983
            // Update user account
1984
            DB::update(
1985
                prefixTable('users'),
1986
                array(
1987
                    'private_key' => $hashedPrivateKey,
1988
                    'special' => 'none',
1989
                    'otp_provided' => 1,
1990
                ),
1991
                'id = %i',
1992
                $post_user_id
1993
            );
1994
1995
            $session->set('user-private_key', $privateKey);
1996
        }
1997
1998
        // Return
1999
        return prepareExchangedData(
2000
            array(
2001
                'error' => false,
2002
                'message' => '',
2003
            ),
2004
            'encode'
2005
        );
2006
    }
2007
    
2008
    return prepareExchangedData(
2009
        array(
2010
            'error' => true,
2011
            'message' => $lang->get('error_no_user'),
2012
            'debug' => '',
2013
        ),
2014
        'encode'
2015
    );
2016
}
2017
2018
function initializeUserPassword(
2019
    int $post_user_id,
2020
    string $post_special,
2021
    string $post_user_password,
2022
    bool $post_self_change,
2023
    array $SETTINGS
2024
): string
2025
{
2026
    // Load user's language
2027
    $session = SessionManager::getSession();
2028
    $lang = new Language($session->get('user-language') ?? 'english');
2029
    
2030
    if (isUserIdValid($post_user_id) === true) {
2031
        // Get user info
2032
        $userData = DB::queryFirstRow(
2033
            'SELECT email, auth_type, login
2034
            FROM ' . prefixTable('users') . '
2035
            WHERE id = %i',
2036
            $post_user_id
2037
        );
2038
        if (DB::count() > 0 && empty($userData['email']) === false) {
2039
            // If user pwd is empty then generate a new one and send it to user
2040
            if (empty($post_user_password) === true) {
2041
                // Generate new password
2042
                $post_user_password = generateQuickPassword();
2043
            }
2044
2045
            // If LDAP enabled, then
2046
            // check that this password is correct
2047
            $continue = true;
2048
            if ($userData['auth_type'] === 'ldap' && (int) $SETTINGS['ldap_mode'] === 1) {
2049
                $continue = ldapCheckUserPassword(
2050
                    $userData['login'],
2051
                    $post_user_password,
2052
                    $SETTINGS
2053
                );
2054
            }
2055
2056
            if ($continue === true) {
2057
                // Only change if email is successfull
2058
                $passwordManager = new PasswordManager();
2059
                // GEnerate new keys
2060
                $userKeys = generateUserKeys($post_user_password);
2061
2062
                // Update user account
2063
                DB::update(
2064
                    prefixTable('users'),
2065
                    array(
2066
                        'special' => $post_special,
2067
                        'pw' => $passwordManager->hashPassword($post_user_password),
2068
                        'public_key' => $userKeys['public_key'],
2069
                        'private_key' => $userKeys['private_key'],
2070
                        'last_pw_change' => time(),
2071
                    ),
2072
                    'id = %i',
2073
                    $post_user_id
2074
                );
2075
2076
                // Return
2077
                return prepareExchangedData(
2078
                    array(
2079
                        'error' => false,
2080
                        'message' => '',
2081
                        'user_pwd' => $post_user_password,
2082
                        'user_email' => $userData['email'],
2083
                    ),
2084
                    'encode'
2085
                );
2086
            }
2087
            // Return error
2088
            return prepareExchangedData(
2089
                array(
2090
                    'error' => true,
2091
                    'message' => $lang->get('no_email_set'),
2092
                    'debug' => '',
2093
                    'self_change' => $post_self_change,
2094
                ),
2095
                'encode'
2096
            );
2097
        }
2098
2099
        // Error
2100
        return prepareExchangedData(
2101
            array(
2102
                'error' => true,
2103
                'message' => $lang->get('no_email_set'),
2104
                'debug' => '',
2105
            ),
2106
            'encode'
2107
        );
2108
    }
2109
    
2110
    return prepareExchangedData(
2111
        array(
2112
            'error' => true,
2113
            'message' => $lang->get('error_no_user'),
2114
            'debug' => '',
2115
        ),
2116
        'encode'
2117
    );
2118
}
2119
2120
function generateOneTimeCode(
2121
    int $userId
2122
): string
2123
{
2124
    // Load user's language
2125
    $session = SessionManager::getSession();
2126
    $lang = new Language($session->get('user-language') ?? 'english');
2127
2128
    if (isUserIdValid($userId) === true) {
2129
        // Get user info
2130
        $userData = DB::queryFirstRow(
2131
            'SELECT email, auth_type, login
2132
            FROM ' . prefixTable('users') . '
2133
            WHERE id = %i',
2134
            $userId
2135
        );
2136
        if (DB::count() > 0 && empty($userData['email']) === false) {
2137
            // Generate pwd
2138
            $password = generateQuickPassword();
2139
2140
            // GEnerate new keys
2141
            $userKeys = generateUserKeys($password);
2142
2143
            // Save in DB
2144
            DB::update(
2145
                prefixTable('users'),
2146
                array(
2147
                    'public_key' => $userKeys['public_key'],
2148
                    'private_key' => $userKeys['private_key'],
2149
                    'special' => 'generate-keys',
2150
                ),
2151
                'id=%i',
2152
                $userId
2153
            );
2154
2155
            return prepareExchangedData(
2156
                array(
2157
                    'error' => false,
2158
                    'message' => '',
2159
                    'code' => $password,
2160
                    'visible_otp' => ADMIN_VISIBLE_OTP_ON_LDAP_IMPORT,
2161
                ),
2162
                'encode'
2163
            );
2164
        }
2165
        
2166
        return prepareExchangedData(
2167
            array(
2168
                'error' => true,
2169
                'message' => $lang->get('no_email_set'),
2170
            ),
2171
            'encode'
2172
        );
2173
    }
2174
        
2175
    return prepareExchangedData(
2176
        array(
2177
            'error' => true,
2178
            'message' => $lang->get('error_no_user'),
2179
        ),
2180
        'encode'
2181
    );
2182
}
2183
2184
function startReEncryptingUserSharekeys(
2185
    int $post_user_id,
2186
    bool $post_self_change,
2187
    array $SETTINGS
2188
): string
2189
{
2190
    // Load user's language
2191
    $session = SessionManager::getSession();
2192
    $lang = new Language($session->get('user-language') ?? 'english');
2193
    
2194
    if (isUserIdValid($post_user_id) === true) {
2195
        // Check if user exists
2196
        DB::queryFirstRow(
2197
            'SELECT *
2198
            FROM ' . prefixTable('users') . '
2199
            WHERE id = %i',
2200
            $post_user_id
2201
        );
2202
        if (DB::count() > 0) {
2203
            // CLear old sharekeys
2204
            if ($post_self_change === false) {
2205
                deleteUserObjetsKeys($post_user_id, $SETTINGS);
2206
            }
2207
2208
            // Continu with next step
2209
            return prepareExchangedData(
2210
                array(
2211
                    'error' => false,
2212
                    'message' => '',
2213
                    'step' => 'step1',
2214
                    'userId' => $post_user_id,
2215
                    'start' => 0,
2216
                    'self_change' => $post_self_change,
2217
                ),
2218
                'encode'
2219
            );
2220
        }
2221
        // Nothing to do
2222
        return prepareExchangedData(
2223
            array(
2224
                'error' => true,
2225
                'message' => $lang->get('error_no_user'),
2226
            ),
2227
            'encode'
2228
        );
2229
    }
2230
2231
    return prepareExchangedData(
2232
        array(
2233
            'error' => true,
2234
            'message' => $lang->get('error_no_user'),
2235
        ),
2236
        'encode'
2237
    );
2238
}
2239
2240
/**
2241
 * Permits to encrypt user's keys
2242
 *
2243
 * @param integer $post_user_id
2244
 * @param boolean $post_self_change
2245
 * @param string $post_action
2246
 * @param integer $post_start
2247
 * @param integer $post_length
2248
 * @param array $SETTINGS
2249
 * @return string
2250
 */
2251
function continueReEncryptingUserSharekeys(
2252
    int     $post_user_id,
2253
    bool    $post_self_change,
2254
    string  $post_action,
2255
    int     $post_start,
2256
    int     $post_length,
2257
    array   $SETTINGS
2258
): string
2259
{
2260
    // Load user's language
2261
    $session = SessionManager::getSession();
2262
    $lang = new Language($session->get('user-language') ?? 'english');
2263
    
2264
    if (isUserIdValid($post_user_id) === true) {
2265
        // Check if user exists
2266
        $userInfo = DB::queryFirstRow(
2267
            'SELECT public_key
2268
            FROM ' . prefixTable('users') . '
2269
            WHERE id = %i',
2270
            $post_user_id
2271
        );
2272
        if (isset($userInfo['public_key']) === true) {
2273
            $return = [];
2274
2275
            // WHAT STEP TO PERFORM?
2276
            if ($post_action === 'step0') {
2277
                // CLear old sharekeys
2278
                if ($post_self_change === false) {
2279
                    deleteUserObjetsKeys($post_user_id, $SETTINGS);
2280
                }
2281
2282
                $return['post_action'] = 'step10';
2283
            }
2284
            
2285
            // STEP 1 - ITEMS
2286
            elseif ($post_action === 'step10') {
2287
                $return = continueReEncryptingUserSharekeysStep10(
2288
                    $post_user_id,
2289
                    $post_self_change,
2290
                    $post_action,
2291
                    $post_start,
2292
                    $post_length,
2293
                    $userInfo['public_key'],
2294
                    $SETTINGS
2295
                );
2296
            }
2297
2298
            // STEP 2 - LOGS
2299
            elseif ($post_action === 'step20') {
2300
                $return = continueReEncryptingUserSharekeysStep20(
2301
                    $post_user_id,
2302
                    $post_self_change,
2303
                    $post_action,
2304
                    $post_start,
2305
                    $post_length,
2306
                    $userInfo['public_key'],
2307
                    $SETTINGS
2308
                );
2309
            }
2310
2311
            // STEP 3 - FIELDS
2312
            elseif ($post_action === 'step30') {
2313
                $return = continueReEncryptingUserSharekeysStep30(
2314
                    $post_user_id,
2315
                    $post_self_change,
2316
                    $post_action,
2317
                    $post_start,
2318
                    $post_length,
2319
                    $userInfo['public_key'],
2320
                    $SETTINGS
2321
                );
2322
            }
2323
            
2324
            // STEP 4 - SUGGESTIONS
2325
            elseif ($post_action === 'step40') {
2326
                $return = continueReEncryptingUserSharekeysStep40(
2327
                    $post_user_id,
2328
                    $post_self_change,
2329
                    $post_action,
2330
                    $post_start,
2331
                    $post_length,
2332
                    $userInfo['public_key'],
2333
                    $SETTINGS
2334
                );
2335
            }
2336
            
2337
            // STEP 5 - FILES
2338
            elseif ($post_action === 'step50') {
2339
                $return = continueReEncryptingUserSharekeysStep50(
2340
                    $post_user_id,
2341
                    $post_self_change,
2342
                    $post_action,
2343
                    $post_start,
2344
                    $post_length,
2345
                    $userInfo['public_key'],
2346
                    $SETTINGS
2347
                );
2348
            }
2349
            
2350
            // STEP 6 - PERSONAL ITEMS
2351
            elseif ($post_action === 'step60') {
2352
                $return = continueReEncryptingUserSharekeysStep60(
2353
                    $post_user_id,
2354
                    $post_self_change,
2355
                    $post_action,
2356
                    $post_start,
2357
                    $post_length,
2358
                    $userInfo['public_key'],
2359
                    $SETTINGS
2360
                );
2361
            }
2362
            
2363
            // Continu with next step
2364
            return prepareExchangedData(
2365
                array(
2366
                    'error' => false,
2367
                    'message' => '',
2368
                    'step' => isset($return['post_action']) === true ? $return['post_action'] : '',
2369
                    'start' => isset($return['next_start']) === true ? $return['next_start'] : 0,
2370
                    'userId' => $post_user_id,
2371
                    'self_change' => $post_self_change,
2372
                ),
2373
                'encode'
2374
            );
2375
        }
2376
        
2377
        // Nothing to do
2378
        return prepareExchangedData(
2379
            array(
2380
                'error' => false,
2381
                'message' => '',
2382
                'step' => 'finished',
2383
                'start' => 0,
2384
                'userId' => $post_user_id,
2385
                'self_change' => $post_self_change,
2386
            ),
2387
            'encode'
2388
        );
2389
    }
2390
    
2391
    // Nothing to do
2392
    return prepareExchangedData(
2393
        array(
2394
            'error' => true,
2395
            'message' => $lang->get('error_no_user'),
2396
            'extra' => $post_user_id,
2397
        ),
2398
        'encode'
2399
    );
2400
}
2401
2402
function continueReEncryptingUserSharekeysStep10(
2403
    int $post_user_id,
2404
    bool $post_self_change,
2405
    string $post_action,
2406
    int $post_start,
2407
    int $post_length,
2408
    string $user_public_key,
2409
    array $SETTINGS
2410
): array 
2411
{
2412
    $session = SessionManager::getSession();
2413
    // Loop on items
2414
    $rows = DB::query(
2415
        'SELECT id, pw
2416
        FROM ' . prefixTable('items') . '
2417
        WHERE perso = 0
2418
        LIMIT ' . $post_start . ', ' . $post_length
2419
    );
2420
    foreach ($rows as $record) {
2421
        // Get itemKey from current user
2422
        $currentUserKey = DB::queryFirstRow(
2423
            'SELECT share_key, increment_id
2424
            FROM ' . prefixTable('sharekeys_items') . '
2425
            WHERE object_id = %i AND user_id = %i',
2426
            $record['id'],
2427
            $session->get('user-id')
2428
        );
2429
2430
        // do we have any input? (#3481)
2431
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2432
            continue;
2433
        }
2434
2435
        // Decrypt itemkey with admin key
2436
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2437
        
2438
        // Encrypt Item key
2439
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2440
        
2441
        // Save the key in DB
2442
        if ($post_self_change === false) {
2443
            DB::insert(
2444
                prefixTable('sharekeys_items'),
2445
                array(
2446
                    'object_id' => (int) $record['id'],
2447
                    'user_id' => (int) $post_user_id,
2448
                    'share_key' => $share_key_for_item,
2449
                )
2450
            );
2451
        } else {
2452
            // Get itemIncrement from selected user
2453
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2454
                $currentUserKey = DB::queryFirstRow(
2455
                    'SELECT increment_id
2456
                    FROM ' . prefixTable('sharekeys_items') . '
2457
                    WHERE object_id = %i AND user_id = %i',
2458
                    $record['id'],
2459
                    $post_user_id
2460
                );
2461
2462
                if (DB::count() > 0) {
2463
                    // NOw update
2464
                    DB::update(
2465
                        prefixTable('sharekeys_items'),
2466
                        array(
2467
                            'share_key' => $share_key_for_item,
2468
                        ),
2469
                        'increment_id = %i',
2470
                        $currentUserKey['increment_id']
2471
                    );
2472
                } else {
2473
                    DB::insert(
2474
                        prefixTable('sharekeys_items'),
2475
                        array(
2476
                            'object_id' => (int) $record['id'],
2477
                            'user_id' => (int) $post_user_id,
2478
                            'share_key' => $share_key_for_item,
2479
                        )
2480
                    );
2481
                }
2482
            }
2483
        }
2484
    }
2485
2486
    // SHould we change step?
2487
    DB::query(
2488
        'SELECT *
2489
        FROM ' . prefixTable('items') . '
2490
        WHERE perso = 0'
2491
    );
2492
2493
    $next_start = (int) $post_start + (int) $post_length;
2494
    return [
2495
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2496
        'post_action' => $next_start > DB::count() ? 'step20' : 'step10',
2497
    ];
2498
}
2499
2500
function continueReEncryptingUserSharekeysStep20(
2501
    int $post_user_id,
2502
    bool $post_self_change,
2503
    string $post_action,
2504
    int $post_start,
2505
    int $post_length,
2506
    string $user_public_key,
2507
    array $SETTINGS
2508
): array
2509
{
2510
    $session = SessionManager::getSession();
2511
    // Loop on logs
2512
    $rows = DB::query(
2513
        'SELECT increment_id
2514
        FROM ' . prefixTable('log_items') . '
2515
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"
2516
        LIMIT ' . $post_start . ', ' . $post_length
2517
    );
2518
    foreach ($rows as $record) {
2519
        // Get itemKey from current user
2520
        $currentUserKey = DB::queryFirstRow(
2521
            'SELECT share_key
2522
            FROM ' . prefixTable('sharekeys_logs') . '
2523
            WHERE object_id = %i AND user_id = %i',
2524
            $record['increment_id'],
2525
            $session->get('user-id')
2526
        );
2527
2528
        // do we have any input? (#3481)
2529
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2530
            continue;
2531
        }
2532
2533
        // Decrypt itemkey with admin key
2534
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2535
2536
        // Encrypt Item key
2537
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2538
2539
        // Save the key in DB
2540
        if ($post_self_change === false) {
2541
            DB::insert(
2542
                prefixTable('sharekeys_logs'),
2543
                array(
2544
                    'object_id' => (int) $record['increment_id'],
2545
                    'user_id' => (int) $post_user_id,
2546
                    'share_key' => $share_key_for_item,
2547
                )
2548
            );
2549
        } else {
2550
            // Get itemIncrement from selected user
2551
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2552
                $currentUserKey = DB::queryFirstRow(
2553
                    'SELECT increment_id
2554
                    FROM ' . prefixTable('sharekeys_items') . '
2555
                    WHERE object_id = %i AND user_id = %i',
2556
                    $record['id'],
2557
                    $post_user_id
2558
                );
2559
            }
2560
2561
            // NOw update
2562
            DB::update(
2563
                prefixTable('sharekeys_logs'),
2564
                array(
2565
                    'share_key' => $share_key_for_item,
2566
                ),
2567
                'increment_id = %i',
2568
                $currentUserKey['increment_id']
2569
            );
2570
        }
2571
    }
2572
2573
    // SHould we change step?
2574
    DB::query(
2575
        'SELECT increment_id
2576
        FROM ' . prefixTable('log_items') . '
2577
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"'
2578
    );
2579
2580
    $next_start = (int) $post_start + (int) $post_length;
2581
    return [
2582
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2583
        'post_action' => $next_start > DB::count() ? 'step30' : 'step20',
2584
    ];
2585
}
2586
2587
function continueReEncryptingUserSharekeysStep30(
2588
    int $post_user_id,
2589
    bool $post_self_change,
2590
    string $post_action,
2591
    int $post_start,
2592
    int $post_length,
2593
    string $user_public_key,
2594
    array $SETTINGS
2595
): array
2596
{
2597
    $session = SessionManager::getSession();
2598
    // Loop on fields
2599
    $rows = DB::query(
2600
        'SELECT id
2601
        FROM ' . prefixTable('categories_items') . '
2602
        WHERE encryption_type = "teampass_aes"
2603
        LIMIT ' . $post_start . ', ' . $post_length
2604
    );
2605
    foreach ($rows as $record) {
2606
        // Get itemKey from current user
2607
        $currentUserKey = DB::queryFirstRow(
2608
            'SELECT share_key
2609
            FROM ' . prefixTable('sharekeys_fields') . '
2610
            WHERE object_id = %i AND user_id = %i',
2611
            $record['id'],
2612
            $session->get('user-id')
2613
        );
2614
2615
        // do we have any input? (#3481)
2616
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2617
            continue;
2618
        }
2619
2620
        // Decrypt itemkey with admin key
2621
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2622
2623
        // Encrypt Item key
2624
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2625
2626
        // Save the key in DB
2627
        if ($post_self_change === false) {
2628
            DB::insert(
2629
                prefixTable('sharekeys_fields'),
2630
                array(
2631
                    'object_id' => (int) $record['id'],
2632
                    'user_id' => (int) $post_user_id,
2633
                    'share_key' => $share_key_for_item,
2634
                )
2635
            );
2636
        } else {
2637
            // Get itemIncrement from selected user
2638
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2639
                $currentUserKey = DB::queryFirstRow(
2640
                    'SELECT increment_id
2641
                    FROM ' . prefixTable('sharekeys_items') . '
2642
                    WHERE object_id = %i AND user_id = %i',
2643
                    $record['id'],
2644
                    $post_user_id
2645
                );
2646
            }
2647
2648
            // NOw update
2649
            DB::update(
2650
                prefixTable('sharekeys_fields'),
2651
                array(
2652
                    'share_key' => $share_key_for_item,
2653
                ),
2654
                'increment_id = %i',
2655
                $currentUserKey['increment_id']
2656
            );
2657
        }
2658
    }
2659
2660
    // SHould we change step?
2661
    DB::query(
2662
        'SELECT *
2663
        FROM ' . prefixTable('categories_items') . '
2664
        WHERE encryption_type = "teampass_aes"'
2665
    );
2666
2667
    $next_start = (int) $post_start + (int) $post_length;
2668
    return [
2669
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2670
        'post_action' => $next_start > DB::count() ? 'step40' : 'step30',
2671
    ];
2672
}
2673
2674
function continueReEncryptingUserSharekeysStep40(
2675
    int $post_user_id,
2676
    bool $post_self_change,
2677
    string $post_action,
2678
    int $post_start,
2679
    int $post_length,
2680
    string $user_public_key,
2681
    array $SETTINGS
2682
): array
2683
{
2684
    $session = SessionManager::getSession();
2685
    // Loop on suggestions
2686
    $rows = DB::query(
2687
        'SELECT id
2688
        FROM ' . prefixTable('suggestion') . '
2689
        LIMIT ' . $post_start . ', ' . $post_length
2690
    );
2691
    foreach ($rows as $record) {
2692
        // Get itemKey from current user
2693
        $currentUserKey = DB::queryFirstRow(
2694
            'SELECT share_key
2695
            FROM ' . prefixTable('sharekeys_suggestions') . '
2696
            WHERE object_id = %i AND user_id = %i',
2697
            $record['id'],
2698
            $session->get('user-id')
2699
        );
2700
2701
        // do we have any input? (#3481)
2702
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2703
            continue;
2704
        }
2705
2706
        // Decrypt itemkey with admin key
2707
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2708
2709
        // Encrypt Item key
2710
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2711
2712
        // Save the key in DB
2713
        if ($post_self_change === false) {
2714
            DB::insert(
2715
                prefixTable('sharekeys_suggestions'),
2716
                array(
2717
                    'object_id' => (int) $record['id'],
2718
                    'user_id' => (int) $post_user_id,
2719
                    'share_key' => $share_key_for_item,
2720
                )
2721
            );
2722
        } else {
2723
            // Get itemIncrement from selected user
2724
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2725
                $currentUserKey = DB::queryFirstRow(
2726
                    'SELECT increment_id
2727
                    FROM ' . prefixTable('sharekeys_items') . '
2728
                    WHERE object_id = %i AND user_id = %i',
2729
                    $record['id'],
2730
                    $post_user_id
2731
                );
2732
            }
2733
2734
            // NOw update
2735
            DB::update(
2736
                prefixTable('sharekeys_suggestions'),
2737
                array(
2738
                    'share_key' => $share_key_for_item,
2739
                ),
2740
                'increment_id = %i',
2741
                $currentUserKey['increment_id']
2742
            );
2743
        }
2744
    }
2745
2746
    // SHould we change step?
2747
    DB::query(
2748
        'SELECT *
2749
        FROM ' . prefixTable('suggestion')
2750
    );
2751
2752
    $next_start = (int) $post_start + (int) $post_length;
2753
    return [
2754
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2755
        'post_action' => $next_start > DB::count() ? 'step50' : 'step40',
2756
    ];
2757
}
2758
2759
function continueReEncryptingUserSharekeysStep50(
2760
    int $post_user_id,
2761
    bool $post_self_change,
2762
    string $post_action,
2763
    int $post_start,
2764
    int $post_length,
2765
    string $user_public_key,
2766
    array $SETTINGS
2767
): array
2768
{
2769
    $session = SessionManager::getSession();
2770
    // Loop on files
2771
    $rows = DB::query(
2772
        'SELECT id
2773
        FROM ' . prefixTable('files') . '
2774
        WHERE status = "' . TP_ENCRYPTION_NAME . '"
2775
        LIMIT ' . $post_start . ', ' . $post_length
2776
    ); //aes_encryption
2777
    foreach ($rows as $record) {
2778
        // Get itemKey from current user
2779
        $currentUserKey = DB::queryFirstRow(
2780
            'SELECT share_key
2781
            FROM ' . prefixTable('sharekeys_files') . '
2782
            WHERE object_id = %i AND user_id = %i',
2783
            $record['id'],
2784
            $session->get('user-id')
2785
        );
2786
2787
        // do we have any input? (#3481)
2788
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2789
            continue;
2790
        }
2791
2792
        // Decrypt itemkey with admin key
2793
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2794
2795
        // Encrypt Item key
2796
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2797
2798
        // Save the key in DB
2799
        if ($post_self_change === false) {
2800
            DB::insert(
2801
                prefixTable('sharekeys_files'),
2802
                array(
2803
                    'object_id' => (int) $record['id'],
2804
                    'user_id' => (int) $post_user_id,
2805
                    'share_key' => $share_key_for_item,
2806
                )
2807
            );
2808
        } else {
2809
            // Get itemIncrement from selected user
2810
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2811
                $currentUserKey = DB::queryFirstRow(
2812
                    'SELECT increment_id
2813
                    FROM ' . prefixTable('sharekeys_items') . '
2814
                    WHERE object_id = %i AND user_id = %i',
2815
                    $record['id'],
2816
                    $post_user_id
2817
                );
2818
            }
2819
2820
            // NOw update
2821
            DB::update(
2822
                prefixTable('sharekeys_files'),
2823
                array(
2824
                    'share_key' => $share_key_for_item,
2825
                ),
2826
                'increment_id = %i',
2827
                $currentUserKey['increment_id']
2828
            );
2829
        }
2830
    }
2831
2832
    // SHould we change step?
2833
    DB::query(
2834
        'SELECT *
2835
        FROM ' . prefixTable('files') . '
2836
        WHERE status = "' . TP_ENCRYPTION_NAME . '"'
2837
    );
2838
2839
    $next_start = (int) $post_start + (int) $post_length;
2840
    return [
2841
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2842
        'post_action' => $next_start > DB::count() ? 'step60' : 'step50',
2843
    ];
2844
}
2845
2846
function continueReEncryptingUserSharekeysStep60(
2847
    int $post_user_id,
2848
    bool $post_self_change,
2849
    string $post_action,
2850
    int $post_start,
2851
    int $post_length,
2852
    string $user_public_key,
2853
    array $SETTINGS
2854
): array
2855
{
2856
    $session = SessionManager::getSession();
2857
    // IF USER IS NOT THE SAME
2858
    if ((int) $post_user_id === (int) $session->get('user-id')) {
2859
        return [
2860
            'next_start' => 0,
2861
            'post_action' => 'finished',
2862
        ];
2863
    }
2864
    
2865
    // Loop on persoanl items
2866
    if (count($session->get('user-personal_folders')) > 0) {
2867
        $rows = DB::query(
2868
            'SELECT id, pw
2869
            FROM ' . prefixTable('items') . '
2870
            WHERE perso = 1 AND id_tree IN %ls AND encryption_type = %s
2871
            LIMIT ' . $post_start . ', ' . $post_length,
2872
            $session->get('user-personal_folders'),
2873
            "defuse"
2874
        );
2875
        foreach ($rows as $record) {
2876
            // Get itemKey from current user
2877
            $currentUserKey = DB::queryFirstRow(
2878
                'SELECT share_key, increment_id
2879
                FROM ' . prefixTable('sharekeys_items') . '
2880
                WHERE object_id = %i AND user_id = %i',
2881
                $record['id'],
2882
                $session->get('user-id')
2883
            );
2884
2885
            // Decrypt itemkey with admin key
2886
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2887
2888
            // Encrypt Item key
2889
            $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2890
2891
            // Save the key in DB
2892
            if ($post_self_change === false) {
2893
                DB::insert(
2894
                    prefixTable('sharekeys_items'),
2895
                    array(
2896
                        'object_id' => (int) $record['id'],
2897
                        'user_id' => (int) $post_user_id,
2898
                        'share_key' => $share_key_for_item,
2899
                    )
2900
                );
2901
            } else {
2902
                // Get itemIncrement from selected user
2903
                if ((int) $post_user_id !== (int) $session->get('user-id')) {
2904
                    $currentUserKey = DB::queryFirstRow(
2905
                        'SELECT increment_id
2906
                        FROM ' . prefixTable('sharekeys_items') . '
2907
                        WHERE object_id = %i AND user_id = %i',
2908
                        $record['id'],
2909
                        $post_user_id
2910
                    );
2911
                }
2912
2913
                // NOw update
2914
                DB::update(
2915
                    prefixTable('sharekeys_items'),
2916
                    array(
2917
                        'share_key' => $share_key_for_item,
2918
                    ),
2919
                    'increment_id = %i',
2920
                    $currentUserKey['increment_id']
2921
                );
2922
            }
2923
        }
2924
    }
2925
2926
    // SHould we change step?
2927
    DB::query(
2928
        'SELECT *
2929
        FROM ' . prefixTable('items') . '
2930
        WHERE perso = 0'
2931
    );
2932
2933
    $next_start = (int) $post_start + (int) $post_length;
2934
    return [
2935
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2936
        'post_action' => $next_start > DB::count() ? 'finished' : 'step60',
2937
    ];
2938
}
2939
2940
function migrateTo3_DoUserPersonalItemsEncryption(
2941
    int $post_user_id,
2942
    int $post_start,
2943
    int $post_length,
2944
    int $post_counterItemsToTreat,
2945
    string $post_user_psk,
2946
    array $SETTINGS
2947
) {
2948
    $next_step = 'psk';
2949
    
2950
    $session = SessionManager::getSession();
2951
    $lang = new Language($session->get('user-language') ?? 'english');
2952
    
2953
    if (isUserIdValid($post_user_id) === true) {
2954
        // Check if user exists
2955
        $userInfo = DB::queryFirstRow(
2956
            'SELECT public_key, encrypted_psk
2957
            FROM ' . prefixTable('users') . '
2958
            WHERE id = %i',
2959
            $post_user_id
2960
        );
2961
        if (DB::count() > 0) {
2962
            // check if psk is correct.
2963
            if (empty($userInfo['encrypted_psk']) === false) {//echo $post_user_psk." ;; ".$userInfo['encrypted_psk']." ;; ";
2964
                $user_key_encoded = defuse_validate_personal_key(
2965
                    html_entity_decode($post_user_psk), // convert tspecial string back to their original characters due to FILTER_SANITIZE_FULL_SPECIAL_CHARS
2966
                    $userInfo['encrypted_psk']
2967
                );
2968
2969
                if (strpos($user_key_encoded, "Error ") !== false) {
2970
                    return prepareExchangedData(
2971
                        array(
2972
                            'error' => true,
2973
                            'message' => $lang->get('bad_psk'),
2974
                        ),
2975
                        'encode'
2976
                    );
2977
                }
2978
2979
                // Get number of user's personal items with no AES encryption
2980
                if ($post_counterItemsToTreat === -1) {
2981
                    DB::query(
2982
                        'SELECT id
2983
                        FROM ' . prefixTable('items') . '
2984
                        WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
2985
                        $session->get('user-personal_folders'),
2986
                        'teampass_aes'
2987
                    );
2988
                    $countUserPersonalItems = DB::count();
2989
                } else {
2990
                    $countUserPersonalItems = $post_counterItemsToTreat;
2991
                }
2992
2993
                // Loop on persoanl items
2994
                $rows = DB::query(
2995
                    'SELECT id, pw
2996
                    FROM ' . prefixTable('items') . '
2997
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s
2998
                    LIMIT ' . $post_length,
2999
                    $session->get('user-personal_folders'),
3000
                    'teampass_aes'
3001
                );
3002
                foreach ($rows as $record) {
3003
                    // Decrypt with Defuse
3004
                    $passwd = cryption(
3005
                        $record['pw'],
3006
                        $user_key_encoded,
3007
                        'decrypt',
3008
                        $SETTINGS
3009
                    );
3010
3011
                    // Encrypt with Object Key
3012
                    $cryptedStuff = doDataEncryption(html_entity_decode($passwd['string']));
3013
3014
                    // Store new password in DB
3015
                    DB::update(
3016
                        prefixTable('items'),
3017
                        array(
3018
                            'pw' => $cryptedStuff['encrypted'],
3019
                            'encryption_type' => 'teampass_aes',
3020
                        ),
3021
                        'id = %i',
3022
                        $record['id']
3023
                    );
3024
3025
                    // Insert in DB the new object key for this item by user
3026
                    DB::insert(
3027
                        prefixTable('sharekeys_items'),
3028
                        array(
3029
                            'object_id' => (int) $record['id'],
3030
                            'user_id' => (int) $post_user_id,
3031
                            'share_key' => encryptUserObjectKey($cryptedStuff['objectKey'], $userInfo['public_key']),
3032
                        )
3033
                    );
3034
3035
3036
                    // Does this item has Files?
3037
                    // Loop on files
3038
                    $rows = DB::query(
3039
                        'SELECT id, file
3040
                        FROM ' . prefixTable('files') . '
3041
                        WHERE status != %s
3042
                        AND id_item = %i',
3043
                        TP_ENCRYPTION_NAME,
3044
                        $record['id']
3045
                    );
3046
                    //aes_encryption
3047
                    foreach ($rows as $record2) {
3048
                        // Now decrypt the file
3049
                        prepareFileWithDefuse(
3050
                            'decrypt',
3051
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'],
3052
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'] . '.delete',
3053
                            $post_user_psk
3054
                        );
3055
3056
                        // Encrypt the file
3057
                        $encryptedFile = encryptFile($record2['file'] . '.delete', $SETTINGS['path_to_upload_folder']);
3058
3059
                        DB::update(
3060
                            prefixTable('files'),
3061
                            array(
3062
                                'file' => $encryptedFile['fileHash'],
3063
                                'status' => TP_ENCRYPTION_NAME,
3064
                            ),
3065
                            'id = %i',
3066
                            $record2['id']
3067
                        );
3068
3069
                        // Save key
3070
                        DB::insert(
3071
                            prefixTable('sharekeys_files'),
3072
                            array(
3073
                                'object_id' => (int) $record2['id'],
3074
                                'user_id' => (int) $session->get('user-id'),
3075
                                'share_key' => encryptUserObjectKey($encryptedFile['objectKey'], $session->get('user-public_key')),
3076
                            )
3077
                        );
3078
3079
                        // Unlink original file
3080
                        unlink($SETTINGS['path_to_upload_folder'] . '/' . $record2['file']);
3081
                    }
3082
                }
3083
3084
                // SHould we change step?
3085
                $next_start = (int) $post_start + (int) $post_length;
3086
                DB::query(
3087
                    'SELECT id
3088
                    FROM ' . prefixTable('items') . '
3089
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
3090
                    $session->get('user-personal_folders'),
3091
                    'teampass_aes'
3092
                );
3093
                if (DB::count() === 0 || ($next_start - $post_length) >= $countUserPersonalItems) {
3094
                    // Now update user
3095
                    DB::update(
3096
                        prefixTable('users'),
3097
                        array(
3098
                            'special' => 'none',
3099
                            'upgrade_needed' => 0,
3100
                            'encrypted_psk' => '',
3101
                        ),
3102
                        'id = %i',
3103
                        $post_user_id
3104
                    );
3105
3106
                    $next_step = 'finished';
3107
                    $next_start = 0;
3108
                }
3109
3110
                // Continu with next step
3111
                return prepareExchangedData(
3112
                    array(
3113
                        'error' => false,
3114
                        'message' => '',
3115
                        'step' => $next_step,
3116
                        'start' => $next_start,
3117
                        'userId' => $post_user_id
3118
                    ),
3119
                    'encode'
3120
                );
3121
            }
3122
        }
3123
        
3124
        // Nothing to do
3125
        return prepareExchangedData(
3126
            array(
3127
                'error' => true,
3128
                'message' => $lang->get('error_no_user'),
3129
            ),
3130
            'encode'
3131
        );
3132
    }
3133
    
3134
    // Nothing to do
3135
    return prepareExchangedData(
3136
        array(
3137
            'error' => true,
3138
            'message' => $lang->get('error_no_user'),
3139
        ),
3140
        'encode'
3141
    );
3142
}
3143
3144
3145
function getUserInfo(
3146
    int $post_user_id,
3147
    array $SETTINGS
3148
)
3149
{
3150
    // Load user's language
3151
    $session = SessionManager::getSession();
3152
    $lang = new Language($session->get('user-language') ?? 'english');
3153
    
3154
    if (isUserIdValid($post_user_id) === true) {
3155
        // Get user info
3156
        $userData = DB::queryFirstRow(
3157
            'SELECT special, auth_type, is_ready_for_usage, ongoing_process_id, otp_provided, keys_recovery_time
3158
            FROM ' . prefixTable('users') . '
3159
            WHERE id = %i',
3160
            $post_user_id
3161
        );
3162
        if (DB::count() > 0) {
3163
            return prepareExchangedData(
3164
                array(
3165
                    'error' => false,
3166
                    'message' => '',
3167
                    'queryResults' => $userData,
3168
                ),
3169
                'encode'
3170
            );
3171
        }
3172
    }
3173
    return prepareExchangedData(
3174
        array(
3175
            'error' => true,
3176
            'message' => $lang->get('error_no_user'),
3177
        ),
3178
        'encode'
3179
    );
3180
}
3181
3182
/**
3183
 * Change user auth password
3184
 *
3185
 * @param integer $post_user_id
3186
 * @param string $post_current_pwd
3187
 * @param string $post_new_pwd
3188
 * @param array $SETTINGS
3189
 * @return string
3190
 */
3191
function changeUserAuthenticationPassword(
3192
    int $post_user_id,
3193
    string $post_current_pwd,
3194
    string $post_new_pwd,
3195
    array $SETTINGS
3196
)
3197
{
3198
    $session = SessionManager::getSession();
3199
    $lang = new Language($session->get('user-language') ?? 'english');
3200
 
3201
    if (isUserIdValid($post_user_id) === true) {
3202
        // Get user info
3203
        $userData = DB::queryFirstRow(
3204
            'SELECT auth_type, login, private_key
3205
            FROM ' . prefixTable('users') . '
3206
            WHERE id = %i',
3207
            $post_user_id
3208
        );
3209
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
3210
            // Now check if current password is correct
3211
            // For this, just check if it is possible to decrypt the privatekey
3212
            // And compare it to the one in session
3213
            try {
3214
                $privateKey = decryptPrivateKey($post_current_pwd, $userData['private_key']);
3215
            } catch (Exception $e) {
3216
                return prepareExchangedData(
3217
                    array(
3218
                        'error' => true,
3219
                        'message' => $lang->get('bad_password'),
3220
                    ),
3221
                    'encode'
3222
                );
3223
            }
3224
3225
            $lang = new Language($session->get('user-language') ?? 'english');
3226
3227
            if ($session->get('user-private_key') === $privateKey) {
3228
                // Encrypt it with new password
3229
                $hashedPrivateKey = encryptPrivateKey($post_new_pwd, $privateKey);
3230
3231
                // Generate new hash for auth password
3232
                $passwordManager = new PasswordManager();
3233
3234
                // Prepare variables
3235
                $newPw = $passwordManager->hashPassword($post_new_pwd);
3236
3237
                // Update user account
3238
                DB::update(
3239
                    prefixTable('users'),
3240
                    array(
3241
                        'private_key' => $hashedPrivateKey,
3242
                        'pw' => $newPw,
3243
                        'special' => 'none',
3244
                        'last_pw_change' => time(),
3245
                    ),
3246
                    'id = %i',
3247
                    $post_user_id
3248
                );
3249
3250
                $session->set('user-private_key', $privateKey);
3251
3252
                return prepareExchangedData(
3253
                    array(
3254
                        'error' => false,
3255
                        'message' => $lang->get('done'),'',
3256
                    ),
3257
                    'encode'
3258
                );
3259
            }
3260
            
3261
            // ERROR
3262
            return prepareExchangedData(
3263
                array(
3264
                    'error' => true,
3265
                    'message' => $lang->get('bad_password'),
3266
                ),
3267
                'encode'
3268
            );
3269
        }
3270
    }
3271
        
3272
    return prepareExchangedData(
3273
        array(
3274
            'error' => true,
3275
            'message' => $lang->get('error_no_user'),
3276
        ),
3277
        'encode'
3278
    );
3279
}
3280
3281
/**
3282
 * Change user LDAP auth password
3283
 *
3284
 * @param integer $post_user_id
3285
 * @param string $post_previous_pwd
3286
 * @param string $post_current_pwd
3287
 * @param string $post_action_type
3288
 * @return string
3289
 */            
3290
function changeUserLDAPAuthenticationPassword(
3291
    int $post_user_id,
3292
    string $post_previous_pwd,
3293
    string $post_current_pwd
3294
)
3295
{
3296
    $session = SessionManager::getSession();
3297
    // Load user's language
3298
    $lang = new Language($session->get('user-language') ?? 'english');
3299
    
3300
    if (isUserIdValid($post_user_id) === true) {
3301
        // Get user info
3302
        $userData = DB::queryFirstRow(
3303
            'SELECT u.auth_type, u.login, u.private_key, u.special
3304
            FROM ' . prefixTable('users') . ' AS u
3305
            WHERE u.id = %i',
3306
            $post_user_id
3307
        );
3308
3309
        if (DB::count() > 0) {
3310
            // 
3311
            if ($userData['auth_type'] === 'ldap' && empty($userData['private_key']) === false && $userData['special'] === 'encrypt_personal_items') {
3312
                // Now check if current password is correct (only if not ldap)
3313
                if ($userData['special'] === 'auth-pwd-change') {
3314
                    // As it is a change for an LDAP user
3315
                    
3316
                    // Now check if current password is correct
3317
                    // For this, just check if it is possible to decrypt the privatekey
3318
                    // And compare it to the one in session
3319
                    $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3320
3321
                    // Encrypt it with new password
3322
                    $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3323
3324
                    // Update user account
3325
                    DB::update(
3326
                        prefixTable('users'),
3327
                        array(
3328
                            'private_key' => $hashedPrivateKey,
3329
                            'special' => 'none',
3330
                        ),
3331
                        'id = %i',
3332
                        $post_user_id
3333
                    );
3334
3335
                    $session->set('user-private_key', $privateKey);
3336
3337
                    return prepareExchangedData(
3338
                        array(
3339
                            'error' => false,
3340
                            'message' => $lang->get('done'),'',
3341
                        ),
3342
                        'encode'
3343
                    );
3344
                }
3345
3346
                // For this, just check if it is possible to decrypt the privatekey
3347
                // And try to decrypt one existing key
3348
                $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3349
3350
                if (empty($privateKey) === true) {
3351
                    return prepareExchangedData(
3352
                        array(
3353
                            'error' => true,
3354
                            'message' => $lang->get('password_is_not_correct'),
3355
                        ),
3356
                        'encode'
3357
                    );
3358
                }
3359
                // Get one itemKey from current user
3360
                $currentUserKey = DB::queryFirstRow(
3361
                    'SELECT ski.share_key, ski.increment_id, l.id_user
3362
                    FROM ' . prefixTable('sharekeys_items') . ' AS ski
3363
                    INNER JOIN ' . prefixTable('log_items') . ' AS l ON ski.object_id = l.id_item
3364
                    WHERE ski.user_id = %i
3365
                    ORDER BY RAND()
3366
                    LIMIT 1',
3367
                    $post_user_id
3368
                );
3369
3370
                if (is_countable($currentUserKey) && count($currentUserKey) > 0) {
3371
                    // Decrypt itemkey with user key
3372
                    // use old password to decrypt private_key
3373
                    $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $privateKey);
3374
                    
3375
                    if (empty(base64_decode($itemKey)) === false) {
3376
                        // GOOD password
3377
                        // Encrypt it with current password
3378
                        $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3379
                        
3380
                        // Update user account
3381
                        DB::update(
3382
                            prefixTable('users'),
3383
                            array(
3384
                                'private_key' => $hashedPrivateKey,
3385
                                'special' => 'none',
3386
                            ),
3387
                            'id = %i',
3388
                            $post_user_id
3389
                        );
3390
                        
3391
                        $lang = new Language($session->get('user-language') ?? 'english');
3392
                        $session->set('user-private_key', $privateKey);
3393
3394
                        return prepareExchangedData(
3395
                            array(
3396
                                'error' => false,
3397
                                'message' => $lang->get('done'),
3398
                            ),
3399
                            'encode'
3400
                        );
3401
                    }
3402
                }
3403
                
3404
                // ERROR
3405
                return prepareExchangedData(
3406
                    array(
3407
                        'error' => true,
3408
                        'message' => $lang->get('bad_password'),
3409
                    ),
3410
                    'encode'
3411
                );
3412
            } elseif ($userData['special'] === 'encrypt_personal_items') {
3413
                // We need to find a valid previous private key
3414
                $validPreviousKey = findValidPreviousPrivateKey(
3415
                    $post_previous_pwd,
3416
                    $post_user_id
3417
                );
3418
3419
                if ($validPreviousKey['private_key'] !== null) {
3420
                    // Decrypt all personal items with this key
3421
                    // Launch the re-encryption process for personal items
3422
                    // Create process
3423
                    DB::insert(
3424
                        prefixTable('background_tasks'),
3425
                        array(
3426
                            'created_at' => time(),
3427
                            'process_type' => 'create_user_keys',
3428
                            'arguments' => json_encode([
3429
                                'new_user_id' => (int) $post_user_id,
3430
                                'new_user_pwd' => cryption($post_previous_pwd, '','encrypt')['string'],
3431
                                'new_user_private_key' => cryption($validPreviousKey['private_key'], '','encrypt')['string'],
3432
                                'send_email' => 0,
3433
                                'otp_provided_new_value' => 0,
3434
                                'user_self_change' => 1,
3435
                                'only_personal_items' => 1,
3436
                            ]),
3437
                        )
3438
                    );
3439
                    $processId = DB::insertId();
3440
3441
                    // Create tasks
3442
                    createUserTasks($processId, NUMBER_ITEMS_IN_BATCH);
3443
3444
                    // update user's new status
3445
                    DB::update(
3446
                        prefixTable('users'),
3447
                        [
3448
                            'is_ready_for_usage' => 1,
3449
                            'otp_provided' => 1,
3450
                            'ongoing_process_id' => $processId,
3451
                            'special' => 'none',
3452
                        ],
3453
                        'id=%i',
3454
                        $post_user_id
3455
                    );
3456
3457
                    return prepareExchangedData(
3458
                        array(
3459
                            'error' => false,
3460
                            'message' => $lang->get('done'),
3461
                        ),
3462
                        'encode'
3463
                    );
3464
                }
3465
                return prepareExchangedData(
3466
                    array(
3467
                        'error' => true,
3468
                        'message' => $lang->get('no_previous_valide_private_key'),
3469
                    ),
3470
                    'encode'
3471
                );
3472
            }
3473
        }
3474
    }
3475
3476
    // ERROR
3477
    return prepareExchangedData(
3478
        array(
3479
            'error' => true,
3480
            'message' => $lang->get('error_no_user'),
3481
        ),
3482
        'encode'
3483
    );
3484
}
3485
3486
/**
3487
 * Try to find a valid previous private key by testing all previous keys
3488
 * until one is able to decrypt the share_key of one item
3489
 * @param string $previousPassword
3490
 * @param int $userId
3491
 * @return array|null
3492
 */
3493
function findValidPreviousPrivateKey($previousPassword, $userId) {
3494
    // Retrieve all user's private keys in descending order (most recent first)
3495
    $privateKeys = DB::query(
3496
        "SELECT 
3497
            id,
3498
            private_key,
3499
            created_at
3500
        FROM " . prefixTable('user_private_keys') . "
3501
        WHERE user_id = %i
3502
        ORDER BY created_at DESC, id DESC",
3503
        $userId
3504
    );
3505
    
3506
    if (empty($privateKeys)) {
3507
        return null;
3508
    }
3509
    
3510
    // Loop through all private keys
3511
    foreach ($privateKeys as $row) {
3512
        $encryptedPrivateKey = $row['private_key'];
3513
        
3514
        // Attempt to decrypt the private key with the previous password
3515
        $privateKey = decryptPrivateKey($previousPassword, $encryptedPrivateKey);
3516
        
3517
        // If decryption succeeded (key not null)
3518
        if ($privateKey !== null) {
3519
            // Select one personal item share_key to test decryption
3520
            $currentUserItemKey = DB::queryFirstRow(
3521
                'SELECT si.share_key, si.increment_id, l.id_user, i.perso
3522
                FROM ' . prefixTable('sharekeys_items') . ' AS si
3523
                INNER JOIN ' . prefixTable('log_items') . ' AS l ON si.object_id = l.id_item
3524
                INNER JOIN ' . prefixTable('items') . ' AS i ON i.id = l.id_item
3525
                WHERE si.user_id = %i AND i.perso = 1 AND si.share_key != ""
3526
                ORDER BY RAND()
3527
                LIMIT 1',
3528
                $userId
3529
            );
3530
3531
            if (is_countable($currentUserItemKey) && count($currentUserItemKey) > 0) {
3532
                // Decrypt itemkey with user key
3533
                // use old password to decrypt private_key
3534
                $itemKey = decryptUserObjectKey($currentUserItemKey['share_key'], $privateKey);
3535
                if (empty(base64_decode($itemKey)) === false) {                
3536
                    return [
3537
                        'private_key' => $privateKey
3538
                    ];
3539
                }
3540
            }
3541
        }
3542
    }
3543
    
3544
    return null;
3545
}
3546
3547
/**
3548
 * Change user LDAP auth password
3549
 *
3550
 * @param integer $post_user_id
3551
 * @param string $post_current_pwd
3552
 * @param string $post_new_pwd
3553
 * @param array $SETTINGS
3554
 * @return string
3555
 */
3556
function increaseSessionDuration(
3557
    int $duration
3558
): string
3559
{
3560
    $session = SessionManager::getSession();
3561
    // check if session is not already expired.
3562
    if ($session->get('user-session_duration') > time()) {
3563
        // Calculate end of session
3564
        $session->set('user-session_duration', (int) $session->get('user-session_duration') + $duration);
3565
        // Update table
3566
        DB::update(
3567
            prefixTable('users'),
3568
            array(
3569
                'session_end' => $session->get('user-session_duration'),
3570
            ),
3571
            'id = %i',
3572
            $session->get('user-id')
3573
        );
3574
        // Return data
3575
        return '[{"new_value":"' . $session->get('user-session_duration') . '"}]';
3576
    }
3577
    
3578
    return '[{"new_value":"expired"}]';
3579
}
3580
3581
/**
3582
 * Generate an OTP secret and QR code
3583
 * @param string $label
3584
 * @param bool $with_qrcode
3585
 * @param string $secretKey
3586
 * @return string
3587
 */
3588
function generateAnOTP(string $label, bool $with_qrcode = false, string $secretKey = ''): string
3589
{
3590
    // generate new secret
3591
    $tfa = new TwoFactorAuth();
3592
    if ($secretKey === '') {
3593
        $secretKey = $tfa->createSecret();
3594
    }
3595
3596
    // generate new QR
3597
    if ($with_qrcode === true) {
3598
        $qrcode = $tfa->getQRCodeImageAsDataUri(
3599
            $label,
3600
            $secretKey
3601
        );
3602
    }
3603
3604
    // ERROR
3605
    return prepareExchangedData(
3606
        array(
3607
            'error' => false,
3608
            'message' => '',
3609
            'secret' => $secretKey,
3610
            'qrcode' => $qrcode ?? '',
3611
        ),
3612
        'encode'
3613
    );
3614
}
3615
3616
/**
3617
 * Reset all personal items keys for a user
3618
 * @param int $userId
3619
 * @return string
3620
 */
3621
function resetUserPersonalItemKeys(int $userId): string
3622
{
3623
    $personalItems = DB::query(
3624
        'SELECT i.id, i.pw, s.share_key, s.increment_id
3625
        FROM ' . prefixTable('items') . ' i
3626
        INNER JOIN ' . prefixTable('sharekeys_items') . ' s ON i.id = s.object_id
3627
        WHERE i.perso = %i
3628
        AND s.user_id = %i',
3629
        1,
3630
        $userId
3631
    );
3632
3633
    if (is_countable($personalItems) && count($personalItems) > 0) {
3634
        // Reset the keys for each personal item
3635
        foreach ($personalItems as $item) {
3636
            DB::update(
3637
                prefixTable('sharekeys_items'),
3638
                array('share_key' => ''),
3639
                'increment_id = %i',
3640
                $item['increment_id']
3641
            );
3642
        }
3643
3644
        // Update user special status
3645
                DB::update(
3646
                    prefixTable('users'),
3647
                    array(
3648
                        'special' => 'none',
3649
                    ),
3650
                    'id = %i',
3651
                    $userId
3652
                );
3653
    }
3654
3655
    return prepareExchangedData(
3656
        array(
3657
            'error' => false,
3658
        ),
3659
        'encode'
3660
    );
3661
}
3662
3663
/**
3664
 * Set user only personal items encryption
3665
 * @param string $userPreviousPwd
3666
 * @param string $userCurrentPwd
3667
 * @param bool $skipPasswordChange
3668
 * @param int $userId
3669
 * @return string
3670
 */
3671
function setUserOnlyPersonalItemsEncryption(string $userPreviousPwd, string $userCurrentPwd, bool $skipPasswordChange, int $userId): string
3672
{
3673
    $session = SessionManager::getSession();
3674
    $lang = new Language($session->get('user-language') ?? 'english');
3675
3676
    // In case where user doesn't know the previous password
3677
    // Then reset the status and remove sharekeys
3678
    if ($skipPasswordChange === true) {
3679
        // Remove all sharekeys for personal items
3680
        DB::query(
3681
            'UPDATE ' . prefixTable('sharekeys_items') . ' AS ski
3682
            INNER JOIN ' . prefixTable('items') . ' AS i ON ski.object_id = i.id
3683
            SET ski.share_key = ""
3684
            WHERE i.perso = 1
3685
            AND ski.user_id = %i',
3686
            $userId
3687
        );
3688
3689
        // Remove all sharekeys for personal files
3690
        DB::query(
3691
            'UPDATE ' . prefixTable('sharekeys_files') . ' AS skf
3692
            INNER JOIN ' . prefixTable('items') . ' AS i ON skf.object_id = i.id
3693
            SET skf.share_key = ""
3694
            WHERE i.perso = 1
3695
            AND skf.user_id = %i',
3696
            $userId
3697
        );
3698
3699
        // Remove all sharekeys for personal fields
3700
        DB::query(
3701
            'UPDATE ' . prefixTable('sharekeys_fields') . ' AS skf
3702
            INNER JOIN ' . prefixTable('items') . ' AS i ON skf.object_id = i.id
3703
            SET skf.share_key = ""
3704
            WHERE i.perso = 1
3705
            AND skf.user_id = %i',
3706
            $userId
3707
        );
3708
3709
        // Set user as ready for usage
3710
        DB::update(
3711
            prefixTable('users'),
3712
            array(
3713
                'ongoing_process_id' => NULL,
3714
                'special' => 'none',
3715
                'updated_at' => time(),
3716
            ),
3717
            'id = %i',
3718
            $userId
3719
        );
3720
3721
        return prepareExchangedData(
3722
            array(
3723
                'error' => false,
3724
                'message' => $lang->get('done'),
3725
            ),
3726
            'encode'
3727
        );
3728
    }
3729
3730
    // We need to find a valid previous private key
3731
    $validPreviousKey = findValidPreviousPrivateKey(
3732
        $userPreviousPwd,
3733
        $userId
3734
    );
3735
    if ($validPreviousKey['private_key'] !== null) {
3736
        // Decrypt all personal items with this key
3737
        // Launch the re-encryption process for personal items
3738
        // Create process
3739
        DB::insert(
3740
            prefixTable('background_tasks'),
3741
            array(
3742
                'created_at' => time(),
3743
                'process_type' => 'create_user_keys',
3744
                'arguments' => json_encode([
3745
                    'new_user_id' => (int) $userId,
3746
                    'new_user_pwd' => cryption($userCurrentPwd, '','encrypt')['string'],
3747
                    'new_user_private_key' => cryption($validPreviousKey['private_key'], '','encrypt')['string'],
3748
                    'send_email' => 0,
3749
                    'otp_provided_new_value' => 0,
3750
                    'user_self_change' => 1,
3751
                    'only_personal_items' => 1,
3752
                ]),
3753
            )
3754
        );
3755
        $processId = DB::insertId();
3756
3757
        // Create tasks
3758
        createUserTasks($processId, NUMBER_ITEMS_IN_BATCH);
3759
3760
        // update user's new status
3761
        DB::update(
3762
            prefixTable('users'),
3763
            [
3764
                'is_ready_for_usage' => 1,
3765
                'otp_provided' => 1,
3766
                'ongoing_process_id' => $processId,
3767
                'special' => 'none',
3768
            ],
3769
            'id=%i',
3770
            $userId
3771
        );
3772
3773
        return prepareExchangedData(
3774
            array(
3775
                'error' => false,
3776
                'message' => $lang->get('done'),
3777
            ),
3778
            'encode'
3779
        );
3780
    }
3781
    return prepareExchangedData(
3782
        array(
3783
            'error' => true,
3784
            'message' => $lang->get('no_previous_valide_private_key'),
3785
        ),
3786
        'encode'
3787
    );
3788
}