Passed
Pull Request — master (#4822)
by Nils
06:23
created

getPostInput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
// TODO : ajouter un check sue l'envoi de la key
54
55
// Load config
56
$configManager = new ConfigManager();
57
$SETTINGS = $configManager->getAllSettings();
58
59
// Do checks
60
// Instantiate the class with posted data
61
$checkUserAccess = new PerformChecks(
62
    dataSanitizer(
63
        [
64
            'type' => htmlspecialchars($request->request->get('type', ''), ENT_QUOTES, 'UTF-8'),
65
        ],
66
        [
67
            'type' => 'trim|escape',
68
        ],
69
    ),
70
    [
71
        'user_id' => returnIfSet($session->get('user-id'), null),
72
        'user_key' => returnIfSet($session->get('key'), null),
73
    ]
74
);
75
// Handle the case
76
echo $checkUserAccess->caseHandler();
77
if (
78
    ($checkUserAccess->userAccessPage('home') === false ||
79
    $checkUserAccess->checkSession() === false)
80
    && in_array(filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS), ['get_teampass_settings', 'ga_generate_qr']) === false
81
) {
82
    // Not allowed page
83
    $session->set('system-error_code', ERR_NOT_ALLOWED);
84
    include $SETTINGS['cpassman_dir'] . '/error.php';
85
    exit;
86
}
87
88
// Define Timezone
89
date_default_timezone_set($SETTINGS['timezone'] ?? 'UTC');
90
set_time_limit(600);
91
92
// DO CHECKS
93
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
94
if (
95
    isset($post_type) === true
96
    && ($post_type === 'ga_generate_qr'
97
        || $post_type === 'get_teampass_settings')
98
) {
99
    // continue
100
    mainQuery($SETTINGS);
101
} elseif (
102
    $session->has('user-id') && null !== $session->get('user-id')
103
    && $checkUserAccess->userAccessPage('home') === false
104
) {
105
    $session->set('system-error_code', ERR_NOT_ALLOWED); //not allowed page
106
    include __DIR__.'/../error.php';
107
    exit();
108
} elseif (($session->has('user-id') && null !== $session->get('user-id')
109
        && $session->get('key') !== null)
110
    || (isset($post_type) === true
111
        && null !== filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES))
112
) {
113
    // continue
114
    mainQuery($SETTINGS);
115
} else {
116
    $session->set('system-error_code', ERR_NOT_ALLOWED); //not allowed page
117
    include __DIR__.'/../error.php';
118
    exit();
119
}
120
121
// Includes
122
include_once __DIR__.'/../sources/main.functions.php';
123
124
/**
125
 * Undocumented function.
126
 */
127
function mainQuery(array $SETTINGS)
128
{
129
    header('Content-type: text/html; charset=utf-8');
130
    header('Cache-Control: no-cache');
131
    error_reporting(E_ERROR);
132
133
    // Load libraries
134
    loadClasses('DB');
135
136
    // User's language loading
137
    $session = SessionManager::getSession();
138
    $lang = new Language($session->get('user-language') ?? 'english');
139
    $request = SymfonyRequest::createFromGlobals();
140
141
    // Prepare POST variables
142
    $inputData = dataSanitizer(
143
        [
144
            'type' => $request->request->filter('type', '', FILTER_SANITIZE_SPECIAL_CHARS),
145
            'data' => $request->request->filter('data', '', FILTER_SANITIZE_SPECIAL_CHARS),
146
            'key' => $request->request->filter('key', '', FILTER_SANITIZE_SPECIAL_CHARS),
147
            'type_category' => $request->request->filter('type_category', '', FILTER_SANITIZE_SPECIAL_CHARS),
148
        ],
149
        [
150
            'type' => 'trim|escape',
151
            'data' => 'trim|escape',
152
            'key' => 'trim|escape',
153
            'type_category' => 'trim|escape',
154
        ]
155
    );
156
    
157
    // Check KEY
158
    if (isValueSetNullEmpty($inputData['key']) === true) {
159
        echo prepareExchangedData(
160
            array(
161
                'error' => true,
162
                'message' => $lang->get('key_is_not_correct'),
163
            ),
164
            'encode',
165
            $inputData['key']
166
        );
167
        return false;
168
    }
169
    // decrypt and retreive data in JSON format
170
    $dataReceived = empty($inputData['data']) === false ? prepareExchangedData(
171
        $inputData['data'],
172
        'decode'
173
    ) : '';
174
    
175
    switch ($inputData['type_category']) {
176
        case 'action_password':
177
            echo passwordHandler($inputData['type'], $dataReceived, $SETTINGS);
178
            break;
179
180
        case 'action_user':
181
            echo userHandler($inputData['type'], $dataReceived, $SETTINGS, $inputData['key']);
182
            break;
183
184
        case 'action_mail':
185
            echo mailHandler($inputData['type'], $dataReceived, $SETTINGS);
186
            break;
187
188
        case 'action_key':
189
            // deepcode ignore ServerLeak: All cases handled by keyHandler return an encrypted string that is sent back to the client
190
            echo keyHandler($inputData['type'], $dataReceived, $SETTINGS);
191
            break;
192
193
        case 'action_system':
194
            echo systemHandler($inputData['type'], $dataReceived, $SETTINGS);
195
            break;
196
197
        case 'action_utils':
198
            echo utilsHandler($inputData['type'], $dataReceived, $SETTINGS);
199
            break;
200
    }
201
    
202
}
203
204
/**
205
 * Handler for all password tasks
206
 *
207
 * @param string $post_type
208
 * @param array|null|string $dataReceived
209
 * @param array $SETTINGS
210
 * @return string
211
 */
212
function passwordHandler(string $post_type, /*php8 array|null|string*/ $dataReceived, array $SETTINGS): string
213
{
214
    $session = SessionManager::getSession();
215
    $lang = new Language($session->get('user-language') ?? 'english');
216
217
    switch ($post_type) {
218
        case 'change_pw'://action_password
219
            return changePassword(
220
                (string) filter_var($dataReceived['new_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
221
                isset($dataReceived['current_pw']) === true ? (string) filter_var($dataReceived['current_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '',
222
                (int) filter_var($dataReceived['complexity'], FILTER_SANITIZE_NUMBER_INT),
223
                (string) filter_var($dataReceived['change_request'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
224
                (int) $session->get('user-id'),
225
                $SETTINGS
226
            );
227
228
        /*
229
         * Change user's authentication password
230
         */
231
        case 'change_user_auth_password'://action_password
232
233
            // Check new password and confirm match server side
234
            if ($dataReceived['new_password'] !== $dataReceived['new_password_confirm']) {
235
                return prepareExchangedData(
236
                    array(
237
                        'error' => true,
238
                        'message' => $lang->get('error_bad_credentials'),
239
                    ),
240
                    'encode'
241
                );
242
            }
243
244
            // Check if new password is strong
245
            if (!isPasswordStrong($dataReceived['new_password'])) {
246
                return prepareExchangedData(
247
                    array(
248
                        'error' => true,
249
                        'message' => $lang->get('complexity_level_not_reached'),
250
                    ),
251
                    'encode'
252
                );
253
            }
254
255
            return changeUserAuthenticationPassword(
256
                (int) $session->get('user-id'),
257
                (string) filter_var($dataReceived['old_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
258
                (string) filter_var($dataReceived['new_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
259
                $SETTINGS
260
            );
261
262
        /*
263
         * User's authentication password in LDAP has changed
264
         */
265
        case 'change_user_ldap_auth_password'://action_password
266
267
            // Users passwords are html escaped
268
            $userPassword = filter_var($dataReceived['current_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
269
270
            // Get current user hash
271
            $userHash = DB::queryFirstRow(
272
                "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
273
                $session->get('user-id')
274
            )['pw'];
275
276
            $passwordManager = new PasswordManager();
277
278
            // Verify provided user password
279
            if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
280
                return prepareExchangedData(
281
                    array(
282
                        'error' => true,
283
                        'message' => $lang->get('error_bad_credentials'),
284
                    ),
285
                    'encode'
286
                );
287
            }
288
289
            return /** @scrutinizer ignore-call */ changeUserLDAPAuthenticationPassword(
290
                (int) $session->get('user-id'),
291
                filter_var($dataReceived['previous_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
292
                filter_var($userPassword)
293
            );
294
295
        /*
296
         * test_current_user_password_is_correct
297
         */
298
        case 'test_current_user_password_is_correct'://action_password
299
            return isUserPasswordCorrect(
300
                (int) $session->get('user-id'),
301
                (string) $dataReceived['password'],
302
                $SETTINGS
303
            );
304
305
        /*
306
         * Default case
307
         */
308
        default :
309
            return prepareExchangedData(
310
                array(
311
                    'error' => true,
312
                ),
313
                'encode'
314
            );
315
    }
316
}
317
318
/**
319
 * Handler for all user tasks
320
 *
321
 * @param string $post_type
322
 * @param array|null|string $dataReceived
323
 * @param array $SETTINGS
324
 * @param string $post_key
325
 * @return string
326
 */
327
function userHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS, string $post_key): string
328
{
329
    $session = SessionManager::getSession();
330
331
    // List of post types allowed to all users
332
    $all_users_can_access = [
333
        'get_user_info',
334
        'increase_session_time',
335
        'generate_password',
336
        'refresh_list_items_seen',
337
        'ga_generate_qr',
338
        'user_get_session_time',
339
        'save_user_location'
340
    ];
341
342
    // Default values
343
    $filtered_user_id = $session->get('user-id');
344
345
    // User can't manage users and requested type is administrative.
346
    if ((int) $session->get('user-admin') !== 1 &&
347
        (int) $session->get('user-manager') !== 1 &&
348
        (int) $session->get('user-can_manage_all_users') !== 1 &&
349
        !in_array($post_type, $all_users_can_access)) {
350
351
        return prepareExchangedData(
352
            array(
353
                'error' => true,
354
            ),
355
            'encode'
356
        );
357
    }
358
359
    if (isset($dataReceived['user_id'])) {
360
        // Get info about user to modify
361
        $targetUserInfos = DB::queryFirstRow(
362
            'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
363
            WHERE id = %i',
364
            $dataReceived['user_id']
365
        );
366
367
        if (
368
            // Administrator user
369
            (int) $session->get('user-admin') === 1
370
            // Manager of basic/ro users in this role
371
            || ((int) $session->get('user-manager') === 1
372
                && in_array($targetUserInfos['isAdministratedByRole'], $session->get('user-roles_array'))
373
                && (int) $targetUserInfos['admin'] !== 1
374
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
375
                && (int) $targetUserInfos['gestionnaire'] !== 1)
376
            // Manager of all basic/ro users
377
            || ((int) $session->get('user-can_manage_all_users') === 1
378
                && (int) $targetUserInfos['admin'] !== 1
379
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
380
                && (int) $targetUserInfos['gestionnaire'] !== 1)
381
        ) {
382
            // This user is allowed to modify other users.
383
            $filtered_user_id = $dataReceived['user_id'];
384
        }
385
    }
386
387
    switch ($post_type) {
388
        /*
389
        * Get info 
390
        */
391
        case 'get_user_info'://action_user
392
            return getUserInfo(
393
                (int) $filtered_user_id,
394
                $SETTINGS
395
            );
396
397
        /*
398
        * Increase the session time of User
399
        */
400
        case 'increase_session_time'://action_user
401
            return increaseSessionDuration(
402
                (int) filter_input(INPUT_POST, 'duration', FILTER_SANITIZE_NUMBER_INT)
403
            );
404
405
        /*
406
        * Generate a password generic
407
        */
408
        case 'generate_password'://action_user
409
            return generateGenericPassword(
410
                (int) filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT),
411
                (bool) filter_input(INPUT_POST, 'secure_pwd', FILTER_VALIDATE_BOOLEAN),
412
                (bool) filter_input(INPUT_POST, 'lowercase', FILTER_VALIDATE_BOOLEAN),
413
                (bool) filter_input(INPUT_POST, 'capitalize', FILTER_VALIDATE_BOOLEAN),
414
                (bool) filter_input(INPUT_POST, 'numerals', FILTER_VALIDATE_BOOLEAN),
415
                (bool) filter_input(INPUT_POST, 'symbols', FILTER_VALIDATE_BOOLEAN),
416
                $SETTINGS
417
            );
418
419
        /*
420
        * Refresh list of last items seen
421
        */
422
        case 'refresh_list_items_seen'://action_user
423
            if ($session->has('user-id') || (int) $session->get('user-id') && null !== $session->get('user-id') || (int) $session->get('user-id') > 0) {
424
                return refreshUserItemsSeenList(
425
                    $SETTINGS
426
                );
427
428
            } else {
429
                return json_encode(
430
                    array(
431
                        'error' => '',
432
                        'existing_suggestions' => 0,
433
                        'html_json' => '',
434
                    ),
435
                    JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
436
                );
437
            }
438
439
        /*
440
        * This will generate the QR Google Authenticator
441
        */
442
        case 'ga_generate_qr'://action_user
443
            return generateQRCode(
444
                (int) $filtered_user_id,
445
                (string) filter_var($dataReceived['demand_origin'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
446
                (string) filter_var($dataReceived['send_email'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
447
                (string) filter_var($dataReceived['login'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
448
                (string) filter_var($dataReceived['pwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
449
                (string) filter_var($dataReceived['token'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
450
                $SETTINGS
451
            );
452
453
        /*
454
        * This will set the user ready
455
        */
456
        case 'user_is_ready'://action_user
457
            return userIsReady(
458
                (int) $filtered_user_id,
459
                (string) $SETTINGS['cpassman_dir']
460
            );
461
462
        /*
463
        * This post type is used to check if the user session is still valid
464
        */
465
        case 'user_get_session_time'://action_user
466
            return userGetSessionTime(
467
                (int) $session->get('user-id'),
468
                (string) $SETTINGS['cpassman_dir'],
469
                (int) $SETTINGS['maximum_session_expiration_time'],
470
            );
471
472
        case 'save_user_location'://action_user
473
            return userSaveIp(
474
                (int) $session->get('user-id'),
475
                (string) filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
476
            );
477
478
        /*
479
        * Default case
480
        */
481
        default :
482
            return prepareExchangedData(
483
                array(
484
                    'error' => true,
485
                ),
486
                'encode'
487
            );
488
    }
489
}
490
491
/**
492
 * Handler for all mail tasks
493
 *
494
 * @param string $post_type
495
 * @param array|null|string $dataReceived
496
 * @param array $SETTINGS
497
 * @return string
498
 */
499
function mailHandler(string $post_type, /*php8 array|null|string */$dataReceived, array $SETTINGS): string
500
{
501
    $session = SessionManager::getSession();
502
503
    switch ($post_type) {
504
        /*
505
         * CASE
506
         * Send email
507
         */
508
        case 'mail_me'://action_mail
509
            // Get info about user to send email
510
            $data_user = DB::queryFirstRow(
511
                'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
512
                WHERE email = %s',
513
                filter_var($dataReceived['receipt'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
514
            );
515
516
            // Unknown email address
517
            if (!$data_user) {
518
                return prepareExchangedData(
519
                    array(
520
                        'error' => true,
521
                    ),
522
                    'encode'
523
                );
524
            }
525
526
            // Only administrators and managers can send mails
527
            if (
528
                // Administrator user
529
                (int) $session->get('user-admin') === 1
530
                // Manager of basic/ro users in this role
531
                || ((int) $session->get('user-manager') === 1
532
                    && in_array($data_user['isAdministratedByRole'], $session->get('user-roles_array'))
533
                    && (int) $data_user['admin'] !== 1
534
                    && (int) $data_user['can_manage_all_users'] !== 1
535
                    && (int) $data_user['gestionnaire'] !== 1)
536
                // Manager of all basic/ro users
537
                || ((int) $session->get('user-can_manage_all_users') === 1
538
                    && (int) $data_user['admin'] !== 1
539
                    && (int) $data_user['can_manage_all_users'] !== 1
540
                    && (int) $data_user['gestionnaire'] !== 1)
541
            ) {
542
                return sendMailToUser(
543
                    filter_var($dataReceived['receipt'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
544
                    $dataReceived['body'],
545
                    (string) filter_var($dataReceived['subject'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
546
                    (array) filter_var_array(
547
                        $dataReceived['pre_replace'],
548
                        FILTER_SANITIZE_FULL_SPECIAL_CHARS
549
                    ),
550
                    true
551
                );
552
            }
553
554
            return prepareExchangedData(
555
                array(
556
                    'error' => true,
557
                ),
558
                'encode'
559
            );
560
        /*
561
        * Send emails not sent
562
        */
563
        case 'send_waiting_emails'://mail
564
            // Administrative task
565
            if ((int) $session->get('user-admin') !== 1) {
566
                return prepareExchangedData(
567
                    array(
568
                        'error' => true,
569
                    ),
570
                    'encode'
571
                );
572
            }
573
574
            sendEmailsNotSent(
575
                $SETTINGS
576
            );
577
            return prepareExchangedData(
578
                array(
579
                    'error' => false,
580
                    'message' => 'mail_sent',
581
                ),
582
                'encode'
583
            );
584
585
        /*
586
        * Default case
587
        */
588
        default :
589
            return prepareExchangedData(
590
                array(
591
                    'error' => true,
592
                ),
593
                'encode'
594
            );
595
    }
596
}
597
598
/**
599
 * Handler for all key related tasks
600
 *
601
 * @param string $post_type
602
 * @param array|null|string $dataReceived
603
 * @param array $SETTINGS
604
 * @return string
605
 */
606
function keyHandler(string $post_type, /*php8 array|null|string */$dataReceived, array $SETTINGS): string
607
{
608
    $session = SessionManager::getSession();
609
    $lang = new Language($session->get('user-language') ?? 'english');
610
611
    // List of post types allowed to all users
612
    $all_users_can_access = [
613
        'change_private_key_encryption_password',
614
        'user_new_keys_generation',
615
        'user_recovery_keys_download',
616
    ];
617
618
    // Default values
619
    $filtered_user_id = $session->get('user-id');
620
621
    if (isset($dataReceived['user_id'])) {
622
        // Get info about user to modify
623
        $targetUserInfos = DB::queryFirstRow(
624
            'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
625
            WHERE id = %i',
626
            $dataReceived['user_id']
627
        );
628
    
629
        if (
630
            // Administrator user
631
            (int) $session->get('user-admin') === 1
632
            // Manager of basic/ro users in this role
633
            || ((int) $session->get('user-manager') === 1
634
                && in_array($targetUserInfos['isAdministratedByRole'], $session->get('user-roles_array'))
635
                && (int) $targetUserInfos['admin'] !== 1
636
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
637
                && (int) $targetUserInfos['gestionnaire'] !== 1)
638
            // Manager of all basic/ro users
639
            || ((int) $session->get('user-can_manage_all_users') === 1
640
                && (int) $targetUserInfos['admin'] !== 1
641
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
642
                && (int) $targetUserInfos['gestionnaire'] !== 1)
643
        ) {
644
            // This user is allowed to modify other users.
645
            $filtered_user_id = $dataReceived['user_id'];
646
    
647
        } else if (!in_array($post_type, $all_users_can_access)) {
648
            // User can't manage users and requested type is administrative.
649
            return prepareExchangedData(
650
                array(
651
                    'error' => true,
652
                ),
653
                'encode'
654
            ); 
655
        }
656
    }
657
658
    switch ($post_type) {
659
        /*
660
         * Generate a temporary encryption key for user
661
         */
662
        case 'generate_temporary_encryption_key'://action_key
663
            return generateOneTimeCode(
664
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT)
665
            );
666
667
        /*
668
         * user_sharekeys_reencryption_next
669
         */
670
        case 'user_sharekeys_reencryption_next'://action_key
671
            return continueReEncryptingUserSharekeys(
672
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
673
                (bool) filter_var($dataReceived['self_change'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
674
                (string) filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
675
                (int) filter_var($dataReceived['start'], FILTER_SANITIZE_NUMBER_INT),
676
                (int) filter_var($dataReceived['length'], FILTER_SANITIZE_NUMBER_INT),
677
                $SETTINGS
678
            );
679
680
        /*
681
         * user_psk_reencryption
682
         */
683
        case 'user_psk_reencryption'://action_key
684
            return migrateTo3_DoUserPersonalItemsEncryption(
685
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
686
                (int) filter_var($dataReceived['start'], FILTER_SANITIZE_NUMBER_INT),
687
                (int) filter_var($dataReceived['length'], FILTER_SANITIZE_NUMBER_INT),
688
                (int) filter_var($dataReceived['counterItemsToTreat'], FILTER_SANITIZE_NUMBER_INT),
689
                (string) filter_var($dataReceived['userPsk'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
690
                $SETTINGS
691
            );
692
693
        /*
694
         * User's public/private keys change
695
         */
696
        case 'change_private_key_encryption_password'://action_key
697
698
            // Users passwords are html escaped
699
            $newPassword = filter_var($dataReceived['new_code'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
700
701
            // Get current user hash
702
            $userHash = DB::queryFirstRow(
703
                "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
704
                $session->get('user-id')
705
            )['pw'];
706
707
            $passwordManager = new PasswordManager();
708
709
            // Verify provided user password
710
            if (!$passwordManager->verifyPassword($userHash, $newPassword)) {
711
                return prepareExchangedData(
712
                    array(
713
                        'error' => true,
714
                        'message' => $lang->get('error_bad_credentials'),
715
                    ),
716
                    'encode'
717
                );
718
            }
719
720
            return changePrivateKeyEncryptionPassword(
721
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
722
                (string) $dataReceived['current_code'],
723
                (string) $newPassword,
724
                (string) filter_var($dataReceived['action_type'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
725
                $SETTINGS
726
            );
727
728
        /*
729
         * Launch user keys change on his demand
730
         */
731
        case 'user_new_keys_generation'://action_key
732
733
            // Users passwords are html escaped
734
            $userPassword = filter_var($dataReceived['user_pwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
735
736
            // Don't generate new user password -> verify it
737
            if ($dataReceived['generate_user_new_password'] !== true) {
738
739
                // Get current user hash
740
                $userHash = DB::queryFirstRow(
741
                    "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
742
                    $session->get('user-id')
743
                )['pw'];
744
745
                $passwordManager = new PasswordManager();
746
747
                // Verify provided user password
748
                if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
749
                    return prepareExchangedData(
750
                        array(
751
                            'error' => true,
752
                            'message' => $lang->get('error_bad_credentials'),
753
                        ),
754
                        'encode'
755
                    );
756
                }
757
            }
758
759
            return handleUserKeys(
760
                (int) filter_var($filtered_user_id, FILTER_SANITIZE_NUMBER_INT),
761
                (string) $userPassword,
762
                (int) isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : NUMBER_ITEMS_IN_BATCH,
763
                (string) filter_var($dataReceived['encryption_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
764
                (bool) filter_var($dataReceived['delete_existing_keys'], FILTER_VALIDATE_BOOLEAN),
765
                (bool) filter_var($dataReceived['send_email_to_user'], FILTER_VALIDATE_BOOLEAN),
766
                (bool) filter_var($dataReceived['encrypt_with_user_pwd'], FILTER_VALIDATE_BOOLEAN),
767
                (bool) isset($dataReceived['generate_user_new_password']) === true ? filter_var($dataReceived['generate_user_new_password'], FILTER_VALIDATE_BOOLEAN) : false,
768
                (string) filter_var($dataReceived['email_body'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
769
                (bool) filter_var($dataReceived['user_self_change'], FILTER_VALIDATE_BOOLEAN),
770
                (string) filter_var($dataReceived['recovery_public_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
771
                (string) filter_var($dataReceived['recovery_private_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
772
            );
773
774
        /*
775
         * Launch user recovery download
776
         */
777
        case 'user_recovery_keys_download'://action_key
778
            // Validate user password on local and LDAP accounts before download
779
            if ($session->get('user-auth_type') !== 'oauth2') {
780
                // Users passwords are html escaped
781
                $userPassword = filter_var($dataReceived['password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
782
783
                // Get current user hash
784
                $userHash = DB::queryFirstRow(
785
                    "SELECT pw FROM " . prefixtable('users') . " WHERE id = %i;",
786
                    $session->get('user-id')
787
                )['pw'];
788
789
                $passwordManager = new PasswordManager();
790
791
                // Verify provided user password
792
                if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
793
                    return prepareExchangedData(
794
                        array(
795
                            'error' => true,
796
                            'message' => $lang->get('error_bad_credentials'),
797
                        ),
798
                        'encode'
799
                    );
800
                }
801
            }
802
803
            return handleUserRecoveryKeysDownload(
804
                (int) $filtered_user_id,
805
                (array) $SETTINGS,
806
            );
807
808
        /*
809
         * Default case
810
         */
811
        default :
812
            return prepareExchangedData(
813
                array(
814
                    'error' => true,
815
                ),
816
                'encode'
817
            );
818
    }
819
}
820
821
/**
822
 * Handler for all system tasks
823
 *
824
 * @param string $post_type Type of action to perform
825
 * @param mixed $dataReceived Data received for processing
826
 * @param array $SETTINGS Application settings
827
 * @return string JSON-encoded response
828
 */
829
function systemHandler(string $post_type, $dataReceived, array $SETTINGS): string
830
{
831
    try {
832
        $session = SessionManager::getSession();
833
834
        switch ($post_type) {
835
            case 'get_number_of_items_to_treat':
836
                return handleGetNumberOfItems($dataReceived, $SETTINGS);
837
            case 'sending_statistics':
838
                return handleSendingStatistics($SETTINGS);
839
            case 'generate_bug_report':
840
                return handleGenerateBugReport($dataReceived, $SETTINGS, $session);
841
            case 'get_teampass_settings':
842
                return handleGetTeampassSettings($SETTINGS);
843
            case 'save_token':
844
                return handleSaveToken($session);
845
            default:
846
                return prepareErrorResponse('Unknown action type');
847
        }
848
    } catch (Exception $e) {
849
        return prepareErrorResponse($e->getMessage());
850
    }
851
}
852
853
/**
854
 * Handles the 'get_number_of_items_to_treat' action
855
 */
856
function handleGetNumberOfItems($dataReceived, array $SETTINGS): string
857
{
858
    $userId = getValidatedInput($dataReceived, 'user_id', FILTER_SANITIZE_NUMBER_INT);
859
    if ($userId === false) {
860
        return prepareErrorResponse('Invalid User ID');
861
    }
862
863
    return prepareSuccessResponse([
864
        'count' => getNumberOfItemsToTreat((int)$userId, $SETTINGS)
865
    ]);
866
}
867
868
/**
869
 * Handles the 'sending_statistics' action
870
 */
871
function handleSendingStatistics(array $SETTINGS): string
872
{
873
    sendingStatistics($SETTINGS);
874
    return prepareSuccessResponse();
875
}
876
877
/**
878
 * Handles the 'generate_bug_report' action
879
 */
880
function handleGenerateBugReport($dataReceived, array $SETTINGS, $session): string
881
{
882
    if ((int) $session->get('user-admin') !== 1) {
883
        return prepareSuccessResponse([]); // Return empty success for non-admins
884
    }
885
886
    if (!is_array($dataReceived)) {
887
        return prepareErrorResponse('Invalid data format');
888
    }
889
890
    $reportContent = generateBugReport($dataReceived, $SETTINGS);
891
    if ($reportContent['error'] === true) {
0 ignored issues
show
introduced by
The condition $reportContent['error'] === true is always false.
Loading history...
892
        return prepareErrorResponse('Failed to generate bug report');
893
    }
894
895
    return prepareSuccessResponse([
896
        'report' => $reportContent['report']
897
    ]);
898
}
899
900
/**
901
 * Handles the 'get_teampass_settings' action
902
 */
903
function handleGetTeampassSettings(array $SETTINGS): string
904
{
905
    $allowedSettings = [
906
        'ldap_user_attribute', 'enable_pf_feature', 'clipboard_life_duration',
907
        'enable_favourites', 'copy_to_clipboard_small_icons', 'enable_attachment_encryption',
908
        'google_authentication', 'agses_authentication_enabled', 'yubico_authentication',
909
        'duo', 'personal_saltkey_security_level', 'enable_tasks_manager',
910
        'insert_manual_entry_item_history', 'show_item_data'
911
    ];
912
913
    return prepareSuccessResponse([
914
        'settings' => array_intersect_key($SETTINGS, array_flip($allowedSettings))
915
    ]);
916
}
917
918
/**
919
 * Handles the 'save_token' action
920
 */
921
function handleSaveToken($session): string
922
{
923
    $tokenParams = [
924
        'size' => FILTER_SANITIZE_NUMBER_INT,
925
        'secure' => FILTER_VALIDATE_BOOLEAN,
926
        'numeric' => FILTER_VALIDATE_BOOLEAN,
927
        'capital' => FILTER_VALIDATE_BOOLEAN,
928
        'symbols' => FILTER_VALIDATE_BOOLEAN,
929
        'lowercase' => FILTER_VALIDATE_BOOLEAN,
930
        'duration' => FILTER_SANITIZE_NUMBER_INT,
931
        'reason' => FILTER_SANITIZE_FULL_SPECIAL_CHARS
932
    ];
933
934
    $params = [];
935
    foreach ($tokenParams as $name => $filter) {
936
        $params[$name] = getPostInput($name, $filter);
937
    }
938
939
    $defaults = [
940
        'size' => 20,
941
        'secure' => false,
942
        'numeric' => false,
943
        'capital' => false,
944
        'symbols' => false,
945
        'lowercase' => false,
946
        'duration' => 3600,
947
        'reason' => ''
948
    ];
949
950
    // Apply defaults where needed
951
    foreach ($defaults as $key => $value) {
952
        if ($params[$key] === null) {
953
            $params[$key] = $value;
954
        }
955
    }
956
957
    $token = GenerateCryptKey(
958
        (int)$params['size'],
959
        (bool)$params['secure'],
960
        (bool)$params['numeric'],
961
        (bool)$params['capital'],
962
        (bool)$params['symbols'],
963
        (bool)$params['lowercase']
964
    );
965
966
    DB::insert(prefixTable('tokens'), [
967
        'user_id' => (int) $session->get('user-id'),
968
        'token' => $token,
969
        'reason' => $params['reason'],
970
        'creation_timestamp' => time(),
971
        'end_timestamp' => time() + (int)$params['duration'],
972
    ]);
973
974
    return prepareSuccessResponse(['token' => $token]);
975
}
976
977
/**
978
 * Helper function to get and validate input
979
 */
980
function getValidatedInput(array $data, string $key, int $filter)
981
{
982
    if (!isset($data[$key])) {
983
        return false;
984
    }
985
    return filter_var($data[$key], $filter);
986
}
987
988
/**
989
 * Helper function to get POST input with default value
990
 */
991
function getPostInput(string $name, int $filter)
992
{
993
    return filter_input(INPUT_POST, $name, $filter);
994
}
995
996
/**
997
 * Prepares a success response
998
 */
999
function prepareSuccessResponse(array $data = []): string
1000
{
1001
    $response = array_merge(['error' => false], $data);
1002
    return prepareExchangedData($response, 'encode');
1003
}
1004
1005
/**
1006
 * Prepares an error response
1007
 */
1008
function prepareErrorResponse(string $message): string
1009
{
1010
    return prepareExchangedData([
1011
        'error' => true,
1012
        'message' => $message
1013
    ], 'encode');
1014
}
1015
1016
1017
1018
function utilsHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS): string
1019
{
1020
    switch ($post_type) {
1021
        /*
1022
         * generate_an_otp
1023
         */
1024
        case 'generate_an_otp'://action_utils
1025
            return generateAnOTP(
1026
                (string) filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
1027
                (bool) filter_var($dataReceived['with_qrcode'], FILTER_VALIDATE_BOOLEAN),
1028
                (string) filter_var($dataReceived['secret_key'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
1029
            );
1030
1031
1032
        /*
1033
         * Default case
1034
         */
1035
        default :
1036
            return prepareExchangedData(
1037
                array(
1038
                    'error' => true,
1039
                ),
1040
                'encode'
1041
            );
1042
    }
1043
}
1044
1045
/**
1046
 * Permits to set the user ready
1047
 *
1048
 * @param integer $userid
1049
 * @param string $dir
1050
 * @return string
1051
 */
1052
function userIsReady(int $userid, string $dir): string
1053
{
1054
    DB::update(
1055
        prefixTable('users'),
1056
        array(
1057
            'is_ready_for_usage' => 1,
1058
        ),
1059
        'id = %i',
1060
        $userid
1061
    );
1062
1063
    // Send back
1064
    return prepareExchangedData(
1065
        array(
1066
            'error' => false,
1067
        ),
1068
        'encode'
1069
    ); 
1070
}
1071
1072
1073
/**
1074
 * Permits to set the user ready
1075
 *
1076
 * @param integer $userid
1077
 * @return string
1078
 */
1079
function userGetSessionTime(int $userid, string $dir, int $maximum_session_expiration_time): string
1080
{
1081
    $session = SessionManager::getSession();
1082
    // Send back
1083
    return prepareExchangedData(
1084
        array(
1085
            'error' => false,
1086
            'timestamp' => $session->get('user-session_duration'),
1087
            'max_time_to_add' => intdiv((($maximum_session_expiration_time*60) - ((int) $session->get('user-session_duration') - time())), 60),
1088
            'max_session_duration' => $maximum_session_expiration_time,
1089
        ),
1090
        'encode'
1091
    ); 
1092
}
1093
1094
/**
1095
 * Save the user's IP
1096
 *
1097
 * @param integer $userID
1098
 * @param string $action
1099
 * @return string
1100
 */
1101
function userSaveIp(int $userID, string $action): string
1102
{
1103
    if ($action === 'perform') {
1104
        DB::update(
1105
            prefixTable('users'),
1106
            array(
1107
                'user_ip' => getClientIpServer(),
1108
                'user_ip_lastdate' => time(),
1109
            ),
1110
            'id = %i',
1111
            $userID
1112
        );
1113
    }
1114
1115
    return prepareExchangedData(
1116
        array(
1117
            'error' => false,
1118
        ),
1119
        'encode'
1120
    );
1121
}
1122
1123
/**
1124
 * Provides the number of items
1125
 *
1126
 * @param int   $userId     User ID
1127
 * @param array $SETTINGS   TeampassSettings
1128
 *
1129
 * @return string
1130
 */
1131
function getNumberOfItemsToTreat(
1132
    int $userId,
1133
    array $SETTINGS
1134
): string
1135
{
1136
    // get number of items
1137
    DB::queryFirstRow(
1138
        'SELECT increment_id
1139
        FROM ' . prefixTable('sharekeys_items') .
1140
        ' WHERE user_id = %i',
1141
        $userId
1142
    );
1143
1144
    // Send back
1145
    return prepareExchangedData(
1146
        array(
1147
            'error' => false,
1148
            'nbItems' => DB::count(),
1149
        ),
1150
        'encode'
1151
    );
1152
}
1153
1154
1155
/**
1156
 * 
1157
 */
1158
function changePassword(
1159
    string $post_new_password,
1160
    string $post_current_password,
1161
    int $post_password_complexity,
1162
    string $post_change_request,
1163
    int $post_user_id,
1164
    array $SETTINGS
1165
): string
1166
{
1167
    $session = SessionManager::getSession();
1168
    
1169
    // Create password hash
1170
    $passwordManager = new PasswordManager();
1171
    $post_new_password_hashed = $passwordManager->hashPassword($post_new_password);
1172
1173
    // Load user's language
1174
    $lang = new Language($session->get('user-language') ?? 'english');
1175
1176
    // User has decided to change is PW
1177
    if ($post_change_request === 'reset_user_password_expected'
1178
        || $post_change_request === 'user_decides_to_change_password'
1179
    ) {
1180
        // Check that current user is correct
1181
        if ((int) $post_user_id !== (int) $session->get('user-id')) {
1182
            return prepareExchangedData(
1183
                array(
1184
                    'error' => true,
1185
                    'message' => $lang->get('error_not_allowed_to'),
1186
                ),
1187
                'encode'
1188
            );
1189
        }
1190
1191
        // check if expected security level is reached
1192
        $dataUser = DB::queryFirstRow(
1193
            'SELECT *
1194
            FROM ' . prefixTable('users') . ' WHERE id = %i',
1195
            $post_user_id
1196
        );
1197
1198
        // check if badly written
1199
        $dataUser['fonction_id'] = array_filter(
1200
            explode(',', str_replace(';', ',', $dataUser['fonction_id']))
1201
        );
1202
        $dataUser['fonction_id'] = implode(',', $dataUser['fonction_id']);
1203
        DB::update(
1204
            prefixTable('users'),
1205
            array(
1206
                'fonction_id' => $dataUser['fonction_id'],
1207
            ),
1208
            'id = %i',
1209
            $post_user_id
1210
        );
1211
1212
        if (empty($dataUser['fonction_id']) === false) {
1213
            $data = DB::queryFirstRow(
1214
                'SELECT complexity
1215
                FROM ' . prefixTable('roles_title') . '
1216
                WHERE id IN (' . $dataUser['fonction_id'] . ')
1217
                ORDER BY complexity DESC'
1218
            );
1219
        } else {
1220
            // In case user has no roles yet
1221
            $data = array();
1222
            $data['complexity'] = 0;
1223
        }
1224
1225
        if ((int) $post_password_complexity < (int) $data['complexity']) {
1226
            return prepareExchangedData(
1227
                array(
1228
                    'error' => true,
1229
                    'message' => '<div style="margin:10px 0 10px 15px;">' . $lang->get('complexity_level_not_reached') . '.<br>' .
1230
                        $lang->get('expected_complexity_level') . ': <b>' . TP_PW_COMPLEXITY[$data['complexity']][1] . '</b></div>',
1231
                ),
1232
                'encode'
1233
            );
1234
        }
1235
1236
        // Check that the 2 passwords are differents
1237
        if ($post_current_password === $post_new_password) {
1238
            return prepareExchangedData(
1239
                array(
1240
                    'error' => true,
1241
                    'message' => $lang->get('password_already_used'),
1242
                ),
1243
                'encode'
1244
            );
1245
        }
1246
1247
        // update sessions
1248
        $session->set('user-last_pw_change', mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')));
1249
        $session->set('user-validite_pw', 1);
1250
1251
        // BEfore updating, check that the pwd is correct
1252
        if ($passwordManager->verifyPassword($post_new_password_hashed, $post_new_password) === true && empty($dataUser['private_key']) === false) {
1253
            $special_action = 'none';
1254
            if ($post_change_request === 'reset_user_password_expected') {
1255
                $session->set('user-private_key', decryptPrivateKey($post_current_password, $dataUser['private_key']));
1256
            }
1257
1258
            // update DB
1259
            DB::update(
1260
                prefixTable('users'),
1261
                array(
1262
                    'pw' => $post_new_password_hashed,
1263
                    'last_pw_change' => mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y')),
1264
                    'last_pw' => $post_current_password,
1265
                    'special' => $special_action,
1266
                    'private_key' => encryptPrivateKey($post_new_password, $session->get('user-private_key')),
1267
                ),
1268
                'id = %i',
1269
                $post_user_id
1270
            );
1271
            // update LOG
1272
            logEvents($SETTINGS, 'user_mngt', 'at_user_pwd_changed', (string) $session->get('user-id'), $session->get('user-login'), $post_user_id);
1273
1274
            // Send back
1275
            return prepareExchangedData(
1276
                array(
1277
                    'error' => false,
1278
                    'message' => '',
1279
                ),
1280
                'encode'
1281
            );
1282
        }
1283
        // Send back
1284
        return prepareExchangedData(
1285
            array(
1286
                'error' => true,
1287
                'message' => $lang->get('pw_hash_not_correct'),
1288
            ),
1289
            'encode'
1290
        );
1291
    }
1292
    return prepareExchangedData(
1293
        array(
1294
            'error' => true,
1295
            'message' => $lang->get('error_not_allowed_to'),
1296
        ),
1297
        'encode'
1298
    );
1299
}
1300
1301
function generateQRCode(
1302
    $post_id,
1303
    $post_demand_origin,
1304
    $post_send_mail,
1305
    $post_login,
1306
    $post_pwd,
1307
    $post_token,
1308
    array $SETTINGS
1309
): string
1310
{
1311
    // Load user's language
1312
    $session = SessionManager::getSession();
1313
    $lang = new Language($session->get('user-language') ?? 'english');
1314
1315
    // is this allowed by setting
1316
    if (isKeyExistingAndEqual('ga_reset_by_user', 0, $SETTINGS) === true
1317
        && (null === $post_demand_origin || $post_demand_origin !== 'users_management_list')
1318
    ) {
1319
        // User cannot ask for a new code
1320
        return prepareExchangedData(
1321
            array(
1322
                'error' => true,
1323
                'message' => "113 ".$lang->get('error_not_allowed_to')." - ".isKeyExistingAndEqual('ga_reset_by_user', 1, $SETTINGS),
1324
            ),
1325
            'encode'
1326
        );
1327
    }
1328
    
1329
    // Check if user exists
1330
    if (isValueSetNullEmpty($post_id) === true) {
1331
        // Get data about user
1332
        $dataUser = DB::queryFirstRow(
1333
            'SELECT id, email, pw
1334
            FROM ' . prefixTable('users') . '
1335
            WHERE login = %s',
1336
            $post_login
1337
        );
1338
    } else {
1339
        $dataUser = DB::queryFirstRow(
1340
            'SELECT id, login, email, pw
1341
            FROM ' . prefixTable('users') . '
1342
            WHERE id = %i',
1343
            $post_id
1344
        );
1345
        $post_login = $dataUser['login'];
1346
    }
1347
    // Get number of returned users
1348
    $counter = DB::count();
1349
1350
    // Do treatment
1351
    if ($counter === 0) {
1352
        // Not a registered user !
1353
        logEvents($SETTINGS, 'failed_auth', 'user_not_exists', '', stripslashes($post_login), stripslashes($post_login));
1354
        return prepareExchangedData(
1355
            array(
1356
                'error' => true,
1357
                'message' => $lang->get('no_user'),
1358
                'tst' => 1,
1359
            ),
1360
            'encode'
1361
        );
1362
    }
1363
1364
    $passwordManager = new PasswordManager();
1365
    if (
1366
        isSetArrayOfValues([$post_pwd, $dataUser['pw']]) === true
1367
        && $passwordManager->verifyPassword($dataUser['pw'], $post_pwd) === false
1368
        && $post_demand_origin !== 'users_management_list'
1369
    ) {
1370
        // checked the given password
1371
        logEvents($SETTINGS, 'failed_auth', 'password_is_not_correct', '', stripslashes($post_login), stripslashes($post_login));
1372
        return prepareExchangedData(
1373
            array(
1374
                'error' => true,
1375
                'message' => $lang->get('no_user'),
1376
                'tst' => $post_demand_origin,
1377
            ),
1378
            'encode'
1379
        );
1380
    }
1381
    
1382
    if (empty($dataUser['email']) === true) {
1383
        return prepareExchangedData(
1384
            array(
1385
                'error' => true,
1386
                'message' => $lang->get('no_email_set'),
1387
            ),
1388
            'encode'
1389
        );
1390
    }
1391
1392
    // Check if token already used
1393
    $dataToken = DB::queryFirstRow(
1394
        'SELECT end_timestamp, reason
1395
        FROM ' . prefixTable('tokens') . '
1396
        WHERE token = %s AND user_id = %i',
1397
        $post_token,
1398
        $dataUser['id']
1399
    );
1400
    $tokenId = '';
1401
    if (DB::count() > 0 && is_null($dataToken['end_timestamp']) === false && $dataToken['reason'] === 'auth_qr_code') {
1402
        // This token has already been used
1403
        return prepareExchangedData(
1404
            array(
1405
                'error' => true,
1406
                'message' => 'TOKEN already used',//$lang->get('no_email_set'),
1407
            ),
1408
            'encode'
1409
        );
1410
    } elseif(DB::count() === 0) {
1411
        // Store token for this action
1412
        DB::insert(
1413
            prefixTable('tokens'),
1414
            array(
1415
                'user_id' => (int) $dataUser['id'],
1416
                'token' => $post_token,
1417
                'reason' => 'auth_qr_code',
1418
                'creation_timestamp' => time(),
1419
            )
1420
        );
1421
        $tokenId = DB::insertId();
1422
    }
1423
    
1424
    // generate new GA user code
1425
    $tfa = new TwoFactorAuth($SETTINGS['ga_website_name']);
1426
    $gaSecretKey = $tfa->createSecret();
1427
    $gaTemporaryCode = GenerateCryptKey(12, false, true, true, false, true);
1428
1429
    DB::update(
1430
        prefixTable('users'),
1431
        [
1432
            'ga' => $gaSecretKey,
1433
            'ga_temporary_code' => $gaTemporaryCode,
1434
        ],
1435
        'id = %i',
1436
        $dataUser['id']
1437
    );
1438
1439
    // Log event
1440
    logEvents($SETTINGS, 'user_connection', 'at_2fa_google_code_send_by_email', (string) $dataUser['id'], stripslashes($post_login), stripslashes($post_login));
1441
1442
    // Update token status
1443
    DB::update(
1444
        prefixTable('tokens'),
1445
        [
1446
            'end_timestamp' => time(),
1447
        ],
1448
        'id = %i',
1449
        $tokenId
1450
    );
1451
1452
    // send mail?
1453
    if ((int) $post_send_mail === 1) {
1454
        prepareSendingEmail(
1455
            $lang->get('email_ga_subject'),
1456
            str_replace(
1457
                '#2FACode#',
1458
                $gaTemporaryCode,
1459
                $lang->get('email_ga_text')
1460
            ),
1461
            $dataUser['email']
1462
        );
1463
1464
        // send back
1465
        return prepareExchangedData(
1466
            array(
1467
                'error' => false,
1468
                'message' => $post_send_mail,
1469
                'email' => $dataUser['email'],
1470
                'email_result' => str_replace(
1471
                    '#email#',
1472
                    '<b>' . obfuscateEmail($dataUser['email']) . '</b>',
1473
                    addslashes($lang->get('admin_email_result_ok'))
1474
                ),
1475
            ),
1476
            'encode'
1477
        );
1478
    }
1479
    
1480
    // send back
1481
    return prepareExchangedData(
1482
        array(
1483
            'error' => false,
1484
            'message' => '',
1485
            'email' => $dataUser['email'],
1486
            'email_result' => str_replace(
1487
                '#email#',
1488
                '<b>' . obfuscateEmail($dataUser['email']) . '</b>',
1489
                addslashes($lang->get('admin_email_result_ok'))
1490
            ),
1491
        ),
1492
        'encode'
1493
    );
1494
}
1495
1496
function sendEmailsNotSent(
1497
    array $SETTINGS
1498
)
1499
{
1500
    $emailSettings = new EmailSettings($SETTINGS);
1501
    $emailService = new EmailService();
1502
1503
    if (isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true) {
1504
        $row = DB::queryFirstRow(
1505
            'SELECT valeur FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s',
1506
            'cron',
1507
            'sending_emails'
1508
        );
1509
1510
        if ((int) (time() - $row['valeur']) >= 300 || (int) $row['valeur'] === 0) {
1511
            $rows = DB::query(
1512
                'SELECT *
1513
                FROM ' . prefixTable('emails') .
1514
                ' WHERE status != %s',
1515
                'sent'
1516
            );
1517
            foreach ($rows as $record) {
1518
                // Send email
1519
                $ret = json_decode(
1520
                    $emailService->sendMail(
1521
                        $record['subject'],
1522
                        $record['body'],
1523
                        $record['receivers'],
1524
                        $emailSettings
1525
                    ),
1526
                    true
1527
                );
1528
1529
                // update item_id in files table
1530
                DB::update(
1531
                    prefixTable('emails'),
1532
                    array(
1533
                        'status' => $ret['error'] === 'error_mail_not_send' ? 'not_sent' : 'sent',
1534
                    ),
1535
                    'timestamp = %s',
1536
                    $record['timestamp']
1537
                );
1538
            }
1539
        }
1540
        // update cron time
1541
        DB::update(
1542
            prefixTable('misc'),
1543
            array(
1544
                'valeur' => time(),
1545
                'updated_at' => time(),
1546
            ),
1547
            'intitule = %s AND type = %s',
1548
            'sending_emails',
1549
            'cron'
1550
        );
1551
    }
1552
}
1553
1554
1555
function refreshUserItemsSeenList(
1556
    array $SETTINGS
1557
): string
1558
{
1559
    $session = SessionManager::getSession();
1560
1561
    // get list of last items seen
1562
    $arr_html = array();
1563
    $rows = DB::query(
1564
        '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
1565
        FROM ' . prefixTable('log_items') . ' AS l
1566
        RIGHT JOIN ' . prefixTable('items') . ' AS i ON (l.id_item = i.id)
1567
        WHERE l.action = %s AND l.id_user = %i
1568
        ORDER BY l.date DESC
1569
        LIMIT 0, 100',
1570
        'at_shown',
1571
        $session->get('user-id')
1572
    );
1573
    if (DB::count() > 0) {
1574
        foreach ($rows as $record) {
1575
            if (in_array($record['id']->id, array_column($arr_html, 'id')) === false) {
1576
                array_push(
1577
                    $arr_html,
1578
                    array(
1579
                        'id' => $record['id'],
1580
                        'label' => htmlspecialchars(stripslashes(htmlspecialchars_decode($record['label'], ENT_QUOTES)), ENT_QUOTES),
1581
                        'tree_id' => $record['id_tree'],
1582
                        'perso' => $record['perso'],
1583
                        'restricted' => $record['restricted'],
1584
                    )
1585
                );
1586
                if (count($arr_html) >= (int) $SETTINGS['max_latest_items']) {
1587
                    break;
1588
                }
1589
            }
1590
        }
1591
    }
1592
1593
    // get wainting suggestions
1594
    $nb_suggestions_waiting = 0;
1595
    if (isKeyExistingAndEqual('enable_suggestion', 1, $SETTINGS) === true
1596
        && ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1)
1597
    ) {
1598
        DB::query('SELECT * FROM ' . prefixTable('suggestion'));
1599
        $nb_suggestions_waiting = DB::count();
1600
    }
1601
1602
    return json_encode(
1603
        array(
1604
            'error' => '',
1605
            'existing_suggestions' => $nb_suggestions_waiting,
1606
            'html_json' => $arr_html,
1607
        ),
1608
        JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1609
    );
1610
}
1611
1612
function sendingStatistics(
1613
    array $SETTINGS
1614
): void
1615
{
1616
    $session = SessionManager::getSession();
1617
    if (
1618
        isSetArrayOfValues([$SETTINGS['send_statistics_items'], $SETTINGS['send_stats_time']]) === true
1619
        && isKeyExistingAndEqual('send_stats', 1, $SETTINGS) === true
1620
        && (int) ($SETTINGS['send_stats_time'] + TP_ONE_DAY_SECONDS) > time()
1621
    ) {
1622
        // get statistics data
1623
        $stats_data = getStatisticsData($SETTINGS);
1624
1625
        // get statistics items to share
1626
        $statsToSend = [];
1627
        $statsToSend['ip'] = $_SERVER['SERVER_ADDR'];
1628
        $statsToSend['timestamp'] = time();
1629
        foreach (array_filter(explode(';', $SETTINGS['send_statistics_items'])) as $data) {
1630
            if ($data === 'stat_languages') {
1631
                $tmp = '';
1632
                foreach ($stats_data[$data] as $key => $value) {
1633
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1634
                }
1635
                $statsToSend[$data] = $tmp;
1636
            } elseif ($data === 'stat_country') {
1637
                $tmp = '';
1638
                foreach ($stats_data[$data] as $key => $value) {
1639
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1640
                }
1641
                $statsToSend[$data] = $tmp;
1642
            } else {
1643
                $statsToSend[$data] = $stats_data[$data];
1644
            }
1645
        }
1646
1647
        // connect to Teampass Statistics database
1648
        $link2 = new MeekroDB(
1649
            'teampass.pw',
1650
            'teampass_user',
1651
            'ZMlEfRzKzFLZNzie',
1652
            'teampass_followup',
1653
            '3306',
1654
            'utf8'
1655
        );
1656
1657
        $link2->insert(
1658
            'statistics',
1659
            $statsToSend
1660
        );
1661
1662
        // update table misc with current timestamp
1663
        DB::update(
1664
            prefixTable('misc'),
1665
            array(
1666
                'valeur' => time(),
1667
                'updated_at' => time(),
1668
            ),
1669
            'type = %s AND intitule = %s',
1670
            'admin',
1671
            'send_stats_time'
1672
        );
1673
1674
        //permits to test only once by session
1675
        $session->set('system-send_stats_done', 1);
1676
        $SETTINGS['send_stats_time'] = time();
1677
    }
1678
}
1679
1680
function generateBugReport(
1681
    array $data,
1682
    array $SETTINGS
1683
): array
1684
{
1685
    $excludedVars = array(
1686
        'bck_script_passkey',
1687
        'email_smtp_server',
1688
        'email_auth_username',
1689
        'email_auth_pwd',
1690
        'email_from',
1691
        'onthefly-restore-key',
1692
        'onthefly-backup-key',
1693
        'ldap_password',
1694
        'ldap_hosts',
1695
        'proxy_ip',
1696
        'ldap_bind_passwd',
1697
        'syslog_host',
1698
        'duo_akey',
1699
        'duo_ikey',
1700
        'duo_skey',
1701
        'duo_host',
1702
        'oauth2_client_id',
1703
        'oauth2_tenant_id',
1704
        'oauth2_client_secret',
1705
        'oauth2_client_token',
1706
        'oauth2_client_endpoint',
1707
    );
1708
1709
    // Load user's language
1710
    $session = SessionManager::getSession();
1711
    $lang = new Language($session->get('user-language') ?? 'english');
1712
1713
    // Anonymisation URL
1714
    $urlFound = $SETTINGS['cpassman_url'] ?? '';
1715
    $anonymUrl = '';
1716
    if (!empty($urlFound)) {
1717
        $parsed = parse_url($urlFound);
1718
        $anonymUrl = $parsed['scheme'] . '://<anonym_url>' . ($parsed['path'] ?? '');
1719
    }
1720
1721
    // Filtrage et tri des réglages
1722
    $listOfOptions = [];
1723
    ksort($SETTINGS);
1724
1725
    foreach ($SETTINGS as $key => $value) {
1726
        if (in_array($key, $excludedVars, true)) {
1727
            $value = '<removed>';
1728
        } elseif (!empty($anonymUrl)) {
1729
            $value = str_replace($urlFound, $anonymUrl, (string) $value);
1730
        }
1731
1732
        $listOfOptions[] = "'$key' => '$value'";
1733
    }
1734
    $listOfOptions = implode("\n", $listOfOptions);
1735
1736
    // Récupère dernière erreur PHP
1737
    $phpError = error_get_last();
1738
    $phpErrorMsg = $phpError
1739
        ? $phpError['message'] . ' - ' . $phpError['file'] . ' (' . $phpError['line'] . ')'
1740
        : $lang->get('no_error_logged');
1741
1742
    // Dernières erreurs Teampass
1743
    $teampassErrors = [];
1744
    $errors = DB::query(
1745
        'SELECT label, date AS error_date
1746
         FROM ' . prefixTable('log_system') . "
1747
         WHERE `type` = 'error'
1748
         ORDER BY `date` DESC
1749
         LIMIT 10"
1750
    );
1751
1752
    foreach ($errors as $record) {
1753
        $teampassErrors[] = ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1754
    }
1755
    $teampassErrors = count($teampassErrors) > 0 ? implode("\n", $teampassErrors) : $lang->get('no_error_logged');
1756
1757
    // Version DB
1758
    if (!defined('DB_PASSWD_CLEAR')) {
1759
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD));
1760
    }
1761
    $dbLink = @mysqli_connect(DB_HOST, DB_USER, DB_PASSWD_CLEAR, DB_NAME, (int) DB_PORT);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with socket. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1761
    $dbLink = @/** @scrutinizer ignore-call */ mysqli_connect(DB_HOST, DB_USER, DB_PASSWD_CLEAR, DB_NAME, (int) DB_PORT);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1762
    $dbVersion = $dbLink ? mysqli_get_server_info($dbLink) : $lang->get('undefined');
1763
1764
    // Now prepare text
1765
    $reportContent = <<<REPORT
1766
### Page on which it happened
1767
{$data['current_page']}
1768
1769
### Steps to reproduce
1770
1.
1771
2.
1772
3.
1773
1774
### Expected behaviour
1775
Tell us what should happen
1776
1777
1778
### Actual behaviour
1779
Tell us what happens instead
1780
1781
### Server configuration
1782
**Operating system**: {php_uname()}
1783
**Web server**: {$_SERVER['SERVER_SOFTWARE']}
1784
**Database**: {$dbVersion}
1785
**PHP version**: {PHP_VERSION}
1786
**Teampass version**: {TP_VERSION}.{TP_VERSION_MINOR}
1787
1788
**Teampass configuration variables:**
1789
```
1790
{$listOfOptions}
1791
```
1792
1793
**Updated from an older Teampass or fresh install:**
1794
1795
### Client configuration
1796
**Browser**: {$data['browser_name']} - {$data['browser_version']}
1797
**Operating system**: {$data['os']} - {$data['os_archi']} bits
1798
1799
### Logs
1800
1801
#### Web server error log
1802
```
1803
{$phpErrorMsg}
1804
```
1805
1806
#### Teampass 10 last system errors
1807
```
1808
{$teampassErrors}
1809
```
1810
1811
#### Log from the web-browser developer console (CTRL + SHIFT + i)
1812
```
1813
Insert the log here and especially the answer of the query that failed.
1814
```
1815
REPORT;
1816
1817
    // Now prepare text
1818
    $reportData = [
1819
        'report' => $reportContent,
1820
        'error' => false
1821
    ];
1822
1823
    return $reportData;
1824
}
1825
1826
/**
1827
 * Check that the user password is valid
1828
 *
1829
 * @param integer $post_user_id
1830
 * @param string $post_user_password
1831
 * @param array $SETTINGS
1832
 * @return string
1833
 */
1834
function isUserPasswordCorrect(
1835
    int $post_user_id,
1836
    string $post_user_password,
1837
    array $SETTINGS
1838
): string
1839
{
1840
    $session = SessionManager::getSession();
1841
    // Load user's language
1842
    $lang = new Language($session->get('user-language') ?? 'english');
1843
    
1844
    if (isUserIdValid($post_user_id) === true) {
1845
        // Check if user exists
1846
        $userInfo = DB::queryFirstRow(
1847
            'SELECT public_key, private_key, pw, auth_type
1848
            FROM ' . prefixTable('users') . '
1849
            WHERE id = %i',
1850
            $post_user_id
1851
        );
1852
        if (DB::count() > 0 && empty($userInfo['private_key']) === false) {
1853
            // Get itemKey from current user
1854
            // Get one item
1855
            $currentUserKey = DB::queryFirstRow(
1856
                'SELECT object_id, share_key, increment_id
1857
                FROM ' . prefixTable('sharekeys_items') . ' AS si
1858
                INNER JOIN ' . prefixTable('items') . ' AS i ON  (i.id = si.object_id)
1859
                INNER JOIN ' . prefixTable('nested_tree') . ' AS nt ON  (i.id_tree = nt.id)
1860
                WHERE user_id = %i AND nt.personal_folder = %i',
1861
                $post_user_id,
1862
                0
1863
            );
1864
            
1865
            if (DB::count() === 0) {
1866
                // This user has no items
1867
                // let's consider no items in DB
1868
                return prepareExchangedData(
1869
                    array(
1870
                        'error' => false,
1871
                        'message' => '',
1872
                        'debug' => '',
1873
                    ),
1874
                    'encode'
1875
                );
1876
            }
1877
1878
            if ($currentUserKey !== null) {
1879
                // Decrypt itemkey with user key
1880
                // use old password to decrypt private_key
1881
                $session->set('user-private_key', decryptPrivateKey($post_user_password, $userInfo['private_key']));
1882
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
1883
1884
                //echo $post_user_password."  --  ".$userInfo['private_key']. ";;";
1885
1886
                if (empty(base64_decode($itemKey)) === false) {
1887
                    // GOOD password
1888
                    return prepareExchangedData(
1889
                        array(
1890
                            'error' => false,
1891
                            'message' => '',
1892
                            'debug' => '',
1893
                        ),
1894
                        'encode'
1895
                    );
1896
                }
1897
            }
1898
1899
            // use the password check
1900
            $passwordManager = new PasswordManager();
1901
            
1902
            if ($passwordManager->verifyPassword($userInfo['pw'], htmlspecialchars_decode($post_user_password)) === true) {
1903
                // GOOD password
1904
                return prepareExchangedData(
1905
                    array(
1906
                        'error' => false,
1907
                        'message' => '',
1908
                        'debug' => '',
1909
                    ),
1910
                    'encode'
1911
                );
1912
            }
1913
        }
1914
    }
1915
1916
    return prepareExchangedData(
1917
        array(
1918
            'error' => true,
1919
            'message' => $lang->get('password_is_not_correct'),
1920
        ),
1921
        'encode'
1922
    );
1923
}
1924
1925
function changePrivateKeyEncryptionPassword(
1926
    int $post_user_id,
1927
    string $post_current_code,
1928
    string $post_new_code,
1929
    string $post_action_type,
1930
    array $SETTINGS
1931
): string
1932
{
1933
    $session = SessionManager::getSession();
1934
    // Load user's language
1935
    $lang = new Language($session->get('user-language') ?? 'english');
1936
    
1937
    if (empty($post_new_code) === true) {
1938
        // no user password
1939
        return prepareExchangedData(
1940
            array(
1941
                'error' => true,
1942
                'message' => $lang->get('error_bad_credentials'),
1943
                'debug' => '',
1944
            ),
1945
            'encode'
1946
        );
1947
    }
1948
1949
    if (isUserIdValid($post_user_id) === true) {
1950
        // Get user info
1951
        $userData = DB::queryFirstRow(
1952
            'SELECT private_key
1953
            FROM ' . prefixTable('users') . '
1954
            WHERE id = %i',
1955
            $post_user_id
1956
        );
1957
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
1958
            if ($post_action_type === 'encrypt_privkey_with_user_password') {
1959
                // Here the user has his private key encrypted with an OTC.
1960
                // We need to encrypt it with his real password
1961
                $privateKey = decryptPrivateKey($post_new_code, $userData['private_key']);
1962
                $hashedPrivateKey = encryptPrivateKey($post_current_code, $privateKey);
1963
            } else {
1964
                $privateKey = decryptPrivateKey($post_current_code, $userData['private_key']);
1965
                $hashedPrivateKey = encryptPrivateKey($post_new_code, $privateKey);
1966
            }
1967
1968
            // Should fail here to avoid break user private key.
1969
            if (strlen($privateKey) === 0 || strlen($hashedPrivateKey) < 30) {
1970
                if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
1971
                    error_log("Error reencrypt user private key. User ID: {$post_user_id}, Given OTP: '{$post_current_code}'");
1972
                }
1973
                return prepareExchangedData(
1974
                    array(
1975
                        'error' => true,
1976
                        'message' => $lang->get('error_otp_secret'),
1977
                        'debug' => '',
1978
                    ),
1979
                    'encode'
1980
                );
1981
            }
1982
1983
            // Update user account
1984
            DB::update(
1985
                prefixTable('users'),
1986
                array(
1987
                    'private_key' => $hashedPrivateKey,
1988
                    'special' => 'none',
1989
                    'otp_provided' => 1,
1990
                ),
1991
                'id = %i',
1992
                $post_user_id
1993
            );
1994
1995
            $session->set('user-private_key', $privateKey);
1996
        }
1997
1998
        // Return
1999
        return prepareExchangedData(
2000
            array(
2001
                'error' => false,
2002
                'message' => '',
2003
            ),
2004
            'encode'
2005
        );
2006
    }
2007
    
2008
    return prepareExchangedData(
2009
        array(
2010
            'error' => true,
2011
            'message' => $lang->get('error_no_user'),
2012
            'debug' => '',
2013
        ),
2014
        'encode'
2015
    );
2016
}
2017
2018
function initializeUserPassword(
2019
    int $post_user_id,
2020
    string $post_special,
2021
    string $post_user_password,
2022
    bool $post_self_change,
2023
    array $SETTINGS
2024
): string
2025
{
2026
    // Load user's language
2027
    $session = SessionManager::getSession();
2028
    $lang = new Language($session->get('user-language') ?? 'english');
2029
    
2030
    if (isUserIdValid($post_user_id) === true) {
2031
        // Get user info
2032
        $userData = DB::queryFirstRow(
2033
            'SELECT email, auth_type, login
2034
            FROM ' . prefixTable('users') . '
2035
            WHERE id = %i',
2036
            $post_user_id
2037
        );
2038
        if (DB::count() > 0 && empty($userData['email']) === false) {
2039
            // If user pwd is empty then generate a new one and send it to user
2040
            if (empty($post_user_password) === true) {
2041
                // Generate new password
2042
                $post_user_password = generateQuickPassword();
2043
            }
2044
2045
            // If LDAP enabled, then
2046
            // check that this password is correct
2047
            $continue = true;
2048
            if ($userData['auth_type'] === 'ldap' && (int) $SETTINGS['ldap_mode'] === 1) {
2049
                $continue = ldapCheckUserPassword(
2050
                    $userData['login'],
2051
                    $post_user_password,
2052
                    $SETTINGS
2053
                );
2054
            }
2055
2056
            if ($continue === true) {
2057
                // Only change if email is successfull
2058
                $passwordManager = new PasswordManager();
2059
                // GEnerate new keys
2060
                $userKeys = generateUserKeys($post_user_password);
2061
2062
                // Update user account
2063
                DB::update(
2064
                    prefixTable('users'),
2065
                    array(
2066
                        'special' => $post_special,
2067
                        'pw' => $passwordManager->hashPassword($post_user_password),
2068
                        'public_key' => $userKeys['public_key'],
2069
                        'private_key' => $userKeys['private_key'],
2070
                        'last_pw_change' => time(),
2071
                    ),
2072
                    'id = %i',
2073
                    $post_user_id
2074
                );
2075
2076
                // Return
2077
                return prepareExchangedData(
2078
                    array(
2079
                        'error' => false,
2080
                        'message' => '',
2081
                        'user_pwd' => $post_user_password,
2082
                        'user_email' => $userData['email'],
2083
                    ),
2084
                    'encode'
2085
                );
2086
            }
2087
            // Return error
2088
            return prepareExchangedData(
2089
                array(
2090
                    'error' => true,
2091
                    'message' => $lang->get('no_email_set'),
2092
                    'debug' => '',
2093
                    'self_change' => $post_self_change,
2094
                ),
2095
                'encode'
2096
            );
2097
        }
2098
2099
        // Error
2100
        return prepareExchangedData(
2101
            array(
2102
                'error' => true,
2103
                'message' => $lang->get('no_email_set'),
2104
                'debug' => '',
2105
            ),
2106
            'encode'
2107
        );
2108
    }
2109
    
2110
    return prepareExchangedData(
2111
        array(
2112
            'error' => true,
2113
            'message' => $lang->get('error_no_user'),
2114
            'debug' => '',
2115
        ),
2116
        'encode'
2117
    );
2118
}
2119
2120
function generateOneTimeCode(
2121
    int $post_user_id
2122
): string
2123
{
2124
    // Load user's language
2125
    $session = SessionManager::getSession();
2126
    $lang = new Language($session->get('user-language') ?? 'english');
2127
    
2128
    if (isUserIdValid($post_user_id) === true) {
2129
        // Get user info
2130
        $userData = DB::queryFirstRow(
2131
            'SELECT email, auth_type, login
2132
            FROM ' . prefixTable('users') . '
2133
            WHERE id = %i',
2134
            $post_user_id
2135
        );
2136
        if (DB::count() > 0 && empty($userData['email']) === false) {
2137
            // Generate pwd
2138
            $password = generateQuickPassword();
2139
2140
            // GEnerate new keys
2141
            $userKeys = generateUserKeys($password);
2142
2143
            // Save in DB
2144
            DB::update(
2145
                prefixTable('users'),
2146
                array(
2147
                    'public_key' => $userKeys['public_key'],
2148
                    'private_key' => $userKeys['private_key'],
2149
                    'special' => 'generate-keys',
2150
                ),
2151
                'id=%i',
2152
                $post_user_id
2153
            );
2154
2155
            return prepareExchangedData(
2156
                array(
2157
                    'error' => false,
2158
                    'message' => '',
2159
                    'code' => $password,
2160
                    'visible_otp' => ADMIN_VISIBLE_OTP_ON_LDAP_IMPORT,
2161
                ),
2162
                'encode'
2163
            );
2164
        }
2165
        
2166
        return prepareExchangedData(
2167
            array(
2168
                'error' => true,
2169
                'message' => $lang->get('no_email_set'),
2170
            ),
2171
            'encode'
2172
        );
2173
    }
2174
        
2175
    return prepareExchangedData(
2176
        array(
2177
            'error' => true,
2178
            'message' => $lang->get('error_no_user'),
2179
        ),
2180
        'encode'
2181
    );
2182
}
2183
2184
function startReEncryptingUserSharekeys(
2185
    int $post_user_id,
2186
    bool $post_self_change,
2187
    array $SETTINGS
2188
): string
2189
{
2190
    // Load user's language
2191
    $session = SessionManager::getSession();
2192
    $lang = new Language($session->get('user-language') ?? 'english');
2193
    
2194
    if (isUserIdValid($post_user_id) === true) {
2195
        // Check if user exists
2196
        DB::queryFirstRow(
2197
            'SELECT *
2198
            FROM ' . prefixTable('users') . '
2199
            WHERE id = %i',
2200
            $post_user_id
2201
        );
2202
        if (DB::count() > 0) {
2203
            // CLear old sharekeys
2204
            if ($post_self_change === false) {
2205
                deleteUserObjetsKeys($post_user_id, $SETTINGS);
2206
            }
2207
2208
            // Continu with next step
2209
            return prepareExchangedData(
2210
                array(
2211
                    'error' => false,
2212
                    'message' => '',
2213
                    'step' => 'step1',
2214
                    'userId' => $post_user_id,
2215
                    'start' => 0,
2216
                    'self_change' => $post_self_change,
2217
                ),
2218
                'encode'
2219
            );
2220
        }
2221
        // Nothing to do
2222
        return prepareExchangedData(
2223
            array(
2224
                'error' => true,
2225
                'message' => $lang->get('error_no_user'),
2226
            ),
2227
            'encode'
2228
        );
2229
    }
2230
2231
    return prepareExchangedData(
2232
        array(
2233
            'error' => true,
2234
            'message' => $lang->get('error_no_user'),
2235
        ),
2236
        'encode'
2237
    );
2238
}
2239
2240
/**
2241
 * Permits to encrypt user's keys
2242
 *
2243
 * @param integer $post_user_id
2244
 * @param boolean $post_self_change
2245
 * @param string $post_action
2246
 * @param integer $post_start
2247
 * @param integer $post_length
2248
 * @param array $SETTINGS
2249
 * @return string
2250
 */
2251
function continueReEncryptingUserSharekeys(
2252
    int     $post_user_id,
2253
    bool    $post_self_change,
2254
    string  $post_action,
2255
    int     $post_start,
2256
    int     $post_length,
2257
    array   $SETTINGS
2258
): string
2259
{
2260
    // Load user's language
2261
    $session = SessionManager::getSession();
2262
    $lang = new Language($session->get('user-language') ?? 'english');
2263
    
2264
    if (isUserIdValid($post_user_id) === true) {
2265
        // Check if user exists
2266
        $userInfo = DB::queryFirstRow(
2267
            'SELECT public_key
2268
            FROM ' . prefixTable('users') . '
2269
            WHERE id = %i',
2270
            $post_user_id
2271
        );
2272
        if (isset($userInfo['public_key']) === true) {
2273
            $return = [];
2274
2275
            // WHAT STEP TO PERFORM?
2276
            if ($post_action === 'step0') {
2277
                // CLear old sharekeys
2278
                if ($post_self_change === false) {
2279
                    deleteUserObjetsKeys($post_user_id, $SETTINGS);
2280
                }
2281
2282
                $return['post_action'] = 'step10';
2283
            }
2284
            
2285
            // STEP 1 - ITEMS
2286
            elseif ($post_action === 'step10') {
2287
                $return = continueReEncryptingUserSharekeysStep10(
2288
                    $post_user_id,
2289
                    $post_self_change,
2290
                    $post_action,
2291
                    $post_start,
2292
                    $post_length,
2293
                    $userInfo['public_key'],
2294
                    $SETTINGS
2295
                );
2296
            }
2297
2298
            // STEP 2 - LOGS
2299
            elseif ($post_action === 'step20') {
2300
                $return = continueReEncryptingUserSharekeysStep20(
2301
                    $post_user_id,
2302
                    $post_self_change,
2303
                    $post_action,
2304
                    $post_start,
2305
                    $post_length,
2306
                    $userInfo['public_key'],
2307
                    $SETTINGS
2308
                );
2309
            }
2310
2311
            // STEP 3 - FIELDS
2312
            elseif ($post_action === 'step30') {
2313
                $return = continueReEncryptingUserSharekeysStep30(
2314
                    $post_user_id,
2315
                    $post_self_change,
2316
                    $post_action,
2317
                    $post_start,
2318
                    $post_length,
2319
                    $userInfo['public_key'],
2320
                    $SETTINGS
2321
                );
2322
            }
2323
            
2324
            // STEP 4 - SUGGESTIONS
2325
            elseif ($post_action === 'step40') {
2326
                $return = continueReEncryptingUserSharekeysStep40(
2327
                    $post_user_id,
2328
                    $post_self_change,
2329
                    $post_action,
2330
                    $post_start,
2331
                    $post_length,
2332
                    $userInfo['public_key'],
2333
                    $SETTINGS
2334
                );
2335
            }
2336
            
2337
            // STEP 5 - FILES
2338
            elseif ($post_action === 'step50') {
2339
                $return = continueReEncryptingUserSharekeysStep50(
2340
                    $post_user_id,
2341
                    $post_self_change,
2342
                    $post_action,
2343
                    $post_start,
2344
                    $post_length,
2345
                    $userInfo['public_key'],
2346
                    $SETTINGS
2347
                );
2348
            }
2349
            
2350
            // STEP 6 - PERSONAL ITEMS
2351
            elseif ($post_action === 'step60') {
2352
                $return = continueReEncryptingUserSharekeysStep60(
2353
                    $post_user_id,
2354
                    $post_self_change,
2355
                    $post_action,
2356
                    $post_start,
2357
                    $post_length,
2358
                    $userInfo['public_key'],
2359
                    $SETTINGS
2360
                );
2361
            }
2362
            
2363
            // Continu with next step
2364
            return prepareExchangedData(
2365
                array(
2366
                    'error' => false,
2367
                    'message' => '',
2368
                    'step' => isset($return['post_action']) === true ? $return['post_action'] : '',
2369
                    'start' => isset($return['next_start']) === true ? $return['next_start'] : 0,
2370
                    'userId' => $post_user_id,
2371
                    'self_change' => $post_self_change,
2372
                ),
2373
                'encode'
2374
            );
2375
        }
2376
        
2377
        // Nothing to do
2378
        return prepareExchangedData(
2379
            array(
2380
                'error' => false,
2381
                'message' => '',
2382
                'step' => 'finished',
2383
                'start' => 0,
2384
                'userId' => $post_user_id,
2385
                'self_change' => $post_self_change,
2386
            ),
2387
            'encode'
2388
        );
2389
    }
2390
    
2391
    // Nothing to do
2392
    return prepareExchangedData(
2393
        array(
2394
            'error' => true,
2395
            'message' => $lang->get('error_no_user'),
2396
            'extra' => $post_user_id,
2397
        ),
2398
        'encode'
2399
    );
2400
}
2401
2402
function continueReEncryptingUserSharekeysStep10(
2403
    int $post_user_id,
2404
    bool $post_self_change,
2405
    string $post_action,
2406
    int $post_start,
2407
    int $post_length,
2408
    string $user_public_key,
2409
    array $SETTINGS
2410
): array 
2411
{
2412
    $session = SessionManager::getSession();
2413
    // Loop on items
2414
    $rows = DB::query(
2415
        'SELECT id, pw
2416
        FROM ' . prefixTable('items') . '
2417
        WHERE perso = 0
2418
        LIMIT ' . $post_start . ', ' . $post_length
2419
    );
2420
    foreach ($rows as $record) {
2421
        // Get itemKey from current user
2422
        $currentUserKey = DB::queryFirstRow(
2423
            'SELECT share_key, increment_id
2424
            FROM ' . prefixTable('sharekeys_items') . '
2425
            WHERE object_id = %i AND user_id = %i',
2426
            $record['id'],
2427
            $session->get('user-id')
2428
        );
2429
2430
        // do we have any input? (#3481)
2431
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2432
            continue;
2433
        }
2434
2435
        // Decrypt itemkey with admin key
2436
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2437
        
2438
        // Encrypt Item key
2439
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2440
        
2441
        // Save the key in DB
2442
        if ($post_self_change === false) {
2443
            DB::insert(
2444
                prefixTable('sharekeys_items'),
2445
                array(
2446
                    'object_id' => (int) $record['id'],
2447
                    'user_id' => (int) $post_user_id,
2448
                    'share_key' => $share_key_for_item,
2449
                )
2450
            );
2451
        } else {
2452
            // Get itemIncrement from selected user
2453
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2454
                $currentUserKey = DB::queryFirstRow(
2455
                    'SELECT increment_id
2456
                    FROM ' . prefixTable('sharekeys_items') . '
2457
                    WHERE object_id = %i AND user_id = %i',
2458
                    $record['id'],
2459
                    $post_user_id
2460
                );
2461
2462
                if (DB::count() > 0) {
2463
                    // NOw update
2464
                    DB::update(
2465
                        prefixTable('sharekeys_items'),
2466
                        array(
2467
                            'share_key' => $share_key_for_item,
2468
                        ),
2469
                        'increment_id = %i',
2470
                        $currentUserKey['increment_id']
2471
                    );
2472
                } else {
2473
                    DB::insert(
2474
                        prefixTable('sharekeys_items'),
2475
                        array(
2476
                            'object_id' => (int) $record['id'],
2477
                            'user_id' => (int) $post_user_id,
2478
                            'share_key' => $share_key_for_item,
2479
                        )
2480
                    );
2481
                }
2482
            }
2483
        }
2484
    }
2485
2486
    // SHould we change step?
2487
    DB::query(
2488
        'SELECT *
2489
        FROM ' . prefixTable('items') . '
2490
        WHERE perso = 0'
2491
    );
2492
2493
    $next_start = (int) $post_start + (int) $post_length;
2494
    return [
2495
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2496
        'post_action' => $next_start > DB::count() ? 'step20' : 'step10',
2497
    ];
2498
}
2499
2500
function continueReEncryptingUserSharekeysStep20(
2501
    int $post_user_id,
2502
    bool $post_self_change,
2503
    string $post_action,
2504
    int $post_start,
2505
    int $post_length,
2506
    string $user_public_key,
2507
    array $SETTINGS
2508
): array
2509
{
2510
    $session = SessionManager::getSession();
2511
    // Loop on logs
2512
    $rows = DB::query(
2513
        'SELECT increment_id
2514
        FROM ' . prefixTable('log_items') . '
2515
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"
2516
        LIMIT ' . $post_start . ', ' . $post_length
2517
    );
2518
    foreach ($rows as $record) {
2519
        // Get itemKey from current user
2520
        $currentUserKey = DB::queryFirstRow(
2521
            'SELECT share_key
2522
            FROM ' . prefixTable('sharekeys_logs') . '
2523
            WHERE object_id = %i AND user_id = %i',
2524
            $record['increment_id'],
2525
            $session->get('user-id')
2526
        );
2527
2528
        // do we have any input? (#3481)
2529
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2530
            continue;
2531
        }
2532
2533
        // Decrypt itemkey with admin key
2534
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2535
2536
        // Encrypt Item key
2537
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2538
2539
        // Save the key in DB
2540
        if ($post_self_change === false) {
2541
            DB::insert(
2542
                prefixTable('sharekeys_logs'),
2543
                array(
2544
                    'object_id' => (int) $record['increment_id'],
2545
                    'user_id' => (int) $post_user_id,
2546
                    'share_key' => $share_key_for_item,
2547
                )
2548
            );
2549
        } else {
2550
            // Get itemIncrement from selected user
2551
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2552
                $currentUserKey = DB::queryFirstRow(
2553
                    'SELECT increment_id
2554
                    FROM ' . prefixTable('sharekeys_items') . '
2555
                    WHERE object_id = %i AND user_id = %i',
2556
                    $record['id'],
2557
                    $post_user_id
2558
                );
2559
            }
2560
2561
            // NOw update
2562
            DB::update(
2563
                prefixTable('sharekeys_logs'),
2564
                array(
2565
                    'share_key' => $share_key_for_item,
2566
                ),
2567
                'increment_id = %i',
2568
                $currentUserKey['increment_id']
2569
            );
2570
        }
2571
    }
2572
2573
    // SHould we change step?
2574
    DB::query(
2575
        'SELECT increment_id
2576
        FROM ' . prefixTable('log_items') . '
2577
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"'
2578
    );
2579
2580
    $next_start = (int) $post_start + (int) $post_length;
2581
    return [
2582
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2583
        'post_action' => $next_start > DB::count() ? 'step30' : 'step20',
2584
    ];
2585
}
2586
2587
function continueReEncryptingUserSharekeysStep30(
2588
    int $post_user_id,
2589
    bool $post_self_change,
2590
    string $post_action,
2591
    int $post_start,
2592
    int $post_length,
2593
    string $user_public_key,
2594
    array $SETTINGS
2595
): array
2596
{
2597
    $session = SessionManager::getSession();
2598
    // Loop on fields
2599
    $rows = DB::query(
2600
        'SELECT id
2601
        FROM ' . prefixTable('categories_items') . '
2602
        WHERE encryption_type = "teampass_aes"
2603
        LIMIT ' . $post_start . ', ' . $post_length
2604
    );
2605
    foreach ($rows as $record) {
2606
        // Get itemKey from current user
2607
        $currentUserKey = DB::queryFirstRow(
2608
            'SELECT share_key
2609
            FROM ' . prefixTable('sharekeys_fields') . '
2610
            WHERE object_id = %i AND user_id = %i',
2611
            $record['id'],
2612
            $session->get('user-id')
2613
        );
2614
2615
        // do we have any input? (#3481)
2616
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2617
            continue;
2618
        }
2619
2620
        // Decrypt itemkey with admin key
2621
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2622
2623
        // Encrypt Item key
2624
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2625
2626
        // Save the key in DB
2627
        if ($post_self_change === false) {
2628
            DB::insert(
2629
                prefixTable('sharekeys_fields'),
2630
                array(
2631
                    'object_id' => (int) $record['id'],
2632
                    'user_id' => (int) $post_user_id,
2633
                    'share_key' => $share_key_for_item,
2634
                )
2635
            );
2636
        } else {
2637
            // Get itemIncrement from selected user
2638
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2639
                $currentUserKey = DB::queryFirstRow(
2640
                    'SELECT increment_id
2641
                    FROM ' . prefixTable('sharekeys_items') . '
2642
                    WHERE object_id = %i AND user_id = %i',
2643
                    $record['id'],
2644
                    $post_user_id
2645
                );
2646
            }
2647
2648
            // NOw update
2649
            DB::update(
2650
                prefixTable('sharekeys_fields'),
2651
                array(
2652
                    'share_key' => $share_key_for_item,
2653
                ),
2654
                'increment_id = %i',
2655
                $currentUserKey['increment_id']
2656
            );
2657
        }
2658
    }
2659
2660
    // SHould we change step?
2661
    DB::query(
2662
        'SELECT *
2663
        FROM ' . prefixTable('categories_items') . '
2664
        WHERE encryption_type = "teampass_aes"'
2665
    );
2666
2667
    $next_start = (int) $post_start + (int) $post_length;
2668
    return [
2669
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2670
        'post_action' => $next_start > DB::count() ? 'step40' : 'step30',
2671
    ];
2672
}
2673
2674
function continueReEncryptingUserSharekeysStep40(
2675
    int $post_user_id,
2676
    bool $post_self_change,
2677
    string $post_action,
2678
    int $post_start,
2679
    int $post_length,
2680
    string $user_public_key,
2681
    array $SETTINGS
2682
): array
2683
{
2684
    $session = SessionManager::getSession();
2685
    // Loop on suggestions
2686
    $rows = DB::query(
2687
        'SELECT id
2688
        FROM ' . prefixTable('suggestion') . '
2689
        LIMIT ' . $post_start . ', ' . $post_length
2690
    );
2691
    foreach ($rows as $record) {
2692
        // Get itemKey from current user
2693
        $currentUserKey = DB::queryFirstRow(
2694
            'SELECT share_key
2695
            FROM ' . prefixTable('sharekeys_suggestions') . '
2696
            WHERE object_id = %i AND user_id = %i',
2697
            $record['id'],
2698
            $session->get('user-id')
2699
        );
2700
2701
        // do we have any input? (#3481)
2702
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2703
            continue;
2704
        }
2705
2706
        // Decrypt itemkey with admin key
2707
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2708
2709
        // Encrypt Item key
2710
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2711
2712
        // Save the key in DB
2713
        if ($post_self_change === false) {
2714
            DB::insert(
2715
                prefixTable('sharekeys_suggestions'),
2716
                array(
2717
                    'object_id' => (int) $record['id'],
2718
                    'user_id' => (int) $post_user_id,
2719
                    'share_key' => $share_key_for_item,
2720
                )
2721
            );
2722
        } else {
2723
            // Get itemIncrement from selected user
2724
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2725
                $currentUserKey = DB::queryFirstRow(
2726
                    'SELECT increment_id
2727
                    FROM ' . prefixTable('sharekeys_items') . '
2728
                    WHERE object_id = %i AND user_id = %i',
2729
                    $record['id'],
2730
                    $post_user_id
2731
                );
2732
            }
2733
2734
            // NOw update
2735
            DB::update(
2736
                prefixTable('sharekeys_suggestions'),
2737
                array(
2738
                    'share_key' => $share_key_for_item,
2739
                ),
2740
                'increment_id = %i',
2741
                $currentUserKey['increment_id']
2742
            );
2743
        }
2744
    }
2745
2746
    // SHould we change step?
2747
    DB::query(
2748
        'SELECT *
2749
        FROM ' . prefixTable('suggestion')
2750
    );
2751
2752
    $next_start = (int) $post_start + (int) $post_length;
2753
    return [
2754
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2755
        'post_action' => $next_start > DB::count() ? 'step50' : 'step40',
2756
    ];
2757
}
2758
2759
function continueReEncryptingUserSharekeysStep50(
2760
    int $post_user_id,
2761
    bool $post_self_change,
2762
    string $post_action,
2763
    int $post_start,
2764
    int $post_length,
2765
    string $user_public_key,
2766
    array $SETTINGS
2767
): array
2768
{
2769
    $session = SessionManager::getSession();
2770
    // Loop on files
2771
    $rows = DB::query(
2772
        'SELECT id
2773
        FROM ' . prefixTable('files') . '
2774
        WHERE status = "' . TP_ENCRYPTION_NAME . '"
2775
        LIMIT ' . $post_start . ', ' . $post_length
2776
    ); //aes_encryption
2777
    foreach ($rows as $record) {
2778
        // Get itemKey from current user
2779
        $currentUserKey = DB::queryFirstRow(
2780
            'SELECT share_key
2781
            FROM ' . prefixTable('sharekeys_files') . '
2782
            WHERE object_id = %i AND user_id = %i',
2783
            $record['id'],
2784
            $session->get('user-id')
2785
        );
2786
2787
        // do we have any input? (#3481)
2788
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2789
            continue;
2790
        }
2791
2792
        // Decrypt itemkey with admin key
2793
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2794
2795
        // Encrypt Item key
2796
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2797
2798
        // Save the key in DB
2799
        if ($post_self_change === false) {
2800
            DB::insert(
2801
                prefixTable('sharekeys_files'),
2802
                array(
2803
                    'object_id' => (int) $record['id'],
2804
                    'user_id' => (int) $post_user_id,
2805
                    'share_key' => $share_key_for_item,
2806
                )
2807
            );
2808
        } else {
2809
            // Get itemIncrement from selected user
2810
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2811
                $currentUserKey = DB::queryFirstRow(
2812
                    'SELECT increment_id
2813
                    FROM ' . prefixTable('sharekeys_items') . '
2814
                    WHERE object_id = %i AND user_id = %i',
2815
                    $record['id'],
2816
                    $post_user_id
2817
                );
2818
            }
2819
2820
            // NOw update
2821
            DB::update(
2822
                prefixTable('sharekeys_files'),
2823
                array(
2824
                    'share_key' => $share_key_for_item,
2825
                ),
2826
                'increment_id = %i',
2827
                $currentUserKey['increment_id']
2828
            );
2829
        }
2830
    }
2831
2832
    // SHould we change step?
2833
    DB::query(
2834
        'SELECT *
2835
        FROM ' . prefixTable('files') . '
2836
        WHERE status = "' . TP_ENCRYPTION_NAME . '"'
2837
    );
2838
2839
    $next_start = (int) $post_start + (int) $post_length;
2840
    return [
2841
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2842
        'post_action' => $next_start > DB::count() ? 'step60' : 'step50',
2843
    ];
2844
}
2845
2846
function continueReEncryptingUserSharekeysStep60(
2847
    int $post_user_id,
2848
    bool $post_self_change,
2849
    string $post_action,
2850
    int $post_start,
2851
    int $post_length,
2852
    string $user_public_key,
2853
    array $SETTINGS
2854
): array
2855
{
2856
    $session = SessionManager::getSession();
2857
    // IF USER IS NOT THE SAME
2858
    if ((int) $post_user_id === (int) $session->get('user-id')) {
2859
        return [
2860
            'next_start' => 0,
2861
            'post_action' => 'finished',
2862
        ];
2863
    }
2864
    
2865
    // Loop on persoanl items
2866
    if (count($session->get('user-personal_folders')) > 0) {
2867
        $rows = DB::query(
2868
            'SELECT id, pw
2869
            FROM ' . prefixTable('items') . '
2870
            WHERE perso = 1 AND id_tree IN %ls AND encryption_type = %s
2871
            LIMIT ' . $post_start . ', ' . $post_length,
2872
            $session->get('user-personal_folders'),
2873
            "defuse"
2874
        );
2875
        foreach ($rows as $record) {
2876
            // Get itemKey from current user
2877
            $currentUserKey = DB::queryFirstRow(
2878
                'SELECT share_key, increment_id
2879
                FROM ' . prefixTable('sharekeys_items') . '
2880
                WHERE object_id = %i AND user_id = %i',
2881
                $record['id'],
2882
                $session->get('user-id')
2883
            );
2884
2885
            // Decrypt itemkey with admin key
2886
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2887
2888
            // Encrypt Item key
2889
            $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2890
2891
            // Save the key in DB
2892
            if ($post_self_change === false) {
2893
                DB::insert(
2894
                    prefixTable('sharekeys_items'),
2895
                    array(
2896
                        'object_id' => (int) $record['id'],
2897
                        'user_id' => (int) $post_user_id,
2898
                        'share_key' => $share_key_for_item,
2899
                    )
2900
                );
2901
            } else {
2902
                // Get itemIncrement from selected user
2903
                if ((int) $post_user_id !== (int) $session->get('user-id')) {
2904
                    $currentUserKey = DB::queryFirstRow(
2905
                        'SELECT increment_id
2906
                        FROM ' . prefixTable('sharekeys_items') . '
2907
                        WHERE object_id = %i AND user_id = %i',
2908
                        $record['id'],
2909
                        $post_user_id
2910
                    );
2911
                }
2912
2913
                // NOw update
2914
                DB::update(
2915
                    prefixTable('sharekeys_items'),
2916
                    array(
2917
                        'share_key' => $share_key_for_item,
2918
                    ),
2919
                    'increment_id = %i',
2920
                    $currentUserKey['increment_id']
2921
                );
2922
            }
2923
        }
2924
    }
2925
2926
    // SHould we change step?
2927
    DB::query(
2928
        'SELECT *
2929
        FROM ' . prefixTable('items') . '
2930
        WHERE perso = 0'
2931
    );
2932
2933
    $next_start = (int) $post_start + (int) $post_length;
2934
    return [
2935
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2936
        'post_action' => $next_start > DB::count() ? 'finished' : 'step60',
2937
    ];
2938
}
2939
2940
function migrateTo3_DoUserPersonalItemsEncryption(
2941
    int $post_user_id,
2942
    int $post_start,
2943
    int $post_length,
2944
    int $post_counterItemsToTreat,
2945
    string $post_user_psk,
2946
    array $SETTINGS
2947
) {
2948
    $next_step = 'psk';
2949
    
2950
    $session = SessionManager::getSession();
2951
    $lang = new Language($session->get('user-language') ?? 'english');
2952
    
2953
    if (isUserIdValid($post_user_id) === true) {
2954
        // Check if user exists
2955
        $userInfo = DB::queryFirstRow(
2956
            'SELECT public_key, encrypted_psk
2957
            FROM ' . prefixTable('users') . '
2958
            WHERE id = %i',
2959
            $post_user_id
2960
        );
2961
        if (DB::count() > 0) {
2962
            // check if psk is correct.
2963
            if (empty($userInfo['encrypted_psk']) === false) {//echo $post_user_psk." ;; ".$userInfo['encrypted_psk']." ;; ";
2964
                $user_key_encoded = defuse_validate_personal_key(
2965
                    html_entity_decode($post_user_psk), // convert tspecial string back to their original characters due to FILTER_SANITIZE_FULL_SPECIAL_CHARS
2966
                    $userInfo['encrypted_psk']
2967
                );
2968
2969
                if (strpos($user_key_encoded, "Error ") !== false) {
2970
                    return prepareExchangedData(
2971
                        array(
2972
                            'error' => true,
2973
                            'message' => $lang->get('bad_psk'),
2974
                        ),
2975
                        'encode'
2976
                    );
2977
                }
2978
2979
                // Get number of user's personal items with no AES encryption
2980
                if ($post_counterItemsToTreat === -1) {
2981
                    DB::query(
2982
                        'SELECT id
2983
                        FROM ' . prefixTable('items') . '
2984
                        WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
2985
                        $session->get('user-personal_folders'),
2986
                        'teampass_aes'
2987
                    );
2988
                    $countUserPersonalItems = DB::count();
2989
                } else {
2990
                    $countUserPersonalItems = $post_counterItemsToTreat;
2991
                }
2992
2993
                // Loop on persoanl items
2994
                $rows = DB::query(
2995
                    'SELECT id, pw
2996
                    FROM ' . prefixTable('items') . '
2997
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s
2998
                    LIMIT ' . $post_length,
2999
                    $session->get('user-personal_folders'),
3000
                    'teampass_aes'
3001
                );
3002
                foreach ($rows as $record) {
3003
                    // Decrypt with Defuse
3004
                    $passwd = cryption(
3005
                        $record['pw'],
3006
                        $user_key_encoded,
3007
                        'decrypt',
3008
                        $SETTINGS
3009
                    );
3010
3011
                    // Encrypt with Object Key
3012
                    $cryptedStuff = doDataEncryption(html_entity_decode($passwd['string']));
3013
3014
                    // Store new password in DB
3015
                    DB::update(
3016
                        prefixTable('items'),
3017
                        array(
3018
                            'pw' => $cryptedStuff['encrypted'],
3019
                            'encryption_type' => 'teampass_aes',
3020
                        ),
3021
                        'id = %i',
3022
                        $record['id']
3023
                    );
3024
3025
                    // Insert in DB the new object key for this item by user
3026
                    DB::insert(
3027
                        prefixTable('sharekeys_items'),
3028
                        array(
3029
                            'object_id' => (int) $record['id'],
3030
                            'user_id' => (int) $post_user_id,
3031
                            'share_key' => encryptUserObjectKey($cryptedStuff['objectKey'], $userInfo['public_key']),
3032
                        )
3033
                    );
3034
3035
3036
                    // Does this item has Files?
3037
                    // Loop on files
3038
                    $rows = DB::query(
3039
                        'SELECT id, file
3040
                        FROM ' . prefixTable('files') . '
3041
                        WHERE status != %s
3042
                        AND id_item = %i',
3043
                        TP_ENCRYPTION_NAME,
3044
                        $record['id']
3045
                    );
3046
                    //aes_encryption
3047
                    foreach ($rows as $record2) {
3048
                        // Now decrypt the file
3049
                        prepareFileWithDefuse(
3050
                            'decrypt',
3051
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'],
3052
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'] . '.delete',
3053
                            $post_user_psk
3054
                        );
3055
3056
                        // Encrypt the file
3057
                        $encryptedFile = encryptFile($record2['file'] . '.delete', $SETTINGS['path_to_upload_folder']);
3058
3059
                        DB::update(
3060
                            prefixTable('files'),
3061
                            array(
3062
                                'file' => $encryptedFile['fileHash'],
3063
                                'status' => TP_ENCRYPTION_NAME,
3064
                            ),
3065
                            'id = %i',
3066
                            $record2['id']
3067
                        );
3068
3069
                        // Save key
3070
                        DB::insert(
3071
                            prefixTable('sharekeys_files'),
3072
                            array(
3073
                                'object_id' => (int) $record2['id'],
3074
                                'user_id' => (int) $session->get('user-id'),
3075
                                'share_key' => encryptUserObjectKey($encryptedFile['objectKey'], $session->get('user-public_key')),
3076
                            )
3077
                        );
3078
3079
                        // Unlink original file
3080
                        unlink($SETTINGS['path_to_upload_folder'] . '/' . $record2['file']);
3081
                    }
3082
                }
3083
3084
                // SHould we change step?
3085
                $next_start = (int) $post_start + (int) $post_length;
3086
                DB::query(
3087
                    'SELECT id
3088
                    FROM ' . prefixTable('items') . '
3089
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
3090
                    $session->get('user-personal_folders'),
3091
                    'teampass_aes'
3092
                );
3093
                if (DB::count() === 0 || ($next_start - $post_length) >= $countUserPersonalItems) {
3094
                    // Now update user
3095
                    DB::update(
3096
                        prefixTable('users'),
3097
                        array(
3098
                            'special' => 'none',
3099
                            'upgrade_needed' => 0,
3100
                            'encrypted_psk' => '',
3101
                        ),
3102
                        'id = %i',
3103
                        $post_user_id
3104
                    );
3105
3106
                    $next_step = 'finished';
3107
                    $next_start = 0;
3108
                }
3109
3110
                // Continu with next step
3111
                return prepareExchangedData(
3112
                    array(
3113
                        'error' => false,
3114
                        'message' => '',
3115
                        'step' => $next_step,
3116
                        'start' => $next_start,
3117
                        'userId' => $post_user_id
3118
                    ),
3119
                    'encode'
3120
                );
3121
            }
3122
        }
3123
        
3124
        // Nothing to do
3125
        return prepareExchangedData(
3126
            array(
3127
                'error' => true,
3128
                'message' => $lang->get('error_no_user'),
3129
            ),
3130
            'encode'
3131
        );
3132
    }
3133
    
3134
    // Nothing to do
3135
    return prepareExchangedData(
3136
        array(
3137
            'error' => true,
3138
            'message' => $lang->get('error_no_user'),
3139
        ),
3140
        'encode'
3141
    );
3142
}
3143
3144
3145
function getUserInfo(
3146
    int $post_user_id,
3147
    array $SETTINGS
3148
)
3149
{
3150
    // Load user's language
3151
    $session = SessionManager::getSession();
3152
    $lang = new Language($session->get('user-language') ?? 'english');
3153
    
3154
    if (isUserIdValid($post_user_id) === true) {
3155
        // Get user info
3156
        $userData = DB::queryFirstRow(
3157
            'SELECT special, auth_type, is_ready_for_usage, ongoing_process_id, otp_provided, keys_recovery_time
3158
            FROM ' . prefixTable('users') . '
3159
            WHERE id = %i',
3160
            $post_user_id
3161
        );
3162
        if (DB::count() > 0) {
3163
            return prepareExchangedData(
3164
                array(
3165
                    'error' => false,
3166
                    'message' => '',
3167
                    'queryResults' => $userData,
3168
                ),
3169
                'encode'
3170
            );
3171
        }
3172
    }
3173
    return prepareExchangedData(
3174
        array(
3175
            'error' => true,
3176
            'message' => $lang->get('error_no_user'),
3177
        ),
3178
        'encode'
3179
    );
3180
}
3181
3182
/**
3183
 * Change user auth password
3184
 *
3185
 * @param integer $post_user_id
3186
 * @param string $post_current_pwd
3187
 * @param string $post_new_pwd
3188
 * @param array $SETTINGS
3189
 * @return string
3190
 */
3191
function changeUserAuthenticationPassword(
3192
    int $post_user_id,
3193
    string $post_current_pwd,
3194
    string $post_new_pwd,
3195
    array $SETTINGS
3196
)
3197
{
3198
    $session = SessionManager::getSession();
3199
    $lang = new Language($session->get('user-language') ?? 'english');
3200
 
3201
    if (isUserIdValid($post_user_id) === true) {
3202
        // Get user info
3203
        $userData = DB::queryFirstRow(
3204
            'SELECT auth_type, login, private_key
3205
            FROM ' . prefixTable('users') . '
3206
            WHERE id = %i',
3207
            $post_user_id
3208
        );
3209
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
3210
            // Now check if current password is correct
3211
            // For this, just check if it is possible to decrypt the privatekey
3212
            // And compare it to the one in session
3213
            try {
3214
                $privateKey = decryptPrivateKey($post_current_pwd, $userData['private_key']);
3215
            } catch (Exception $e) {
3216
                return prepareExchangedData(
3217
                    array(
3218
                        'error' => true,
3219
                        'message' => $lang->get('bad_password'),
3220
                    ),
3221
                    'encode'
3222
                );
3223
            }
3224
3225
            $lang = new Language($session->get('user-language') ?? 'english');
3226
3227
            if ($session->get('user-private_key') === $privateKey) {
3228
                // Encrypt it with new password
3229
                $hashedPrivateKey = encryptPrivateKey($post_new_pwd, $privateKey);
3230
3231
                // Generate new hash for auth password
3232
                $passwordManager = new PasswordManager();
3233
3234
                // Prepare variables
3235
                $newPw = $passwordManager->hashPassword($post_new_pwd);
3236
3237
                // Update user account
3238
                DB::update(
3239
                    prefixTable('users'),
3240
                    array(
3241
                        'private_key' => $hashedPrivateKey,
3242
                        'pw' => $newPw,
3243
                        'special' => 'none',
3244
                        'last_pw_change' => time(),
3245
                    ),
3246
                    'id = %i',
3247
                    $post_user_id
3248
                );
3249
3250
                $session->set('user-private_key', $privateKey);
3251
3252
                return prepareExchangedData(
3253
                    array(
3254
                        'error' => false,
3255
                        'message' => $lang->get('done'),'',
3256
                    ),
3257
                    'encode'
3258
                );
3259
            }
3260
            
3261
            // ERROR
3262
            return prepareExchangedData(
3263
                array(
3264
                    'error' => true,
3265
                    'message' => $lang->get('bad_password'),
3266
                ),
3267
                'encode'
3268
            );
3269
        }
3270
    }
3271
        
3272
    return prepareExchangedData(
3273
        array(
3274
            'error' => true,
3275
            'message' => $lang->get('error_no_user'),
3276
        ),
3277
        'encode'
3278
    );
3279
}
3280
3281
/**
3282
 * Change user LDAP auth password
3283
 *
3284
 * @param integer $post_user_id
3285
 * @param string $post_previous_pwd
3286
 * @param string $post_current_pwd
3287
 * @return string
3288
 */            
3289
function changeUserLDAPAuthenticationPassword(
3290
    int $post_user_id,
3291
    string $post_previous_pwd,
3292
    string $post_current_pwd
3293
)
3294
{
3295
    $session = SessionManager::getSession();
3296
    // Load user's language
3297
    $lang = new Language($session->get('user-language') ?? 'english');
3298
    
3299
    if (isUserIdValid($post_user_id) === true) {
3300
        // Get user info
3301
        $userData = DB::queryFirstRow(
3302
            'SELECT auth_type, login, private_key, special
3303
            FROM ' . prefixTable('users') . '
3304
            WHERE id = %i',
3305
            $post_user_id
3306
        );
3307
3308
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
3309
            // Now check if current password is correct (only if not ldap)
3310
            if ($userData['auth_type'] === 'ldap' && $userData['special'] === 'auth-pwd-change') {
3311
                // As it is a change for an LDAP user
3312
                
3313
                // Now check if current password is correct
3314
                // For this, just check if it is possible to decrypt the privatekey
3315
                // And compare it to the one in session
3316
                $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3317
3318
                // Encrypt it with new password
3319
                $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3320
3321
                // Update user account
3322
                DB::update(
3323
                    prefixTable('users'),
3324
                    array(
3325
                        'private_key' => $hashedPrivateKey,
3326
                        'special' => 'none',
3327
                    ),
3328
                    'id = %i',
3329
                    $post_user_id
3330
                );
3331
3332
                $session->set('user-private_key', $privateKey);
3333
3334
                return prepareExchangedData(
3335
                    array(
3336
                        'error' => false,
3337
                        'message' => $lang->get('done'),'',
3338
                    ),
3339
                    'encode'
3340
                );
3341
            }
3342
3343
            // For this, just check if it is possible to decrypt the privatekey
3344
            // And try to decrypt one existing key
3345
            $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3346
3347
            if (empty($privateKey) === true) {
3348
                return prepareExchangedData(
3349
                    array(
3350
                        'error' => true,
3351
                        'message' => $lang->get('password_is_not_correct'),
3352
                    ),
3353
                    'encode'
3354
                );
3355
            }
3356
            // Get one itemKey from current user
3357
            $currentUserKey = DB::queryFirstRow(
3358
                'SELECT ski.share_key, ski.increment_id, l.id_user
3359
                FROM ' . prefixTable('sharekeys_items') . ' AS ski
3360
                INNER JOIN ' . prefixTable('log_items') . ' AS l ON ski.object_id = l.id_item
3361
                WHERE ski.user_id = %i
3362
                ORDER BY RAND()
3363
                LIMIT 1',
3364
                $post_user_id
3365
            );
3366
3367
            if (is_countable($currentUserKey) && count($currentUserKey) > 0) {
3368
                // Decrypt itemkey with user key
3369
                // use old password to decrypt private_key
3370
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $privateKey);
3371
                
3372
                if (empty(base64_decode($itemKey)) === false) {
3373
                    // GOOD password
3374
                    // Encrypt it with current password
3375
                    $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3376
                    
3377
                    // Update user account
3378
                    DB::update(
3379
                        prefixTable('users'),
3380
                        array(
3381
                            'private_key' => $hashedPrivateKey,
3382
                            'special' => 'none',
3383
                        ),
3384
                        'id = %i',
3385
                        $post_user_id
3386
                    );
3387
                    
3388
                    $lang = new Language($session->get('user-language') ?? 'english');
3389
                    $session->set('user-private_key', $privateKey);
3390
3391
                    return prepareExchangedData(
3392
                        array(
3393
                            'error' => false,
3394
                            'message' => $lang->get('done'),
3395
                        ),
3396
                        'encode'
3397
                    );
3398
                }
3399
            }
3400
            
3401
            // ERROR
3402
            return prepareExchangedData(
3403
                array(
3404
                    'error' => true,
3405
                    'message' => $lang->get('bad_password'),
3406
                ),
3407
                'encode'
3408
            );
3409
        }
3410
    }
3411
3412
    // ERROR
3413
    return prepareExchangedData(
3414
        array(
3415
            'error' => true,
3416
            'message' => $lang->get('error_no_user'),
3417
        ),
3418
        'encode'
3419
    );
3420
}
3421
3422
/**
3423
 * Change user LDAP auth password
3424
 *
3425
 * @param integer $post_user_id
3426
 * @param string $post_current_pwd
3427
 * @param string $post_new_pwd
3428
 * @param array $SETTINGS
3429
 * @return string
3430
 */
3431
function increaseSessionDuration(
3432
    int $duration
3433
): string
3434
{
3435
    $session = SessionManager::getSession();
3436
    // check if session is not already expired.
3437
    if ($session->get('user-session_duration') > time()) {
3438
        // Calculate end of session
3439
        $session->set('user-session_duration', (int) $session->get('user-session_duration') + $duration);
3440
        // Update table
3441
        DB::update(
3442
            prefixTable('users'),
3443
            array(
3444
                'session_end' => $session->get('user-session_duration'),
3445
            ),
3446
            'id = %i',
3447
            $session->get('user-id')
3448
        );
3449
        // Return data
3450
        return '[{"new_value":"' . $session->get('user-session_duration') . '"}]';
3451
    }
3452
    
3453
    return '[{"new_value":"expired"}]';
3454
}
3455
3456
function generateAnOTP(string $label, bool $with_qrcode = false, string $secretKey = ''): string
3457
{
3458
    // generate new secret
3459
    $tfa = new TwoFactorAuth();
3460
    if ($secretKey === '') {
3461
        $secretKey = $tfa->createSecret();
3462
    }
3463
3464
    // generate new QR
3465
    if ($with_qrcode === true) {
3466
        $qrcode = $tfa->getQRCodeImageAsDataUri(
3467
            $label,
3468
            $secretKey
3469
        );
3470
    }
3471
3472
    // ERROR
3473
    return prepareExchangedData(
3474
        array(
3475
            'error' => false,
3476
            'message' => '',
3477
            'secret' => $secretKey,
3478
            'qrcode' => $qrcode ?? '',
3479
        ),
3480
        'encode'
3481
    );
3482
}