Issues (22)

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