Passed
Pull Request — master (#4551)
by Nils
06:28
created

handleUserIsReady()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.queries.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-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
    $request = SymfonyRequest::createFromGlobals();
140
141
    // Prepare POST variables
142
    $inputData = dataSanitizer(
143
        [
144
            'type' => $request->request->filter('type', '', FILTER_SANITIZE_SPECIAL_CHARS),
145
            'data' => $request->request->filter('data', '', FILTER_SANITIZE_SPECIAL_CHARS),
146
            'key' => $request->request->filter('key', '', FILTER_SANITIZE_SPECIAL_CHARS),
147
            'type_category' => $request->request->filter('type_category', '', FILTER_SANITIZE_SPECIAL_CHARS),
148
        ],
149
        [
150
            'type' => 'trim|escape',
151
            'data' => 'trim|escape',
152
            'key' => 'trim|escape',
153
            'type_category' => 'trim|escape',
154
        ]
155
    );
156
    
157
    // Check KEY
158
    if (isValueSetNullEmpty($inputData['key']) === true) {
159
        echo prepareExchangedData(
160
            array(
161
                'error' => true,
162
                'message' => $lang->get('key_is_not_correct'),
163
            ),
164
            'encode',
165
            $inputData['key']
166
        );
167
        return false;
168
    }
169
    // decrypt and retreive data in JSON format
170
    $dataReceived = empty($inputData['data']) === false ? prepareExchangedData(
171
        $inputData['data'],
172
        'decode'
173
    ) : '';
174
    
175
    switch ($inputData['type_category']) {
176
        case 'action_password':
177
            echo passwordHandler($inputData['type'], $dataReceived, $SETTINGS);
178
            break;
179
180
        case 'action_user':
181
            echo userHandler($inputData['type'], $dataReceived, $SETTINGS, $inputData['key']);
182
            break;
183
184
        case 'action_mail':
185
            echo mailHandler($inputData['type'], $dataReceived, $SETTINGS);
186
            break;
187
188
        case 'action_key':
189
            // deepcode ignore ServerLeak: All cases handled by keyHandler return an encrypted string that is sent back to the client
190
            echo keyHandler($inputData['type'], $dataReceived, $SETTINGS);
191
            break;
192
193
        case 'action_system':
194
            echo systemHandler($inputData['type'], $dataReceived, $SETTINGS);
195
            break;
196
197
        case 'action_utils':
198
            echo utilsHandler($inputData['type'], $dataReceived, $SETTINGS);
199
            break;
200
    }
201
    
202
}
203
204
/**
205
 * Handler for all password tasks
206
 *
207
 * @param string $post_type
208
 * @param array|null|string $dataReceived
209
 * @param array $SETTINGS
210
 * @return string
211
 */
212
function passwordHandler(string $post_type, /*php8 array|null|string*/ $dataReceived, array $SETTINGS): string
213
{
214
    $session = SessionManager::getSession();
215
    $lang = new Language($session->get('user-language') ?? 'english');
216
217
    switch ($post_type) {
218
        case 'change_pw'://action_password
219
            return changePassword(
220
                (string) filter_var($dataReceived['new_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
221
                isset($dataReceived['current_pw']) === true ? (string) filter_var($dataReceived['current_pw'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '',
222
                (int) filter_var($dataReceived['complexity'], FILTER_SANITIZE_NUMBER_INT),
223
                (string) filter_var($dataReceived['change_request'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
224
                (int) $session->get('user-id'),
225
                $SETTINGS
226
            );
227
228
        /*
229
         * Change user's authentication password
230
         */
231
        case 'change_user_auth_password'://action_password
232
233
            // Check new password and confirm match server side
234
            if ($dataReceived['new_password'] !== $dataReceived['new_password_confirm']) {
235
                return prepareExchangedData(
236
                    array(
237
                        'error' => true,
238
                        'message' => $lang->get('error_bad_credentials'),
239
                    ),
240
                    'encode'
241
                );
242
            }
243
244
            // Check if new password is strong
245
            if (!isPasswordStrong($dataReceived['new_password'])) {
246
                return prepareExchangedData(
247
                    array(
248
                        'error' => true,
249
                        'message' => $lang->get('complexity_level_not_reached'),
250
                    ),
251
                    'encode'
252
                );
253
            }
254
255
            return changeUserAuthenticationPassword(
256
                (int) $session->get('user-id'),
257
                (string) filter_var($dataReceived['old_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
258
                (string) filter_var($dataReceived['new_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
259
                $SETTINGS
260
            );
261
262
        /*
263
         * User's authentication password in LDAP has changed
264
         */
265
        case 'change_user_ldap_auth_password'://action_password
266
267
            // Users passwords are html escaped
268
            $userPassword = filter_var($dataReceived['current_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
269
270
            // Get current user hash
271
            $userHash = DB::queryFirstRow(
272
                "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
273
                $session->get('user-id')
274
            )['pw'];
275
276
            $passwordManager = new PasswordManager();
277
278
            // Verify provided user password
279
            if (!$passwordManager->verifyPassword($userHash, $userPassword)) {
280
                return prepareExchangedData(
281
                    array(
282
                        'error' => true,
283
                        'message' => $lang->get('error_bad_credentials'),
284
                    ),
285
                    'encode'
286
                );
287
            }
288
289
            return /** @scrutinizer ignore-call */ changeUserLDAPAuthenticationPassword(
290
                (int) $session->get('user-id'),
291
                filter_var($dataReceived['previous_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
292
                filter_var($userPassword),
293
                $SETTINGS
294
            );
295
296
        /*
297
         * test_current_user_password_is_correct
298
         */
299
        case 'test_current_user_password_is_correct'://action_password
300
            return isUserPasswordCorrect(
301
                (int) $session->get('user-id'),
302
                (string) $dataReceived['password'],
303
                $SETTINGS
304
            );
305
306
        /*
307
         * Default case
308
         */
309
        default :
310
            return prepareExchangedData(
311
                array(
312
                    'error' => true,
313
                ),
314
                'encode'
315
            );
316
    }
317
}
318
319
/**
320
 * Handler for all user tasks
321
 *
322
 * @param string $post_type       Type of action to handle.
323
 * @param array|null|string $dataReceived Data received from the request.
324
 * @param array $SETTINGS         Global settings.
325
 * @param string $post_key        Additional key for post validation.
326
 * @return string
327
 */
328
function userHandler(string $post_type, array|null|string $dataReceived, array $SETTINGS, string $post_key): string
329
{
330
    $session = SessionManager::getSession();
331
332
    // Vérifier si l'utilisateur a les permissions nécessaires
333
    if (!userHandlerHasPermission($post_type, $dataReceived, $session)) {
334
        return prepareExchangedData(['error' => true], 'encode');
335
    }
336
337
    // Map des gestionnaires par post_type
338
    $handlers = [
339
        'get_user_info' => 'handleGetUserInfo',
340
        'increase_session_time' => 'handleIncreaseSessionTime',
341
        'generate_password' => 'handleGeneratePassword',
342
        'refresh_list_items_seen' => 'handleRefreshListItemsSeen',
343
        'ga_generate_qr' => 'handleGenerateQRCode',
344
        'user_is_ready' => 'handleUserIsReady',
345
        'user_get_session_time' => 'handleUserGetSessionTime',
346
        'save_user_location' => 'handleSaveUserLocation',
347
    ];
348
349
    // Appeler la fonction correspondante ou retourner une erreur par défaut
350
    if (isset($handlers[$post_type]) && function_exists($handlers[$post_type])) {
351
        return $handlers[$post_type]($dataReceived, $session, $SETTINGS, $post_key);
352
    }
353
354
    // Cas par défaut si le post_type est invalide
355
    return prepareExchangedData(['error' => true], 'encode');
356
}
357
358
/**
359
 * Handler for getting user information.
360
 * 
361
 * @param array $dataReceived The data received from the client.
362
 * @param object $session The current session data.
363
 * @param array $SETTINGS The application settings.
364
 * @param string $post_key The additional key for post validation.
365
 * @return string The user information.
366
 */
367
function handleGetUserInfo($dataReceived, $session, $SETTINGS, $post_key): string
368
{
369
    $userId = (int) $session->get('user-id');
370
    return getUserInfo($userId, $SETTINGS);
371
}
372
373
/**
374
 * Handler for increasing the session time.
375
 * 
376
 * @param array $dataReceived The data received from the client.
377
 * @param object $session The current session data.
378
 * @param array $SETTINGS The application settings.
379
 * @param string $post_key The additional key for post validation.
380
 * @return string The new session time.
381
 */
382
function handleIncreaseSessionTime($dataReceived, $session, $SETTINGS, $post_key): string
383
{
384
    $duration = (int) filter_input(INPUT_POST, 'duration', FILTER_SANITIZE_NUMBER_INT);
385
    return increaseSessionDuration($duration);
386
}
387
388
/**
389
 * Handler for generating a password.
390
 * 
391
 * @param array $dataReceived The data received from the client.
392
 * @param object $session The current session data.
393
 * @param array $SETTINGS The application settings.
394
 * @param string $post_key The additional key for post validation.
395
 * @return string The generated password.
396
 */
397
function handleGeneratePassword($dataReceived, $session, $SETTINGS, $post_key): string
398
{
399
    $validatedData = validateHandlerData($dataReceived, [
400
        'size' => FILTER_SANITIZE_NUMBER_INT,
401
        'secure_pwd' => FILTER_VALIDATE_BOOLEAN,
402
        'lowercase' => FILTER_VALIDATE_BOOLEAN,
403
        'capitalize' => FILTER_VALIDATE_BOOLEAN,
404
        'numerals' => FILTER_VALIDATE_BOOLEAN,
405
        'symbols' => FILTER_VALIDATE_BOOLEAN,
406
    ]);
407
408
    return generateGenericPassword(
409
        $validatedData['size'],
410
        $validatedData['secure_pwd'],
411
        $validatedData['lowercase'],
412
        $validatedData['capitalize'],
413
        $validatedData['numerals'],
414
        $validatedData['symbols'],
415
        $SETTINGS
416
    );
417
}
418
419
/**
420
 * Handler for refreshing the list of items seen by the user.
421
 * 
422
 * @param array $dataReceived The data received from the client.
423
 * @param object $session The current session data.
424
 * @param array $SETTINGS The application settings.
425
 * @param string $post_key The additional key for post validation.
426
 * @return string The refreshed list of items seen by the user.
427
 */
428
function handleRefreshListItemsSeen($dataReceived, $session, $SETTINGS, $post_key): string
429
{
430
    if ($session->has('user-id') && (int) $session->get('user-id') > 0) {
431
        return refreshUserItemsSeenList($SETTINGS);
432
    }
433
434
    return json_encode(
435
        ['error' => '', 'existing_suggestions' => 0, 'html_json' => ''],
436
        JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
437
    );
438
}
439
440
/**
441
 * Handler for generating a QR code for Google Authenticator.
442
 * 
443
 * @param array $dataReceived The data received from the client.
444
 * @param object $session The current session data.
445
 * @param array $SETTINGS The application settings.
446
 * @param string $post_key The additional key for post validation.
447
 * @return string The generated QR code.
448
 */
449
function handleGenerateQRCode($dataReceived, $session, $SETTINGS, $post_key): string
450
{
451
    $validatedData = validateHandlerData($dataReceived, [
452
        'demand_origin' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
453
        'send_email' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
454
        'login' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
455
        'pwd' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
456
        'token' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
457
    ]);
458
459
    return generateQRCode(
460
        (int) $session->get('user-id'),
461
        $validatedData['demand_origin'],
462
        $validatedData['send_email'],
463
        $validatedData['login'],
464
        $validatedData['pwd'],
465
        $validatedData['token'],
466
        $SETTINGS
467
    );
468
}
469
470
/**
471
 * Handler for checking if the user is ready.
472
 * 
473
 * @param array $dataReceived The data received from the client.
474
 * @param object $session The current session data.
475
 * @param array $SETTINGS The application settings.
476
 * @param string $post_key The additional key for post validation.
477
 * @return string The user's readiness status.
478
 */
479
function handleUserIsReady($dataReceived, $session, $SETTINGS, $post_key): string
480
{
481
    $userId = (int) $session->get('user-id');
482
    return userIsReady($userId, $SETTINGS['cpassman_dir']);
483
}
484
485
/**
486
 * Handler for getting the user's session time.
487
 * 
488
 * @param array $dataReceived The data received from the client.
489
 * @param object $session The current session data.
490
 * @param array $SETTINGS The application settings.
491
 * @param string $post_key The additional key for post validation.
492
 * @return string The user's session time.
493
 */
494
function handleUserGetSessionTime($dataReceived, $session, $SETTINGS, $post_key): string
495
{
496
    return userGetSessionTime(
497
        (int) $session->get('user-id'),
498
        $SETTINGS['cpassman_dir'],
499
        (int) $SETTINGS['maximum_session_expiration_time']
500
    );
501
}
502
503
/**
504
 * Handler for saving the user's location.
505
 * 
506
 * @param array $dataReceived The data received from the client.
507
 * @param object $session The current session data.
508
 * @param array $SETTINGS The application settings.
509
 * @param string $post_key The additional key for post validation.
510
 * @return string The user's location.
511
 */
512
function handleSaveUserLocation($dataReceived, $session, $SETTINGS, $post_key): string
513
{
514
    return userSaveIp(
515
        (int) $session->get('user-id'),
516
        (string) filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
517
    );
518
}
519
520
/**
521
 * Handler for all system tasks.
522
 *
523
 * @param string $post_type
524
 * @param array|null|string $dataReceived
525
 * @param array $SETTINGS
526
 * @return string
527
 */
528
function userHandlerHasPermission(string $post_type, $dataReceived, $session): bool
529
{
530
    $all_users_can_access = [
531
        'get_user_info',
532
        'increase_session_time',
533
        'generate_password',
534
        'refresh_list_items_seen',
535
        'ga_generate_qr',
536
        'user_get_session_time',
537
        'save_user_location',
538
    ];
539
540
    if (in_array($post_type, $all_users_can_access)) {
541
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type string.
Loading history...
542
    }
543
544
    return (int) $session->get('user-admin') === 1
0 ignored issues
show
Bug Best Practice introduced by
The expression return (int)$session->ge...anage_all_users') === 1 returns the type boolean which is incompatible with the documented return type string.
Loading history...
545
        || (int) $session->get('user-manager') === 1
546
        || (int) $session->get('user-can_manage_all_users') === 1;
547
}
548
549
/**
550
 * Handler for validating data received from the client.
551
 * 
552
 * @param array $data The data received from the client.
553
 * @param array $rules The rules to apply to the data.
554
 * @return array The validated data.
555
 */
556
function validateHandlerData(array $data, array $rules): array
557
{
558
    $validated = [];
559
    foreach ($rules as $key => $filter) {
560
        $validated[$key] = filter_var($data[$key] ?? null, $filter);
561
    }
562
    return $validated;
563
}
564
565
566
567
/**
568
 * Handler for all mail tasks
569
 *
570
 * @param string $post_type
571
 * @param array|null|string $dataReceived
572
 * @param array $SETTINGS
573
 * @return string
574
 */
575
function mailHandler(string $post_type, /*php8 array|null|string */$dataReceived, array $SETTINGS): string
576
{
577
    $session = SessionManager::getSession();
578
579
    switch ($post_type) {
580
        /*
581
         * CASE
582
         * Send email
583
         */
584
        case 'mail_me'://action_mail
585
            // Get info about user to send email
586
            $data_user = DB::queryfirstrow(
587
                'SELECT admin, gestionnaire, can_manage_all_users, isAdministratedByRole FROM ' . prefixTable('users') . '
588
                WHERE email = %s',
589
                filter_var($dataReceived['receipt'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
590
            );
591
592
            // Unknown email address
593
            if (!$data_user) {
594
                return prepareExchangedData(
595
                    array(
596
                        'error' => true,
597
                    ),
598
                    'encode'
599
                );
600
            }
601
602
            // Only administrators and managers can send mails
603
            if (
604
                // Administrator user
605
                (int) $session->get('user-admin') === 1
606
                // Manager of basic/ro users in this role
607
                || ((int) $session->get('user-manager') === 1
608
                    && in_array($data_user['isAdministratedByRole'], $session->get('user-roles_array'))
609
                    && (int) $data_user['admin'] !== 1
610
                    && (int) $data_user['can_manage_all_users'] !== 1
611
                    && (int) $data_user['gestionnaire'] !== 1)
612
                // Manager of all basic/ro users
613
                || ((int) $session->get('user-can_manage_all_users') === 1
614
                    && (int) $data_user['admin'] !== 1
615
                    && (int) $data_user['can_manage_all_users'] !== 1
616
                    && (int) $data_user['gestionnaire'] !== 1)
617
            ) {
618
                return sendMailToUser(
619
                    filter_var($dataReceived['receipt'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
620
                    $dataReceived['body'],
621
                    (string) filter_var($dataReceived['subject'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
622
                    (array) filter_var_array(
623
                        $dataReceived['pre_replace'],
624
                        FILTER_SANITIZE_FULL_SPECIAL_CHARS
625
                    ),
626
                    true
627
                );
628
            }
629
630
            return prepareExchangedData(
631
                array(
632
                    'error' => true,
633
                ),
634
                'encode'
635
            );
636
        /*
637
        * Send emails not sent
638
        */
639
        case 'send_waiting_emails'://mail
640
            // Administrative task
641
            if ((int) $session->get('user-admin') !== 1) {
642
                return prepareExchangedData(
643
                    array(
644
                        'error' => true,
645
                    ),
646
                    'encode'
647
                );
648
            }
649
650
            sendEmailsNotSent(
651
                $SETTINGS
652
            );
653
            return prepareExchangedData(
654
                array(
655
                    'error' => false,
656
                    'message' => 'mail_sent',
657
                ),
658
                'encode'
659
            );
660
661
        /*
662
        * Default case
663
        */
664
        default :
665
            return prepareExchangedData(
666
                array(
667
                    'error' => true,
668
                ),
669
                'encode'
670
            );
671
    }
672
}
673
674
/**
675
 * Handler for all key-related tasks.
676
 *
677
 * @param string $post_type       Type of action to handle.
678
 * @param array|null|string $dataReceived Data received from the request.
679
 * @param array $SETTINGS         Global settings.
680
 * @return string
681
 */
682
function keyHandler(string $post_type, $dataReceived, array $SETTINGS): string
683
{
684
    $session = SessionManager::getSession();
685
    $lang = new Language($session->get('user-language') ?? 'english');
686
687
    // Check user permissions
688
    if (!userHasPermission($post_type, $dataReceived, $session)) {
0 ignored issues
show
Bug introduced by
It seems like $dataReceived can also be of type string; however, parameter $dataReceived of userHasPermission() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

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