Passed
Pull Request — master (#4551)
by Nils
05:36
created

handleGenerateQRCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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