Passed
Pull Request — master (#4405)
by
unknown
05:46
created

mailHandler()   C

Complexity

Conditions 14
Paths 5

Size

Total Lines 85
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 14
eloc 47
c 4
b 0
f 0
nc 5
nop 3
dl 0
loc 85
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.queries.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 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' => $request->request->get('type', '') !== '' ? htmlspecialchars($request->request->get('type')) : '',
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(isset($SETTINGS['timezone']) === true ? $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
140
    // Prepare post variables
141
    $post_key = filter_input(INPUT_POST, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
142
    $post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
143
    $post_type_category = filter_input(INPUT_POST, 'type_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
144
    $post_data = filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
145
146
    // Check KEY
147
    if (isValueSetNullEmpty($post_key) === true) {
148
        echo prepareExchangedData(
149
            array(
150
                'error' => true,
151
                'message' => $lang->get('key_is_not_correct'),
152
            ),
153
            'encode',
154
            $post_key
155
        );
156
        return false;
157
    }
158
    // decrypt and retreive data in JSON format
159
    $dataReceived = empty($post_data) === false ? prepareExchangedData(
160
        $post_data,
161
        'decode'
162
    ) : '';
163
    
164
    switch ($post_type_category) {
165
        case 'action_password':
166
            echo passwordHandler($post_type, $dataReceived, $SETTINGS);
167
            break;
168
169
        case 'action_user':
170
            echo userHandler($post_type, $dataReceived, $SETTINGS, $post_key);
171
            break;
172
173
        case 'action_mail':
174
            echo mailHandler($post_type, $dataReceived, $SETTINGS);
175
            break;
176
177
        case 'action_key':
178
            // deepcode ignore ServerLeak: All cases handled by keyHandler return an encrypted string that is sent back to the client
179
            echo keyHandler($post_type, $dataReceived, $SETTINGS);
180
            break;
181
182
        case 'action_system':
183
            echo systemHandler($post_type, $dataReceived, $SETTINGS);
184
            break;
185
186
        case 'action_utils':
187
            echo utilsHandler($post_type, $dataReceived, $SETTINGS);
188
            break;
189
    }
190
    
191
}
192
193
/**
194
 * Handler for all password tasks
195
 *
196
 * @param string $post_type
197
 * @param array|null|string $dataReceived
198
 * @param array $SETTINGS
199
 * @return string
200
 */
201
function passwordHandler(string $post_type, /*php8 array|null|string*/ $dataReceived, array $SETTINGS): string
202
{
203
    $session = SessionManager::getSession();
204
205
    switch ($post_type) {
206
        case 'change_pw'://action_password
207
            return changePassword(
208
                (string) filter_var($dataReceived['new_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
209
                isset($dataReceived['current_pw']) === true ? (string) filter_var($dataReceived['current_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '',
210
                (int) filter_var($dataReceived['complexity'], FILTER_SANITIZE_NUMBER_INT),
211
                (string) filter_var($dataReceived['change_request'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
212
                (int) $session->get('user-id'),
213
                $SETTINGS
214
            );
215
216
        /*
217
         * Change user's authentication password
218
         */
219
        case 'change_user_auth_password'://action_password
220
            return changeUserAuthenticationPassword(
221
                (int) $session->get('user-id'),
222
                (string) filter_var($dataReceived['old_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
223
                (string) filter_var($dataReceived['new_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
224
                $SETTINGS
225
            );
226
227
        /*
228
         * User's authentication password in LDAP has changed
229
         */
230
        case 'change_user_ldap_auth_password'://action_password
231
            return /** @scrutinizer ignore-call */ changeUserLDAPAuthenticationPassword(
232
                (int) $session->get('user-id'),
233
                filter_var($dataReceived['previous_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
234
                filter_var($dataReceived['current_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
235
                $SETTINGS
236
            );
237
238
        /*
239
         * test_current_user_password_is_correct
240
         */
241
        case 'test_current_user_password_is_correct'://action_password
242
            return isUserPasswordCorrect(
243
                (int) $session->get('user-id'),
244
                (string) $dataReceived['password'],
245
                $SETTINGS
246
            );
247
248
        /*
249
         * Default case
250
         */
251
        default :
252
            return prepareExchangedData(
253
                array(
254
                    'error' => true,
255
                ),
256
                'encode'
257
            );
258
    }
259
}
260
261
/**
262
 * Handler for all user tasks
263
 *
264
 * @param string $post_type
265
 * @param array|null|string $dataReceived
266
 * @param array $SETTINGS
267
 * @param string $post_key
268
 * @return string
269
 */
270
function userHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS, string $post_key): string
271
{
272
    $session = SessionManager::getSession();
273
274
    // List of post types allowed to all users
275
    $all_users_can_access = [
276
        'get_user_info',
277
        'increase_session_time',
278
        'generate_password',
279
        'refresh_list_items_seen',
280
        'ga_generate_qr',
281
        'user_get_session_time',
282
        'save_user_location'
283
    ];
284
285
    // Default values
286
    $filtered_user_id = $session->get('user-id');
287
288
    // User can't manage users and requested type is administrative.
289
    if ((int) $session->get('user-admin') !== 1 &&
290
        (int) $session->get('user-manager') !== 1 &&
291
        (int) $session->get('user-can_manage_all_users') !== 1 &&
292
        !in_array($post_type, $all_users_can_access)) {
293
294
        echo prepareExchangedData(
295
            array(
296
                'error' => true,
297
            ),
298
            'encode'
299
        );
300
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
301
    }
302
303
    if (isset($dataReceived['user_id'])) {
304
        // Get info about user to modify
305
        $targetUserInfos = DB::queryfirstrow(
306
            'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
307
            WHERE id = %i',
308
            $dataReceived['user_id']
309
        );
310
311
        if (
312
            // Administrator user
313
            (int) $session->get('user-admin') === 1
314
            // Manager of basic/ro users in this role
315
            || ((int) $session->get('user-manager') === 1
316
                && in_array($targetUserInfos['isAdministratedByRole'], $session->get('user-roles_array'))
317
                && (int) $targetUserInfos['admin'] !== 1
318
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
319
                && (int) $targetUserInfos['gestionnaire'] !== 1)
320
            // Manager of all basic/ro users
321
            || ((int) $session->get('user-can_manage_all_users') === 1
322
                && (int) $targetUserInfos['admin'] !== 1
323
                && (int) $targetUserInfos['can_manage_all_users'] !== 1
324
                && (int) $targetUserInfos['gestionnaire'] !== 1)
325
        ) {
326
            // This user is allowed to modify other users.
327
            $filtered_user_id = $dataReceived['user_id'];
328
        }
329
    }
330
331
    switch ($post_type) {
332
        /*
333
        * Get info 
334
        */
335
        case 'get_user_info'://action_user
336
            return getUserInfo(
337
                (int) $filtered_user_id,
338
                (string) filter_var($dataReceived['fields'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
339
                $SETTINGS
340
            );
341
342
        /*
343
        * Increase the session time of User
344
        */
345
        case 'increase_session_time'://action_user
346
            return increaseSessionDuration(
347
                (int) filter_input(INPUT_POST, 'duration', FILTER_SANITIZE_NUMBER_INT)
348
            );
349
350
        /*
351
        * Generate a password generic
352
        */
353
        case 'generate_password'://action_user
354
            return generateGenericPassword(
355
                (int) filter_input(INPUT_POST, 'size', FILTER_SANITIZE_NUMBER_INT),
356
                (bool) filter_input(INPUT_POST, 'secure_pwd', FILTER_VALIDATE_BOOLEAN),
357
                (bool) filter_input(INPUT_POST, 'lowercase', FILTER_VALIDATE_BOOLEAN),
358
                (bool) filter_input(INPUT_POST, 'capitalize', FILTER_VALIDATE_BOOLEAN),
359
                (bool) filter_input(INPUT_POST, 'numerals', FILTER_VALIDATE_BOOLEAN),
360
                (bool) filter_input(INPUT_POST, 'symbols', FILTER_VALIDATE_BOOLEAN),
361
                $SETTINGS
362
            );
363
364
        /*
365
        * Refresh list of last items seen
366
        */
367
        case 'refresh_list_items_seen'://action_user
368
            if ($session->has('user-id') || (int) $session->get('user-id') && null !== $session->get('user-id') || (int) $session->get('user-id') > 0) {
369
                return refreshUserItemsSeenList(
370
                    $SETTINGS
371
                );
372
373
            } else {
374
                return json_encode(
375
                    array(
376
                        'error' => '',
377
                        'existing_suggestions' => 0,
378
                        'html_json' => '',
379
                    ),
380
                    JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
381
                );
382
            }
383
384
        /*
385
        * This will generate the QR Google Authenticator
386
        */
387
        case 'ga_generate_qr'://action_user
388
            return generateQRCode(
389
                (int) $filtered_user_id,
390
                (string) filter_var($dataReceived['demand_origin'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
391
                (string) filter_var($dataReceived['send_email'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
392
                (string) filter_var($dataReceived['login'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
393
                (string) filter_var($dataReceived['pwd'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
394
                (string) filter_var($dataReceived['token'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
395
                $SETTINGS,
396
                (string) $post_key
0 ignored issues
show
Unused Code introduced by
The call to generateQRCode() has too many arguments starting with (string)$post_key. ( Ignorable by Annotation )

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

396
            return /** @scrutinizer ignore-call */ generateQRCode(

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

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

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