Passed
Push — master ( 6bf24f...97d9b0 )
by Nils
06:28 queued 11s
created

changePrivateKeyEncryptionPassword()   C

Complexity

Conditions 11
Paths 10

Size

Total Lines 101
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 11
eloc 56
c 3
b 1
f 0
nc 10
nop 5
dl 0
loc 101
rs 6.8133

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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