Issues (27)

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