keyHandler()   F
last analyzed

Complexity

Conditions 30
Paths 27

Size

Total Lines 232
Code Lines 130

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 30
eloc 130
c 1
b 0
f 0
nc 27
nop 3
dl 0
loc 232
rs 3.3333

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