Issues (29)

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

sources/main.queries.php (1 issue)

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