Passed
Branch feature/code_clean (75ffa1)
by Nils
06:31
created

generateQRCode()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 53
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
nc 7
nop 7
dl 0
loc 53
rs 8.6506
c 1
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.queries.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use TeampassClasses\PasswordManager\PasswordManager;
33
use TeampassClasses\SessionManager\SessionManager;
34
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
35
use TeampassClasses\Language\Language;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use RobThree\Auth\TwoFactorAuth;
39
use EZimuel\PHPSecureSession;
40
use TeampassClasses\PerformChecks\PerformChecks;
41
use TeampassClasses\ConfigManager\ConfigManager;
42
use TeampassClasses\EmailService\EmailService;
43
use TeampassClasses\EmailService\EmailSettings;
44
45
// Load functions
46
require_once 'main.functions.php';
47
48
loadClasses('DB');
49
$session = SessionManager::getSession();
50
$request = SymfonyRequest::createFromGlobals();
51
$lang = new Language($session->get('user-language') ?? 'english');
52
53
// TODO : ajouter un check sue l'envoi de la key
54
55
// Load config
56
$configManager = new ConfigManager();
57
$SETTINGS = $configManager->getAllSettings();
58
59
// Do checks
60
// Instantiate the class with posted data
61
$checkUserAccess = new PerformChecks(
62
    dataSanitizer(
63
        [
64
            'type' => $request->request->get('type', '') !== '' ? htmlspecialchars($request->request->get('type')) : '',
65
        ],
66
        [
67
            'type' => 'trim|escape',
68
        ],
69
    ),
70
    [
71
        'user_id' => returnIfSet($session->get('user-id'), null),
72
        'user_key' => returnIfSet($session->get('key'), null),
73
    ]
74
);
75
// Handle the case
76
echo $checkUserAccess->caseHandler();
77
if (
78
    ($checkUserAccess->userAccessPage('home') === false ||
79
    $checkUserAccess->checkSession() === false)
80
    && in_array(filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS), ['get_teampass_settings', 'ga_generate_qr']) === false
81
) {
82
    // Not allowed page
83
    $session->set('system-error_code', ERR_NOT_ALLOWED);
84
    include $SETTINGS['cpassman_dir'] . '/error.php';
85
    exit;
86
}
87
88
// Define Timezone
89
date_default_timezone_set(isset($SETTINGS['timezone']) === true ? $SETTINGS['timezone'] : 'UTC');
90
set_time_limit(600);
91
92
// DO CHECKS
93
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
94
if (
95
    isset($post_type) === true
96
    && ($post_type === 'ga_generate_qr'
97
        || $post_type === 'get_teampass_settings')
98
) {
99
    // continue
100
    mainQuery($SETTINGS);
101
} elseif (
102
    $session->has('user-id') && null !== $session->get('user-id')
103
    && $checkUserAccess->userAccessPage('home') === false
104
) {
105
    $session->set('system-error_code', ERR_NOT_ALLOWED); //not allowed page
106
    include __DIR__.'/../error.php';
107
    exit();
108
} elseif (($session->has('user-id') && null !== $session->get('user-id')
109
        && $session->get('key') !== null)
110
    || (isset($post_type) === true
111
        && null !== filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES))
112
) {
113
    // continue
114
    mainQuery($SETTINGS);
115
} else {
116
    $session->set('system-error_code', ERR_NOT_ALLOWED); //not allowed page
117
    include __DIR__.'/../error.php';
118
    exit();
119
}
120
121
// Includes
122
include_once __DIR__.'/../sources/main.functions.php';
123
124
/**
125
 * Undocumented function.
126
 */
127
function mainQuery(array $SETTINGS)
128
{
129
    header('Content-type: text/html; charset=utf-8');
130
    header('Cache-Control: no-cache');
131
    error_reporting(E_ERROR);
132
133
    // Load libraries
134
    loadClasses('DB');
135
136
    // User's language loading
137
    $session = SessionManager::getSession();
138
    $lang = new Language($session->get('user-language') ?? 'english');
139
    $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
        (int) $validatedData['size'],
410
        (bool) $validatedData['secure_pwd'],
411
        (bool) $validatedData['lowercase'],
412
        (bool) $validatedData['capitalize'],
413
        (bool) $validatedData['numerals'],
414
        (bool) $validatedData['symbols'],
415
        (array) $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
/**
1350
 * Generates a QR code for the user.
1351
 *
1352
 * @param int $post_id The user's ID.
1353
 * @param string $post_demand_origin The origin of the request.
1354
 * @param int $post_send_mail Whether to send an email.
1355
 * @param string $post_login The user's login.
1356
 * @param string $post_pwd The user's password.
1357
 * @param string $post_token The user's token.
1358
 * @param array $SETTINGS The application settings.
1359
 * @return string The generated QR code.
1360
 */
1361
function generateQRCode(
1362
    $post_id,
1363
    $post_demand_origin,
1364
    $post_send_mail,
1365
    $post_login,
1366
    $post_pwd,
1367
    $post_token,
1368
    array $SETTINGS
1369
): string {
1370
    $session = SessionManager::getSession();
1371
    $lang = new Language($session->get('user-language') ?? 'english');
1372
1373
    // Validate settings and origin
1374
    if (!isQRCodeRequestAllowed($post_demand_origin, $SETTINGS)) {
1375
        return generateErrorResponse($lang->get('error_not_allowed_to'), '113');
1376
    }
1377
1378
    // Retrieve user data
1379
    $dataUser = fetchUserData($post_id, $post_login);
1380
    if (!$dataUser) {
1381
        logFailedAuth($SETTINGS, 'user_not_exists', $post_login);
1382
        return generateErrorResponse($lang->get('no_user'));
1383
    }
1384
1385
    // Validate password if required
1386
    if (!isPasswordValid($post_pwd, $dataUser['pw'], $post_demand_origin, $SETTINGS, $post_login)) {
1387
        return generateErrorResponse($lang->get('no_user'));
1388
    }
1389
1390
    if (empty($dataUser['email'])) {
1391
        return generateErrorResponse($lang->get('no_email_set'));
1392
    }
1393
1394
    // Check token usage
1395
    $tokenId = handleToken($post_token, $dataUser['id']);
1396
    if ($tokenId === false) {
1397
        return generateErrorResponse('TOKEN already used');
1398
    }
1399
1400
    // Generate and update user 2FA data
1401
    [$gaSecretKey, $gaTemporaryCode] = updateUserGAData($dataUser['id'], $SETTINGS);
1402
1403
    logEvents($SETTINGS, 'user_connection', 'at_2fa_google_code_send_by_email', (string)$dataUser['id'], $post_login);
1404
1405
    // Update token status
1406
    updateTokenStatus($tokenId);
1407
1408
    // Send email if required
1409
    if ((int) $post_send_mail === 1) {
1410
        return sendQRCodeEmail($gaTemporaryCode, $dataUser['email'], $lang);
1411
    }
1412
1413
    return generateSuccessResponse($dataUser['email'], $lang);
1414
}
1415
/**
1416
 * Validates whether the QR code request is allowed.
1417
 *
1418
 * @param string|null $post_demand_origin Origin of the request.
1419
 * @param array $SETTINGS Application settings.
1420
 * @return bool True if the request is allowed, false otherwise.
1421
 */
1422
function isQRCodeRequestAllowed(?string $post_demand_origin, array $SETTINGS): bool {
1423
    return !(isKeyExistingAndEqual('ga_reset_by_user', 0, $SETTINGS) &&
1424
        ($post_demand_origin !== 'users_management_list'));
1425
}
1426
1427
/**
1428
 * Fetches user data based on their ID or login.
1429
 *
1430
 * @param int|string|null $post_id User ID or null if not provided.
1431
 * @param string|null $post_login User login (updated if ID is used).
1432
 * @return array|null User data as an associative array or null if not found.
1433
 */
1434
function fetchUserData($post_id, ?string &$post_login): ?array {
1435
    if (isValueSetNullEmpty($post_id)) {
1436
        $dataUser = DB::queryfirstrow(
1437
            'SELECT id, email, pw FROM ' . prefixTable('users') . ' WHERE login = %s',
1438
            $post_login
1439
        );
1440
    } else {
1441
        $dataUser = DB::queryfirstrow(
1442
            'SELECT id, login, email, pw FROM ' . prefixTable('users') . ' WHERE id = %i',
1443
            $post_id
1444
        );
1445
        $post_login = $dataUser['login'];
1446
    }
1447
    return $dataUser;
1448
}
1449
1450
/**
1451
 * Validates the user's password.
1452
 *
1453
 * @param string|null $post_pwd Provided password.
1454
 * @param string $storedPwd Stored password hash.
1455
 * @param string|null $post_demand_origin Request origin.
1456
 * @param array $SETTINGS Application settings.
1457
 * @param string $post_login User login.
1458
 * @return bool True if the password is valid, false otherwise.
1459
 */
1460
function isPasswordValid(?string $post_pwd, string $storedPwd, ?string $post_demand_origin, array $SETTINGS, string $post_login): bool {
1461
    if (isSetArrayOfValues([$post_pwd, $storedPwd]) &&
1462
        !(new PasswordManager())->verifyPassword($storedPwd, $post_pwd) &&
0 ignored issues
show
Bug introduced by
It seems like $post_pwd can also be of type null; however, parameter $plainPassword of TeampassClasses\Password...nager::verifyPassword() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1462
        !(new PasswordManager())->verifyPassword($storedPwd, /** @scrutinizer ignore-type */ $post_pwd) &&
Loading history...
1463
        $post_demand_origin !== 'users_management_list') {
1464
        logFailedAuth($SETTINGS, 'password_is_not_correct', $post_login);
1465
        return false;
1466
    }
1467
    return true;
1468
}
1469
1470
/**
1471
 * Handles the token validation and storage process.
1472
 *
1473
 * @param string $post_token Provided token.
1474
 * @param int $userId User ID.
1475
 * 
1476
 * @return mixed Token ID if a new token is created, false if the token was already used.
1477
 */
1478
function handleToken(string $post_token, int $userId): mixed {
1479
    $dataToken = DB::queryfirstrow(
1480
        'SELECT end_timestamp, reason FROM ' . prefixTable('tokens') . ' WHERE token = %s AND user_id = %i',
1481
        $post_token,
1482
        $userId
1483
    );
1484
1485
    if (DB::count() > 0 && !is_null($dataToken['end_timestamp']) && $dataToken['reason'] === 'auth_qr_code') {
1486
        return false;
1487
    }
1488
1489
    if (DB::count() === 0) {
1490
        DB::insert(prefixTable('tokens'), [
1491
            'user_id' => $userId,
1492
            'token' => $post_token,
1493
            'reason' => 'auth_qr_code',
1494
            'creation_timestamp' => time(),
1495
        ]);
1496
        return DB::insertId();
1497
    }
1498
    return null;
1499
}
1500
1501
/**
1502
 * Generates and updates the user's Google Authenticator data.
1503
 *
1504
 * @param int $userId User ID.
1505
 * @param array $SETTINGS Application settings.
1506
 * @return array A tuple containing the secret key and temporary code.
1507
 */
1508
function updateUserGAData(int $userId, array $SETTINGS): array {
1509
    $tfa = new TwoFactorAuth($SETTINGS['ga_website_name']);
1510
    $gaSecretKey = $tfa->createSecret();
1511
    $gaTemporaryCode = GenerateCryptKey(12, false, true, true, false, true);
1512
1513
    DB::update(prefixTable('users'), [
1514
        'ga' => $gaSecretKey,
1515
        'ga_temporary_code' => $gaTemporaryCode,
1516
    ], 'id = %i', $userId);
1517
1518
    return [$gaSecretKey, $gaTemporaryCode];
1519
}
1520
1521
/**
1522
 * Updates the token status to mark it as used.
1523
 *
1524
 * @param int $tokenId Token ID.
1525
 * @return void
1526
 */
1527
function updateTokenStatus(int $tokenId): void {
1528
    DB::update(prefixTable('tokens'), [
1529
        'end_timestamp' => time(),
1530
    ], 'id = %i', $tokenId);
1531
}
1532
1533
/**
1534
 * Sends the QR code email with the temporary code.
1535
 *
1536
 * @param string $gaTemporaryCode Temporary Google Authenticator code.
1537
 * @param string $email User's email address.
1538
 * @param Language $lang Language object for messages.
1539
 * @return string JSON response indicating success.
1540
 */
1541
function sendQRCodeEmail(string $gaTemporaryCode, string $email, Language $lang): string {
1542
    prepareSendingEmail(
1543
        $lang->get('email_ga_subject'),
1544
        str_replace('#2FACode#', $gaTemporaryCode, $lang->get('email_ga_text')),
1545
        $email
1546
    );
1547
1548
    return generateSuccessResponse($email, $lang);
1549
}
1550
1551
/**
1552
 * Generates an error response in JSON format.
1553
 *
1554
 * @param string $message Error message.
1555
 * @param string $code Optional error code.
1556
 * @return string JSON response.
1557
 */
1558
function generateErrorResponse(string $message, string $code = ''): string {
1559
    return prepareExchangedData([
1560
        'error' => true,
1561
        'message' => $code . ' ' . $message,
1562
    ], 'encode');
1563
}
1564
1565
/**
1566
 * Generates a success response in JSON format.
1567
 *
1568
 * @param string $email User's email address.
1569
 * @param Language $lang Language object for messages.
1570
 * @return string JSON response.
1571
 */
1572
function generateSuccessResponse(string $email, Language $lang): string {
1573
    return prepareExchangedData([
1574
        'error' => false,
1575
        'message' => '',
1576
        'email' => $email,
1577
        'email_result' => str_replace(
1578
            '#email#',
1579
            '<b>' . obfuscateEmail($email) . '</b>',
1580
            addslashes($lang->get('admin_email_result_ok'))
1581
        ),
1582
    ], 'encode');
1583
}
1584
1585
/**
1586
 * Logs a failed authentication attempt for a user.
1587
 *
1588
 * @param array $SETTINGS Application settings.
1589
 * @param string $reason Reason for the failure.
1590
 * @param string|null $user_login The login of the user (if available).
1591
 * @return void
1592
 */
1593
function logFailedAuth(array $SETTINGS, string $reason, ?string $user_login): void {
1594
    logEvents(
1595
        $SETTINGS,
1596
        'user_connection',
1597
        $reason,
1598
        'null',
1599
        $user_login
1600
    );
1601
}
1602
1603
1604
/**
1605
 * Sends emails that have not been sent yet.
1606
 * 
1607
 * @param array $SETTINGS The application settings.
1608
 * @return void
1609
 */
1610
function sendEmailsNotSent(array $SETTINGS): void
1611
{
1612
    $emailSettings = new EmailSettings($SETTINGS);
1613
    $emailService = new EmailService();
1614
1615
    if (isKeyExistingAndEqual('enable_send_email_on_user_login', 1, $SETTINGS) === true) {
1616
        $row = DB::queryFirstRow(
1617
            'SELECT valeur FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s',
1618
            'cron',
1619
            'sending_emails'
1620
        );
1621
1622
        if ((int) (time() - $row['valeur']) >= 300 || (int) $row['valeur'] === 0) {
1623
            $rows = DB::query(
1624
                'SELECT *
1625
                FROM ' . prefixTable('emails') .
1626
                ' WHERE status != %s',
1627
                'sent'
1628
            );
1629
            foreach ($rows as $record) {
1630
                // Send email
1631
                $ret = json_decode(
1632
                    $emailService->sendMail(
1633
                        $record['subject'],
1634
                        $record['body'],
1635
                        $record['receivers'],
1636
                        $emailSettings
1637
                    ),
1638
                    true
1639
                );
1640
1641
                // update item_id in files table
1642
                DB::update(
1643
                    prefixTable('emails'),
1644
                    array(
1645
                        'status' => $ret['error'] === 'error_mail_not_send' ? 'not_sent' : 'sent',
1646
                    ),
1647
                    'timestamp = %s',
1648
                    $record['timestamp']
1649
                );
1650
            }
1651
        }
1652
        // update cron time
1653
        DB::update(
1654
            prefixTable('misc'),
1655
            array(
1656
                'valeur' => time(),
1657
                'updated_at' => time(),
1658
            ),
1659
            'intitule = %s AND type = %s',
1660
            'sending_emails',
1661
            'cron'
1662
        );
1663
    }
1664
}
1665
1666
1667
function refreshUserItemsSeenList(
1668
    array $SETTINGS
1669
): string
1670
{
1671
    $session = SessionManager::getSession();
1672
1673
    // get list of last items seen
1674
    $arr_html = array();
1675
    $rows = DB::query(
1676
        '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
1677
        FROM ' . prefixTable('log_items') . ' AS l
1678
        RIGHT JOIN ' . prefixTable('items') . ' AS i ON (l.id_item = i.id)
1679
        WHERE l.action = %s AND l.id_user = %i
1680
        ORDER BY l.date DESC
1681
        LIMIT 0, 100',
1682
        'at_shown',
1683
        $session->get('user-id')
1684
    );
1685
    if (DB::count() > 0) {
1686
        foreach ($rows as $record) {
1687
            if (in_array($record['id']->id, array_column($arr_html, 'id')) === false) {
1688
                array_push(
1689
                    $arr_html,
1690
                    array(
1691
                        'id' => $record['id'],
1692
                        'label' => htmlspecialchars(stripslashes(htmlspecialchars_decode($record['label'], ENT_QUOTES)), ENT_QUOTES),
1693
                        'tree_id' => $record['id_tree'],
1694
                        'perso' => $record['perso'],
1695
                        'restricted' => $record['restricted'],
1696
                    )
1697
                );
1698
                if (count($arr_html) >= (int) $SETTINGS['max_latest_items']) {
1699
                    break;
1700
                }
1701
            }
1702
        }
1703
    }
1704
1705
    // get wainting suggestions
1706
    $nb_suggestions_waiting = 0;
1707
    if (isKeyExistingAndEqual('enable_suggestion', 1, $SETTINGS) === true
1708
        && ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1)
1709
    ) {
1710
        DB::query('SELECT * FROM ' . prefixTable('suggestion'));
1711
        $nb_suggestions_waiting = DB::count();
1712
    }
1713
1714
    return json_encode(
1715
        array(
1716
            'error' => '',
1717
            'existing_suggestions' => $nb_suggestions_waiting,
1718
            'html_json' => $arr_html,
1719
        ),
1720
        JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1721
    );
1722
}
1723
1724
function sendingStatistics(
1725
    array $SETTINGS
1726
): void
1727
{
1728
    $session = SessionManager::getSession();
1729
    if (
1730
        isSetArrayOfValues([$SETTINGS['send_statistics_items'], $SETTINGS['send_stats_time']]) === true
1731
        && isKeyExistingAndEqual('send_stats', 1, $SETTINGS) === true
1732
        && (int) ($SETTINGS['send_stats_time'] + TP_ONE_DAY_SECONDS) > time()
1733
    ) {
1734
        // get statistics data
1735
        $stats_data = getStatisticsData($SETTINGS);
1736
1737
        // get statistics items to share
1738
        $statsToSend = [];
1739
        $statsToSend['ip'] = $_SERVER['SERVER_ADDR'];
1740
        $statsToSend['timestamp'] = time();
1741
        foreach (array_filter(explode(';', $SETTINGS['send_statistics_items'])) as $data) {
1742
            if ($data === 'stat_languages') {
1743
                $tmp = '';
1744
                foreach ($stats_data[$data] as $key => $value) {
1745
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1746
                }
1747
                $statsToSend[$data] = $tmp;
1748
            } elseif ($data === 'stat_country') {
1749
                $tmp = '';
1750
                foreach ($stats_data[$data] as $key => $value) {
1751
                    $tmp .= $tmp === '' ? $key . '-' . $value : ',' . $key . '-' . $value;
1752
                }
1753
                $statsToSend[$data] = $tmp;
1754
            } else {
1755
                $statsToSend[$data] = $stats_data[$data];
1756
            }
1757
        }
1758
1759
        // connect to Teampass Statistics database
1760
        $link2 = new MeekroDB(
1761
            'teampass.pw',
1762
            'teampass_user',
1763
            'ZMlEfRzKzFLZNzie',
1764
            'teampass_followup',
1765
            '3306',
1766
            'utf8'
1767
        );
1768
1769
        $link2->insert(
1770
            'statistics',
1771
            $statsToSend
1772
        );
1773
1774
        // update table misc with current timestamp
1775
        DB::update(
1776
            prefixTable('misc'),
1777
            array(
1778
                'valeur' => time(),
1779
                'updated_at' => time(),
1780
            ),
1781
            'type = %s AND intitule = %s',
1782
            'admin',
1783
            'send_stats_time'
1784
        );
1785
1786
        //permits to test only once by session
1787
        $session->set('system-send_stats_done', 1);
1788
        $SETTINGS['send_stats_time'] = time();
1789
    }
1790
}
1791
1792
function generateBugReport(
1793
    array $data,
1794
    array $SETTINGS
1795
): string
1796
{
1797
    $config_exclude_vars = array(
1798
        'bck_script_passkey',
1799
        'email_smtp_server',
1800
        'email_auth_username',
1801
        'email_auth_pwd',
1802
        'email_from',
1803
        'onthefly-restore-key',
1804
        'onthefly-backup-key',
1805
        'ldap_password',
1806
        'ldap_hosts',
1807
        'proxy_ip',
1808
        'ldap_bind_passwd',
1809
        'syslog_host',
1810
        'duo_akey',
1811
        'duo_ikey',
1812
        'duo_skey',
1813
        'duo_host',
1814
        'oauth2_client_id',
1815
        'oauth2_tenant_id',
1816
        'oauth2_client_secret',
1817
        'oauth2_client_token',
1818
        'oauth2_client_endpoint',
1819
    );
1820
1821
    // Load user's language
1822
    $session = SessionManager::getSession();
1823
    $lang = new Language($session->get('user-language') ?? 'english');
1824
1825
    // Read config file
1826
    $list_of_options = '';
1827
    $url_found = '';
1828
    $anonym_url = '';
1829
    $sortedSettings = $SETTINGS;
1830
    ksort($sortedSettings);
1831
1832
    foreach ($sortedSettings as $key => $value) {
1833
        // Identify url to anonymize it
1834
        if ($key === 'cpassman_url' && empty($url_found) === true) {
1835
            $url_found = $value;
1836
            if (empty($url_found) === false) {
1837
                $tmp = parse_url($url_found);
1838
                $anonym_url = $tmp['scheme'] . '://<anonym_url>' . (isset($tmp['path']) === true ? $tmp['path'] : '');
1839
                $value = $anonym_url;
1840
            } else {
1841
                $value = '';
1842
            }
1843
        }
1844
1845
        // Anonymize all urls
1846
        if (empty($anonym_url) === false) {
1847
            $value = str_replace($url_found, $anonym_url, (string) $value);
1848
        }
1849
1850
        // Clear some vars
1851
        foreach ($config_exclude_vars as $var) {
1852
            if ($key === $var) {
1853
                $value = '<removed>';
1854
            }
1855
        }
1856
1857
        // Complete line to display
1858
        $list_of_options .= "'$key' => '$value'\n";
1859
    }
1860
1861
    // Get error
1862
    $err = error_get_last();
1863
1864
    // Get 10 latest errors in Teampass
1865
    $teampass_errors = '';
1866
    $rows = DB::query(
1867
        'SELECT label, date AS error_date
1868
        FROM ' . prefixTable('log_system') . "
1869
        WHERE `type` LIKE 'error'
1870
        ORDER BY `date` DESC
1871
        LIMIT 0, 10"
1872
    );
1873
    if (DB::count() > 0) {
1874
        foreach ($rows as $record) {
1875
            if (empty($teampass_errors) === true) {
1876
                $teampass_errors = ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1877
            } else {
1878
                $teampass_errors .= ' * ' . date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['error_date']) . ' - ' . $record['label'];
1879
            }
1880
        }
1881
    }
1882
1883
    $link = mysqli_connect(DB_HOST, DB_USER, DB_PASSWD_CLEAR, DB_NAME, (int) DB_PORT, null);
1884
1885
    // Now prepare text
1886
    $txt = '### Page on which it happened
1887
' . $data['current_page'] . '
1888
1889
### Steps to reproduce
1890
1.
1891
2.
1892
3.
1893
1894
### Expected behaviour
1895
Tell us what should happen
1896
1897
1898
### Actual behaviour
1899
Tell us what happens instead
1900
1901
### Server configuration
1902
**Operating system**: ' . php_uname() . '
1903
1904
**Web server:** ' . $_SERVER['SERVER_SOFTWARE'] . '
1905
1906
**Database:** ' . ($link === false ? $lang->get('undefined') : mysqli_get_server_info($link)) . '
1907
1908
**PHP version:** ' . PHP_VERSION . '
1909
1910
**Teampass version:** ' . TP_VERSION . '.' . TP_VERSION_MINOR . '
1911
1912
**Teampass configuration variables:**
1913
```
1914
' . $list_of_options . '
1915
```
1916
1917
**Updated from an older Teampass or fresh install:**
1918
1919
### Client configuration
1920
1921
**Browser:** ' . $data['browser_name'] . ' - ' . $data['browser_version'] . '
1922
1923
**Operating system:** ' . $data['os'] . ' - ' . $data['os_archi'] . 'bits
1924
1925
### Logs
1926
1927
#### Web server error log
1928
```
1929
' . $err['message'] . ' - ' . $err['file'] . ' (' . $err['line'] . ')
1930
```
1931
1932
#### Teampass 10 last system errors
1933
```
1934
' . $teampass_errors . '
1935
```
1936
1937
#### Log from the web-browser developer console (CTRL + SHIFT + i)
1938
```
1939
Insert the log here and especially the answer of the query that failed.
1940
```
1941
';
1942
1943
    return prepareExchangedData(
1944
        array(
1945
            'html' => $txt,
1946
            'error' => '',
1947
        ),
1948
        'encode'
1949
    );
1950
}
1951
1952
/**
1953
 * Check that the user password is valid
1954
 *
1955
 * @param integer $post_user_id
1956
 * @param string $post_user_password
1957
 * @param array $SETTINGS
1958
 * @return string
1959
 */
1960
function isUserPasswordCorrect(
1961
    int $post_user_id,
1962
    string $post_user_password,
1963
    array $SETTINGS
1964
): string
1965
{
1966
    $session = SessionManager::getSession();
1967
    // Load user's language
1968
    $lang = new Language($session->get('user-language') ?? 'english');
1969
    
1970
    if (isUserIdValid($post_user_id) === true) {
1971
        // Check if user exists
1972
        $userInfo = DB::queryFirstRow(
1973
            'SELECT public_key, private_key, pw, auth_type
1974
            FROM ' . prefixTable('users') . '
1975
            WHERE id = %i',
1976
            $post_user_id
1977
        );
1978
        if (DB::count() > 0 && empty($userInfo['private_key']) === false) {
1979
            // Get itemKey from current user
1980
            // Get one item
1981
            $currentUserKey = DB::queryFirstRow(
1982
                'SELECT object_id, share_key, increment_id
1983
                FROM ' . prefixTable('sharekeys_items') . ' AS si
1984
                INNER JOIN ' . prefixTable('items') . ' AS i ON  (i.id = si.object_id)
1985
                INNER JOIN ' . prefixTable('nested_tree') . ' AS nt ON  (i.id_tree = nt.id)
1986
                WHERE user_id = %i AND nt.personal_folder = %i',
1987
                $post_user_id,
1988
                0
1989
            );
1990
            
1991
            if (DB::count() === 0) {
1992
                // This user has no items
1993
                // let's consider no items in DB
1994
                return prepareExchangedData(
1995
                    array(
1996
                        'error' => false,
1997
                        'message' => '',
1998
                        'debug' => '',
1999
                    ),
2000
                    'encode'
2001
                );
2002
            }
2003
2004
            if ($currentUserKey !== null) {
2005
                // Decrypt itemkey with user key
2006
                // use old password to decrypt private_key
2007
                $session->set('user-private_key', decryptPrivateKey($post_user_password, $userInfo['private_key']));
2008
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2009
2010
                //echo $post_user_password."  --  ".$userInfo['private_key']. ";;";
2011
2012
                if (empty(base64_decode($itemKey)) === false) {
2013
                    // GOOD password
2014
                    return prepareExchangedData(
2015
                        array(
2016
                            'error' => false,
2017
                            'message' => '',
2018
                            'debug' => '',
2019
                        ),
2020
                        'encode'
2021
                    );
2022
                }
2023
            }
2024
2025
            // use the password check
2026
            $passwordManager = new PasswordManager();
2027
            
2028
            if ($passwordManager->verifyPassword($userInfo['pw'], htmlspecialchars_decode($post_user_password)) === true) {
2029
                // GOOD password
2030
                return prepareExchangedData(
2031
                    array(
2032
                        'error' => false,
2033
                        'message' => '',
2034
                        'debug' => '',
2035
                    ),
2036
                    'encode'
2037
                );
2038
            }
2039
        }
2040
    }
2041
2042
    return prepareExchangedData(
2043
        array(
2044
            'error' => true,
2045
            'message' => $lang->get('password_is_not_correct'),
2046
        ),
2047
        'encode'
2048
    );
2049
}
2050
2051
function changePrivateKeyEncryptionPassword(
2052
    int $post_user_id,
2053
    string $post_current_code,
2054
    string $post_new_code,
2055
    string $post_action_type,
2056
    array $SETTINGS
2057
): string
2058
{
2059
    $session = SessionManager::getSession();
2060
    // Load user's language
2061
    $lang = new Language($session->get('user-language') ?? 'english');
2062
    
2063
    if (empty($post_new_code) === true) {
2064
        // no user password
2065
        return prepareExchangedData(
2066
            array(
2067
                'error' => true,
2068
                'message' => $lang->get('error_bad_credentials'),
2069
                'debug' => '',
2070
            ),
2071
            'encode'
2072
        );
2073
    }
2074
2075
    if (isUserIdValid($post_user_id) === true) {
2076
        // Get user info
2077
        $userData = DB::queryFirstRow(
2078
            'SELECT private_key
2079
            FROM ' . prefixTable('users') . '
2080
            WHERE id = %i',
2081
            $post_user_id
2082
        );
2083
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
2084
            if ($post_action_type === 'encrypt_privkey_with_user_password') {
2085
                // Here the user has his private key encrypted with an OTC.
2086
                // We need to encrypt it with his real password
2087
                $privateKey = decryptPrivateKey($post_new_code, $userData['private_key']);
2088
                $hashedPrivateKey = encryptPrivateKey($post_current_code, $privateKey);
2089
            } else {
2090
                $privateKey = decryptPrivateKey($post_current_code, $userData['private_key']);
2091
                $hashedPrivateKey = encryptPrivateKey($post_new_code, $privateKey);
2092
            }
2093
2094
            // Should fail here to avoid break user private key.
2095
            if (strlen($privateKey) === 0 || strlen($hashedPrivateKey) < 30) {
2096
                if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2097
                    error_log("Error reencrypt user private key. User ID: {$post_user_id}, Given OTP: '{$post_current_code}'");
2098
                }
2099
                return prepareExchangedData(
2100
                    array(
2101
                        'error' => true,
2102
                        'message' => $lang->get('error_otp_secret'),
2103
                        'debug' => '',
2104
                    ),
2105
                    'encode'
2106
                );
2107
            }
2108
2109
            // Update user account
2110
            DB::update(
2111
                prefixTable('users'),
2112
                array(
2113
                    'private_key' => $hashedPrivateKey,
2114
                    'special' => 'none',
2115
                    'otp_provided' => 1,
2116
                ),
2117
                'id = %i',
2118
                $post_user_id
2119
            );
2120
2121
            $session->set('user-private_key', $privateKey);
2122
        }
2123
2124
        // Return
2125
        return prepareExchangedData(
2126
            array(
2127
                'error' => false,
2128
                'message' => '',
2129
            ),
2130
            'encode'
2131
        );
2132
    }
2133
    
2134
    return prepareExchangedData(
2135
        array(
2136
            'error' => true,
2137
            'message' => $lang->get('error_no_user'),
2138
            'debug' => '',
2139
        ),
2140
        'encode'
2141
    );
2142
}
2143
2144
function initializeUserPassword(
2145
    int $post_user_id,
2146
    string $post_special,
2147
    string $post_user_password,
2148
    bool $post_self_change,
2149
    array $SETTINGS
2150
): string
2151
{
2152
    // Load user's language
2153
    $session = SessionManager::getSession();
2154
    $lang = new Language($session->get('user-language') ?? 'english');
2155
    
2156
    if (isUserIdValid($post_user_id) === true) {
2157
        // Get user info
2158
        $userData = DB::queryFirstRow(
2159
            'SELECT email, auth_type, login
2160
            FROM ' . prefixTable('users') . '
2161
            WHERE id = %i',
2162
            $post_user_id
2163
        );
2164
        if (DB::count() > 0 && empty($userData['email']) === false) {
2165
            // If user pwd is empty then generate a new one and send it to user
2166
            if (isset($post_user_password) === false || empty($post_user_password) === true) {
2167
                // Generate new password
2168
                $post_user_password = generateQuickPassword();
2169
            }
2170
2171
            // If LDAP enabled, then
2172
            // check that this password is correct
2173
            $continue = true;
2174
            if ($userData['auth_type'] === 'ldap' && (int) $SETTINGS['ldap_mode'] === 1) {
2175
                $continue = ldapCheckUserPassword(
2176
                    $userData['login'],
2177
                    $post_user_password,
2178
                    $SETTINGS
2179
                );
2180
            }
2181
2182
            if ($continue === true) {
2183
                // Only change if email is successfull
2184
                $passwordManager = new PasswordManager();
2185
                // GEnerate new keys
2186
                $userKeys = generateUserKeys($post_user_password);
2187
2188
                // Update user account
2189
                DB::update(
2190
                    prefixTable('users'),
2191
                    array(
2192
                        'special' => $post_special,
2193
                        'pw' => $passwordManager->hashPassword($post_user_password),
2194
                        'public_key' => $userKeys['public_key'],
2195
                        'private_key' => $userKeys['private_key'],
2196
                        'last_pw_change' => time(),
2197
                    ),
2198
                    'id = %i',
2199
                    $post_user_id
2200
                );
2201
2202
                // Return
2203
                return prepareExchangedData(
2204
                    array(
2205
                        'error' => false,
2206
                        'message' => '',
2207
                        'user_pwd' => $post_user_password,
2208
                        'user_email' => $userData['email'],
2209
                    ),
2210
                    'encode'
2211
                );
2212
            }
2213
            // Return error
2214
            return prepareExchangedData(
2215
                array(
2216
                    'error' => true,
2217
                    'message' => $lang->get('no_email_set'),
2218
                    'debug' => '',
2219
                    'self_change' => $post_self_change,
2220
                ),
2221
                'encode'
2222
            );
2223
        }
2224
2225
        // Error
2226
        return prepareExchangedData(
2227
            array(
2228
                'error' => true,
2229
                'message' => $lang->get('no_email_set'),
2230
                'debug' => '',
2231
            ),
2232
            'encode'
2233
        );
2234
    }
2235
    
2236
    return prepareExchangedData(
2237
        array(
2238
            'error' => true,
2239
            'message' => $lang->get('error_no_user'),
2240
            'debug' => '',
2241
        ),
2242
        'encode'
2243
    );
2244
}
2245
2246
function generateOneTimeCode(
2247
    int $post_user_id
2248
): string
2249
{
2250
    // Load user's language
2251
    $session = SessionManager::getSession();
2252
    $lang = new Language($session->get('user-language') ?? 'english');
2253
    
2254
    if (isUserIdValid($post_user_id) === true) {
2255
        // Get user info
2256
        $userData = DB::queryFirstRow(
2257
            'SELECT email, auth_type, login
2258
            FROM ' . prefixTable('users') . '
2259
            WHERE id = %i',
2260
            $post_user_id
2261
        );
2262
        if (DB::count() > 0 && empty($userData['email']) === false) {
2263
            // Generate pwd
2264
            $password = generateQuickPassword();
2265
2266
            // GEnerate new keys
2267
            $userKeys = generateUserKeys($password);
2268
2269
            // Save in DB
2270
            DB::update(
2271
                prefixTable('users'),
2272
                array(
2273
                    'public_key' => $userKeys['public_key'],
2274
                    'private_key' => $userKeys['private_key'],
2275
                    'special' => 'generate-keys',
2276
                ),
2277
                'id=%i',
2278
                $post_user_id
2279
            );
2280
2281
            return prepareExchangedData(
2282
                array(
2283
                    'error' => false,
2284
                    'message' => '',
2285
                    'code' => $password,
2286
                    'visible_otp' => ADMIN_VISIBLE_OTP_ON_LDAP_IMPORT,
2287
                ),
2288
                'encode'
2289
            );
2290
        }
2291
        
2292
        return prepareExchangedData(
2293
            array(
2294
                'error' => true,
2295
                'message' => $lang->get('no_email_set'),
2296
            ),
2297
            'encode'
2298
        );
2299
    }
2300
        
2301
    return prepareExchangedData(
2302
        array(
2303
            'error' => true,
2304
            'message' => $lang->get('error_no_user'),
2305
        ),
2306
        'encode'
2307
    );
2308
}
2309
2310
function startReEncryptingUserSharekeys(
2311
    int $post_user_id,
2312
    bool $post_self_change,
2313
    array $SETTINGS
2314
): string
2315
{
2316
    // Load user's language
2317
    $session = SessionManager::getSession();
2318
    $lang = new Language($session->get('user-language') ?? 'english');
2319
    
2320
    if (isUserIdValid($post_user_id) === true) {
2321
        // Check if user exists
2322
        DB::queryFirstRow(
2323
            'SELECT *
2324
            FROM ' . prefixTable('users') . '
2325
            WHERE id = %i',
2326
            $post_user_id
2327
        );
2328
        if (DB::count() > 0) {
2329
            // CLear old sharekeys
2330
            if ($post_self_change === false) {
2331
                deleteUserObjetsKeys($post_user_id, $SETTINGS);
2332
            }
2333
2334
            // Continu with next step
2335
            return prepareExchangedData(
2336
                array(
2337
                    'error' => false,
2338
                    'message' => '',
2339
                    'step' => 'step1',
2340
                    'userId' => $post_user_id,
2341
                    'start' => 0,
2342
                    'self_change' => $post_self_change,
2343
                ),
2344
                'encode'
2345
            );
2346
        }
2347
        // Nothing to do
2348
        return prepareExchangedData(
2349
            array(
2350
                'error' => true,
2351
                'message' => $lang->get('error_no_user'),
2352
            ),
2353
            'encode'
2354
        );
2355
    }
2356
2357
    return prepareExchangedData(
2358
        array(
2359
            'error' => true,
2360
            'message' => $lang->get('error_no_user'),
2361
        ),
2362
        'encode'
2363
    );
2364
}
2365
2366
2367
/**
2368
 * Encrypts or re-encrypts user's shared keys through multiple steps.
2369
 *
2370
 * @param int $post_user_id ID of the user whose keys are being encrypted.
2371
 * @param bool $post_self_change Indicates if the change is triggered by the user.
2372
 * @param string $post_action Current step of the encryption process.
2373
 * @param int $post_start Start index for the current batch.
2374
 * @param int $post_length Number of items to process in the current batch.
2375
 * @param array $SETTINGS Application settings.
2376
 * @return string JSON-encoded response indicating the next step or an error message.
2377
 */
2378
function continueReEncryptingUserSharekeys(
2379
    int $post_user_id,
2380
    bool $post_self_change,
2381
    string $post_action,
2382
    int $post_start,
2383
    int $post_length,
2384
    array $SETTINGS
2385
): string {
2386
    $session = SessionManager::getSession();
2387
    $lang = new Language($session->get('user-language') ?? 'english');
2388
    error_log("continueReEncryptingUserSharekeys: post_user_id: $post_user_id, post_self_change: $post_self_change, post_action: $post_action, post_start: $post_start, post_length: $post_length");
2389
2390
    // Validate the user ID
2391
    if (!isUserIdValid($post_user_id)) {
2392
        return prepareErrorResponse($lang->get('error_no_user'), $post_user_id);
2393
    }
2394
2395
    // Retrieve user information
2396
    $userInfo = getUserPublicKey($post_user_id);
2397
    if (!$userInfo || !isset($userInfo['public_key'])) {
2398
        return prepareFinishedResponse($post_user_id, $post_self_change);
2399
    }
2400
2401
    // Perform the appropriate step
2402
    $response = handleEncryptionStep(
2403
        $post_user_id,
2404
        $post_self_change,
2405
        $post_action,
2406
        $post_start,
2407
        $post_length,
2408
        $userInfo['public_key'],
2409
        $SETTINGS
2410
    );
2411
2412
    return prepareExchangedData($response, 'encode');
2413
}
2414
2415
/**
2416
 * Retrieves user information from the database.
2417
 *
2418
 * @param int $userId ID of the user.
2419
 * @return array|null User information or null if the user does not exist.
2420
 */
2421
function getUserPublicKey(int $userId): ?array {
2422
    return DB::queryFirstRow(
2423
        'SELECT public_key FROM ' . prefixTable('users') . ' WHERE id = %i',
2424
        $userId
2425
    );
2426
}
2427
2428
/**
2429
 * Handles the logic for each step of the encryption process.
2430
 *
2431
 * @param int $userId ID of the user.
2432
 * @param bool $selfChange Indicates if the change is triggered by the user.
2433
 * @param string $action Current step of the encryption process.
2434
 * @param int $start Start index for the current batch.
2435
 * @param int $length Number of items to process in the current batch.
2436
 * @param string $publicKey User's public key.
2437
 * @param array $settings Application settings.
2438
 * @return array Response data for the current step or next step.
2439
 */
2440
function handleEncryptionStep(
2441
    int $userId,
2442
    bool $selfChange,
2443
    string $action,
2444
    int $start,
2445
    int $length,
2446
    string $publicKey,
2447
    array $settings
2448
): array {
2449
    $response = [];
2450
2451
    switch ($action) {
2452
        case 'step0':
2453
            if (!$selfChange) {
2454
                deleteUserObjetsKeys($userId, $settings);
2455
            }
2456
            $response['post_action'] = 'step10';
2457
            break;
2458
2459
        case 'step10':
2460
            $response = continueReEncryptingUserSharekeysStep10($userId, $selfChange, $action, $start, $length, $publicKey, $settings);
2461
            break;
2462
2463
        case 'step20':
2464
            $response = continueReEncryptingUserSharekeysStep20($userId, $selfChange, $action, $start, $length, $publicKey, $settings);
2465
            break;
2466
2467
        case 'step30':
2468
            $response = continueReEncryptingUserSharekeysStep30($userId, $selfChange, $action, $start, $length, $publicKey, $settings);
2469
            break;
2470
2471
        case 'step40':
2472
            $response = continueReEncryptingUserSharekeysStep40($userId, $selfChange, $action, $start, $length, $publicKey, $settings);
2473
            break;
2474
2475
        case 'step50':
2476
            $response = continueReEncryptingUserSharekeysStep50($userId, $selfChange, $action, $start, $length, $publicKey, $settings);
2477
            break;
2478
2479
        case 'step60':
2480
            $response = continueReEncryptingUserSharekeysStep60($userId, $selfChange, $action, $start, $length, $publicKey, $settings);
2481
            break;
2482
2483
        default:
2484
            $response = [
2485
                'error' => false,
2486
                'message' => '',
2487
                'step' => 'finished',
2488
                'start' => 0,
2489
                'userId' => $userId,
2490
                'self_change' => $selfChange,
2491
            ];
2492
            break;
2493
    }
2494
2495
    // Add common fields
2496
    $response['userId'] = $userId;
2497
    $response['self_change'] = $selfChange;
2498
2499
    return $response;
2500
}
2501
2502
/**
2503
 * Prepares an error response.
2504
 *
2505
 * @param string $message Error message.
2506
 * @param int $userId User ID causing the error.
2507
 * @return string JSON-encoded error response.
2508
 */
2509
function prepareErrorResponse(string $message, int $userId): string {
2510
    return prepareExchangedData(
2511
        [
2512
            'error' => true,
2513
            'message' => $message,
2514
            'extra' => $userId,
2515
        ],
2516
        'encode'
2517
    );
2518
}
2519
2520
/**
2521
 * Prepares a response indicating that the process is finished.
2522
 *
2523
 * @param int $userId ID of the user.
2524
 * @param bool $selfChange Indicates if the change is triggered by the user.
2525
 * @return string JSON-encoded response for a finished process.
2526
 */
2527
function prepareFinishedResponse(int $userId, bool $selfChange): string {
2528
    return prepareExchangedData(
2529
        [
2530
            'error' => false,
2531
            'message' => '',
2532
            'step' => 'finished',
2533
            'start' => 0,
2534
            'userId' => $userId,
2535
            'self_change' => $selfChange,
2536
        ],
2537
        'encode'
2538
    );
2539
}
2540
2541
2542
2543
function continueReEncryptingUserSharekeysStep10(
2544
    int $post_user_id,
2545
    bool $post_self_change,
2546
    string $post_action,
2547
    int $post_start,
2548
    int $post_length,
2549
    string $user_public_key,
2550
    array $SETTINGS
2551
): array 
2552
{
2553
    $session = SessionManager::getSession();
2554
    // Loop on items
2555
    $rows = DB::query(
2556
        'SELECT id, pw
2557
        FROM ' . prefixTable('items') . '
2558
        WHERE perso = 0
2559
        LIMIT ' . $post_start . ', ' . $post_length
2560
    );
2561
    foreach ($rows as $record) {
2562
        // Get itemKey from current user
2563
        $currentUserKey = DB::queryFirstRow(
2564
            'SELECT share_key, increment_id
2565
            FROM ' . prefixTable('sharekeys_items') . '
2566
            WHERE object_id = %i AND user_id = %i',
2567
            $record['id'],
2568
            $session->get('user-id')
2569
        );
2570
2571
        // do we have any input? (#3481)
2572
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2573
            continue;
2574
        }
2575
2576
        // Decrypt itemkey with admin key
2577
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2578
        
2579
        // Encrypt Item key
2580
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2581
        
2582
        // Save the key in DB
2583
        if ($post_self_change === false) {
2584
            DB::insert(
2585
                prefixTable('sharekeys_items'),
2586
                array(
2587
                    'object_id' => (int) $record['id'],
2588
                    'user_id' => (int) $post_user_id,
2589
                    'share_key' => $share_key_for_item,
2590
                )
2591
            );
2592
        } else {
2593
            // Get itemIncrement from selected user
2594
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2595
                $currentUserKey = DB::queryFirstRow(
2596
                    'SELECT increment_id
2597
                    FROM ' . prefixTable('sharekeys_items') . '
2598
                    WHERE object_id = %i AND user_id = %i',
2599
                    $record['id'],
2600
                    $post_user_id
2601
                );
2602
2603
                if (DB::count() > 0) {
2604
                    // NOw update
2605
                    DB::update(
2606
                        prefixTable('sharekeys_items'),
2607
                        array(
2608
                            'share_key' => $share_key_for_item,
2609
                        ),
2610
                        'increment_id = %i',
2611
                        $currentUserKey['increment_id']
2612
                    );
2613
                } else {
2614
                    DB::insert(
2615
                        prefixTable('sharekeys_items'),
2616
                        array(
2617
                            'object_id' => (int) $record['id'],
2618
                            'user_id' => (int) $post_user_id,
2619
                            'share_key' => $share_key_for_item,
2620
                        )
2621
                    );
2622
                }
2623
            }
2624
        }
2625
    }
2626
2627
    // SHould we change step?
2628
    DB::query(
2629
        'SELECT *
2630
        FROM ' . prefixTable('items') . '
2631
        WHERE perso = 0'
2632
    );
2633
2634
    $next_start = (int) $post_start + (int) $post_length;
2635
    return [
2636
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2637
        'post_action' => $next_start > DB::count() ? 'step20' : 'step10',
2638
    ];
2639
}
2640
2641
function continueReEncryptingUserSharekeysStep20(
2642
    int $post_user_id,
2643
    bool $post_self_change,
2644
    string $post_action,
2645
    int $post_start,
2646
    int $post_length,
2647
    string $user_public_key,
2648
    array $SETTINGS
2649
): array
2650
{
2651
    $session = SessionManager::getSession();
2652
    // Loop on logs
2653
    $rows = DB::query(
2654
        'SELECT increment_id
2655
        FROM ' . prefixTable('log_items') . '
2656
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"
2657
        LIMIT ' . $post_start . ', ' . $post_length
2658
    );
2659
    foreach ($rows as $record) {
2660
        // Get itemKey from current user
2661
        $currentUserKey = DB::queryFirstRow(
2662
            'SELECT share_key
2663
            FROM ' . prefixTable('sharekeys_logs') . '
2664
            WHERE object_id = %i AND user_id = %i',
2665
            $record['increment_id'],
2666
            $session->get('user-id')
2667
        );
2668
2669
        // do we have any input? (#3481)
2670
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2671
            continue;
2672
        }
2673
2674
        // Decrypt itemkey with admin key
2675
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2676
2677
        // Encrypt Item key
2678
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2679
2680
        // Save the key in DB
2681
        if ($post_self_change === false) {
2682
            DB::insert(
2683
                prefixTable('sharekeys_logs'),
2684
                array(
2685
                    'object_id' => (int) $record['increment_id'],
2686
                    'user_id' => (int) $post_user_id,
2687
                    'share_key' => $share_key_for_item,
2688
                )
2689
            );
2690
        } else {
2691
            // Get itemIncrement from selected user
2692
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2693
                $currentUserKey = DB::queryFirstRow(
2694
                    'SELECT increment_id
2695
                    FROM ' . prefixTable('sharekeys_items') . '
2696
                    WHERE object_id = %i AND user_id = %i',
2697
                    $record['id'],
2698
                    $post_user_id
2699
                );
2700
            }
2701
2702
            // NOw update
2703
            DB::update(
2704
                prefixTable('sharekeys_logs'),
2705
                array(
2706
                    'share_key' => $share_key_for_item,
2707
                ),
2708
                'increment_id = %i',
2709
                $currentUserKey['increment_id']
2710
            );
2711
        }
2712
    }
2713
2714
    // SHould we change step?
2715
    DB::query(
2716
        'SELECT increment_id
2717
        FROM ' . prefixTable('log_items') . '
2718
        WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"'
2719
    );
2720
2721
    $next_start = (int) $post_start + (int) $post_length;
2722
    return [
2723
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2724
        'post_action' => $next_start > DB::count() ? 'step30' : 'step20',
2725
    ];
2726
}
2727
2728
function continueReEncryptingUserSharekeysStep30(
2729
    int $post_user_id,
2730
    bool $post_self_change,
2731
    string $post_action,
2732
    int $post_start,
2733
    int $post_length,
2734
    string $user_public_key,
2735
    array $SETTINGS
2736
): array
2737
{
2738
    $session = SessionManager::getSession();
2739
    // Loop on fields
2740
    $rows = DB::query(
2741
        'SELECT id
2742
        FROM ' . prefixTable('categories_items') . '
2743
        WHERE encryption_type = "teampass_aes"
2744
        LIMIT ' . $post_start . ', ' . $post_length
2745
    );
2746
    foreach ($rows as $record) {
2747
        // Get itemKey from current user
2748
        $currentUserKey = DB::queryFirstRow(
2749
            'SELECT share_key
2750
            FROM ' . prefixTable('sharekeys_fields') . '
2751
            WHERE object_id = %i AND user_id = %i',
2752
            $record['id'],
2753
            $session->get('user-id')
2754
        );
2755
2756
        // do we have any input? (#3481)
2757
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2758
            continue;
2759
        }
2760
2761
        // Decrypt itemkey with admin key
2762
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2763
2764
        // Encrypt Item key
2765
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2766
2767
        // Save the key in DB
2768
        if ($post_self_change === false) {
2769
            DB::insert(
2770
                prefixTable('sharekeys_fields'),
2771
                array(
2772
                    'object_id' => (int) $record['id'],
2773
                    'user_id' => (int) $post_user_id,
2774
                    'share_key' => $share_key_for_item,
2775
                )
2776
            );
2777
        } else {
2778
            // Get itemIncrement from selected user
2779
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2780
                $currentUserKey = DB::queryFirstRow(
2781
                    'SELECT increment_id
2782
                    FROM ' . prefixTable('sharekeys_items') . '
2783
                    WHERE object_id = %i AND user_id = %i',
2784
                    $record['id'],
2785
                    $post_user_id
2786
                );
2787
            }
2788
2789
            // NOw update
2790
            DB::update(
2791
                prefixTable('sharekeys_fields'),
2792
                array(
2793
                    'share_key' => $share_key_for_item,
2794
                ),
2795
                'increment_id = %i',
2796
                $currentUserKey['increment_id']
2797
            );
2798
        }
2799
    }
2800
2801
    // SHould we change step?
2802
    DB::query(
2803
        'SELECT *
2804
        FROM ' . prefixTable('categories_items') . '
2805
        WHERE encryption_type = "teampass_aes"'
2806
    );
2807
2808
    $next_start = (int) $post_start + (int) $post_length;
2809
    return [
2810
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2811
        'post_action' => $next_start > DB::count() ? 'step40' : 'step30',
2812
    ];
2813
}
2814
2815
function continueReEncryptingUserSharekeysStep40(
2816
    int $post_user_id,
2817
    bool $post_self_change,
2818
    string $post_action,
2819
    int $post_start,
2820
    int $post_length,
2821
    string $user_public_key,
2822
    array $SETTINGS
2823
): array
2824
{
2825
    $session = SessionManager::getSession();
2826
    // Loop on suggestions
2827
    $rows = DB::query(
2828
        'SELECT id
2829
        FROM ' . prefixTable('suggestion') . '
2830
        LIMIT ' . $post_start . ', ' . $post_length
2831
    );
2832
    foreach ($rows as $record) {
2833
        // Get itemKey from current user
2834
        $currentUserKey = DB::queryFirstRow(
2835
            'SELECT share_key
2836
            FROM ' . prefixTable('sharekeys_suggestions') . '
2837
            WHERE object_id = %i AND user_id = %i',
2838
            $record['id'],
2839
            $session->get('user-id')
2840
        );
2841
2842
        // do we have any input? (#3481)
2843
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2844
            continue;
2845
        }
2846
2847
        // Decrypt itemkey with admin key
2848
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2849
2850
        // Encrypt Item key
2851
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2852
2853
        // Save the key in DB
2854
        if ($post_self_change === false) {
2855
            DB::insert(
2856
                prefixTable('sharekeys_suggestions'),
2857
                array(
2858
                    'object_id' => (int) $record['id'],
2859
                    'user_id' => (int) $post_user_id,
2860
                    'share_key' => $share_key_for_item,
2861
                )
2862
            );
2863
        } else {
2864
            // Get itemIncrement from selected user
2865
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2866
                $currentUserKey = DB::queryFirstRow(
2867
                    'SELECT increment_id
2868
                    FROM ' . prefixTable('sharekeys_items') . '
2869
                    WHERE object_id = %i AND user_id = %i',
2870
                    $record['id'],
2871
                    $post_user_id
2872
                );
2873
            }
2874
2875
            // NOw update
2876
            DB::update(
2877
                prefixTable('sharekeys_suggestions'),
2878
                array(
2879
                    'share_key' => $share_key_for_item,
2880
                ),
2881
                'increment_id = %i',
2882
                $currentUserKey['increment_id']
2883
            );
2884
        }
2885
    }
2886
2887
    // SHould we change step?
2888
    DB::query(
2889
        'SELECT *
2890
        FROM ' . prefixTable('suggestion')
2891
    );
2892
2893
    $next_start = (int) $post_start + (int) $post_length;
2894
    return [
2895
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2896
        'post_action' => $next_start > DB::count() ? 'step50' : 'step40',
2897
    ];
2898
}
2899
2900
function continueReEncryptingUserSharekeysStep50(
2901
    int $post_user_id,
2902
    bool $post_self_change,
2903
    string $post_action,
2904
    int $post_start,
2905
    int $post_length,
2906
    string $user_public_key,
2907
    array $SETTINGS
2908
): array
2909
{
2910
    $session = SessionManager::getSession();
2911
    // Loop on files
2912
    $rows = DB::query(
2913
        'SELECT id
2914
        FROM ' . prefixTable('files') . '
2915
        WHERE status = "' . TP_ENCRYPTION_NAME . '"
2916
        LIMIT ' . $post_start . ', ' . $post_length
2917
    ); //aes_encryption
2918
    foreach ($rows as $record) {
2919
        // Get itemKey from current user
2920
        $currentUserKey = DB::queryFirstRow(
2921
            'SELECT share_key
2922
            FROM ' . prefixTable('sharekeys_files') . '
2923
            WHERE object_id = %i AND user_id = %i',
2924
            $record['id'],
2925
            $session->get('user-id')
2926
        );
2927
2928
        // do we have any input? (#3481)
2929
        if ($currentUserKey === null || count($currentUserKey) === 0) {
2930
            continue;
2931
        }
2932
2933
        // Decrypt itemkey with admin key
2934
        $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
2935
2936
        // Encrypt Item key
2937
        $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
2938
2939
        // Save the key in DB
2940
        if ($post_self_change === false) {
2941
            DB::insert(
2942
                prefixTable('sharekeys_files'),
2943
                array(
2944
                    'object_id' => (int) $record['id'],
2945
                    'user_id' => (int) $post_user_id,
2946
                    'share_key' => $share_key_for_item,
2947
                )
2948
            );
2949
        } else {
2950
            // Get itemIncrement from selected user
2951
            if ((int) $post_user_id !== (int) $session->get('user-id')) {
2952
                $currentUserKey = DB::queryFirstRow(
2953
                    'SELECT increment_id
2954
                    FROM ' . prefixTable('sharekeys_items') . '
2955
                    WHERE object_id = %i AND user_id = %i',
2956
                    $record['id'],
2957
                    $post_user_id
2958
                );
2959
            }
2960
2961
            // NOw update
2962
            DB::update(
2963
                prefixTable('sharekeys_files'),
2964
                array(
2965
                    'share_key' => $share_key_for_item,
2966
                ),
2967
                'increment_id = %i',
2968
                $currentUserKey['increment_id']
2969
            );
2970
        }
2971
    }
2972
2973
    // SHould we change step?
2974
    DB::query(
2975
        'SELECT *
2976
        FROM ' . prefixTable('files') . '
2977
        WHERE status = "' . TP_ENCRYPTION_NAME . '"'
2978
    );
2979
2980
    $next_start = (int) $post_start + (int) $post_length;
2981
    return [
2982
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
2983
        'post_action' => $next_start > DB::count() ? 'step60' : 'step50',
2984
    ];
2985
}
2986
2987
function continueReEncryptingUserSharekeysStep60(
2988
    int $post_user_id,
2989
    bool $post_self_change,
2990
    string $post_action,
2991
    int $post_start,
2992
    int $post_length,
2993
    string $user_public_key,
2994
    array $SETTINGS
2995
): array
2996
{
2997
    $session = SessionManager::getSession();
2998
    // IF USER IS NOT THE SAME
2999
    if ((int) $post_user_id === (int) $session->get('user-id')) {
3000
        return [
3001
            'next_start' => 0,
3002
            'post_action' => 'finished',
3003
        ];
3004
    }
3005
    
3006
    // Loop on persoanl items
3007
    if (count($session->get('user-personal_folders')) > 0) {
3008
        $rows = DB::query(
3009
            'SELECT id, pw
3010
            FROM ' . prefixTable('items') . '
3011
            WHERE perso = 1 AND id_tree IN %ls AND encryption_type = %s
3012
            LIMIT ' . $post_start . ', ' . $post_length,
3013
            $session->get('user-personal_folders'),
3014
            "defuse"
3015
        );
3016
        foreach ($rows as $record) {
3017
            // Get itemKey from current user
3018
            $currentUserKey = DB::queryFirstRow(
3019
                'SELECT share_key, increment_id
3020
                FROM ' . prefixTable('sharekeys_items') . '
3021
                WHERE object_id = %i AND user_id = %i',
3022
                $record['id'],
3023
                $session->get('user-id')
3024
            );
3025
3026
            // Decrypt itemkey with admin key
3027
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $session->get('user-private_key'));
3028
3029
            // Encrypt Item key
3030
            $share_key_for_item = encryptUserObjectKey($itemKey, $user_public_key);
3031
3032
            // Save the key in DB
3033
            if ($post_self_change === false) {
3034
                DB::insert(
3035
                    prefixTable('sharekeys_items'),
3036
                    array(
3037
                        'object_id' => (int) $record['id'],
3038
                        'user_id' => (int) $post_user_id,
3039
                        'share_key' => $share_key_for_item,
3040
                    )
3041
                );
3042
            } else {
3043
                // Get itemIncrement from selected user
3044
                if ((int) $post_user_id !== (int) $session->get('user-id')) {
3045
                    $currentUserKey = DB::queryFirstRow(
3046
                        'SELECT increment_id
3047
                        FROM ' . prefixTable('sharekeys_items') . '
3048
                        WHERE object_id = %i AND user_id = %i',
3049
                        $record['id'],
3050
                        $post_user_id
3051
                    );
3052
                }
3053
3054
                // NOw update
3055
                DB::update(
3056
                    prefixTable('sharekeys_items'),
3057
                    array(
3058
                        'share_key' => $share_key_for_item,
3059
                    ),
3060
                    'increment_id = %i',
3061
                    $currentUserKey['increment_id']
3062
                );
3063
            }
3064
        }
3065
    }
3066
3067
    // SHould we change step?
3068
    DB::query(
3069
        'SELECT *
3070
        FROM ' . prefixTable('items') . '
3071
        WHERE perso = 0'
3072
    );
3073
3074
    $next_start = (int) $post_start + (int) $post_length;
3075
    return [
3076
        'next_start' => $next_start > DB::count() ? 0 : $next_start,
3077
        'post_action' => $next_start > DB::count() ? 'finished' : 'step60',
3078
    ];
3079
}
3080
3081
function migrateTo3_DoUserPersonalItemsEncryption(
3082
    int $post_user_id,
3083
    int $post_start,
3084
    int $post_length,
3085
    int $post_counterItemsToTreat,
3086
    string $post_user_psk,
3087
    array $SETTINGS
3088
) {
3089
    $next_step = 'psk';
3090
    
3091
    $session = SessionManager::getSession();
3092
    $lang = new Language($session->get('user-language') ?? 'english');
3093
    
3094
    if (isUserIdValid($post_user_id) === true) {
3095
        // Check if user exists
3096
        $userInfo = DB::queryFirstRow(
3097
            'SELECT public_key, encrypted_psk
3098
            FROM ' . prefixTable('users') . '
3099
            WHERE id = %i',
3100
            $post_user_id
3101
        );
3102
        if (DB::count() > 0) {
3103
            // check if psk is correct.
3104
            if (empty($userInfo['encrypted_psk']) === false) {//echo $post_user_psk." ;; ".$userInfo['encrypted_psk']." ;; ";
3105
                $user_key_encoded = defuse_validate_personal_key(
3106
                    html_entity_decode($post_user_psk), // convert tspecial string back to their original characters due to FILTER_SANITIZE_FULL_SPECIAL_CHARS
3107
                    $userInfo['encrypted_psk']
3108
                );
3109
3110
                if (strpos($user_key_encoded, "Error ") !== false) {
3111
                    return prepareExchangedData(
3112
                        array(
3113
                            'error' => true,
3114
                            'message' => $lang->get('bad_psk'),
3115
                        ),
3116
                        'encode'
3117
                    );
3118
                }
3119
3120
                // Get number of user's personal items with no AES encryption
3121
                if ($post_counterItemsToTreat === -1) {
3122
                    DB::query(
3123
                        'SELECT id
3124
                        FROM ' . prefixTable('items') . '
3125
                        WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
3126
                        $session->get('user-personal_folders'),
3127
                        'teampass_aes'
3128
                    );
3129
                    $countUserPersonalItems = DB::count();
3130
                } else {
3131
                    $countUserPersonalItems = $post_counterItemsToTreat;
3132
                }
3133
3134
                // Loop on persoanl items
3135
                $rows = DB::query(
3136
                    'SELECT id, pw
3137
                    FROM ' . prefixTable('items') . '
3138
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s
3139
                    LIMIT ' . $post_length,
3140
                    $session->get('user-personal_folders'),
3141
                    'teampass_aes'
3142
                );
3143
                foreach ($rows as $record) {
3144
                    // Decrypt with Defuse
3145
                    $passwd = cryption(
3146
                        $record['pw'],
3147
                        $user_key_encoded,
3148
                        'decrypt',
3149
                        $SETTINGS
3150
                    );
3151
3152
                    // Encrypt with Object Key
3153
                    $cryptedStuff = doDataEncryption(html_entity_decode($passwd['string']));
3154
3155
                    // Store new password in DB
3156
                    DB::update(
3157
                        prefixTable('items'),
3158
                        array(
3159
                            'pw' => $cryptedStuff['encrypted'],
3160
                            'encryption_type' => 'teampass_aes',
3161
                        ),
3162
                        'id = %i',
3163
                        $record['id']
3164
                    );
3165
3166
                    // Insert in DB the new object key for this item by user
3167
                    DB::insert(
3168
                        prefixTable('sharekeys_items'),
3169
                        array(
3170
                            'object_id' => (int) $record['id'],
3171
                            'user_id' => (int) $post_user_id,
3172
                            'share_key' => encryptUserObjectKey($cryptedStuff['objectKey'], $userInfo['public_key']),
3173
                        )
3174
                    );
3175
3176
3177
                    // Does this item has Files?
3178
                    // Loop on files
3179
                    $rows = DB::query(
3180
                        'SELECT id, file
3181
                        FROM ' . prefixTable('files') . '
3182
                        WHERE status != %s
3183
                        AND id_item = %i',
3184
                        TP_ENCRYPTION_NAME,
3185
                        $record['id']
3186
                    );
3187
                    //aes_encryption
3188
                    foreach ($rows as $record2) {
3189
                        // Now decrypt the file
3190
                        prepareFileWithDefuse(
3191
                            'decrypt',
3192
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'],
3193
                            $SETTINGS['path_to_upload_folder'] . '/' . $record2['file'] . '.delete',
3194
                            $post_user_psk
3195
                        );
3196
3197
                        // Encrypt the file
3198
                        $encryptedFile = encryptFile($record2['file'] . '.delete', $SETTINGS['path_to_upload_folder']);
3199
3200
                        DB::update(
3201
                            prefixTable('files'),
3202
                            array(
3203
                                'file' => $encryptedFile['fileHash'],
3204
                                'status' => TP_ENCRYPTION_NAME,
3205
                            ),
3206
                            'id = %i',
3207
                            $record2['id']
3208
                        );
3209
3210
                        // Save key
3211
                        DB::insert(
3212
                            prefixTable('sharekeys_files'),
3213
                            array(
3214
                                'object_id' => (int) $record2['id'],
3215
                                'user_id' => (int) $session->get('user-id'),
3216
                                'share_key' => encryptUserObjectKey($encryptedFile['objectKey'], $session->get('user-public_key')),
3217
                            )
3218
                        );
3219
3220
                        // Unlink original file
3221
                        unlink($SETTINGS['path_to_upload_folder'] . '/' . $record2['file']);
3222
                    }
3223
                }
3224
3225
                // SHould we change step?
3226
                $next_start = (int) $post_start + (int) $post_length;
3227
                DB::query(
3228
                    'SELECT id
3229
                    FROM ' . prefixTable('items') . '
3230
                    WHERE perso = 1 AND id_tree IN %ls AND encryption_type != %s',
3231
                    $session->get('user-personal_folders'),
3232
                    'teampass_aes'
3233
                );
3234
                if (DB::count() === 0 || ($next_start - $post_length) >= $countUserPersonalItems) {
3235
                    // Now update user
3236
                    DB::update(
3237
                        prefixTable('users'),
3238
                        array(
3239
                            'special' => 'none',
3240
                            'upgrade_needed' => 0,
3241
                            'encrypted_psk' => '',
3242
                        ),
3243
                        'id = %i',
3244
                        $post_user_id
3245
                    );
3246
3247
                    $next_step = 'finished';
3248
                    $next_start = 0;
3249
                }
3250
3251
                // Continu with next step
3252
                return prepareExchangedData(
3253
                    array(
3254
                        'error' => false,
3255
                        'message' => '',
3256
                        'step' => $next_step,
3257
                        'start' => $next_start,
3258
                        'userId' => $post_user_id
3259
                    ),
3260
                    'encode'
3261
                );
3262
            }
3263
        }
3264
        
3265
        // Nothing to do
3266
        return prepareExchangedData(
3267
            array(
3268
                'error' => true,
3269
                'message' => $lang->get('error_no_user'),
3270
            ),
3271
            'encode'
3272
        );
3273
    }
3274
    
3275
    // Nothing to do
3276
    return prepareExchangedData(
3277
        array(
3278
            'error' => true,
3279
            'message' => $lang->get('error_no_user'),
3280
        ),
3281
        'encode'
3282
    );
3283
}
3284
3285
3286
function getUserInfo(
3287
    int $post_user_id,
3288
    array $SETTINGS
3289
)
3290
{
3291
    // Load user's language
3292
    $session = SessionManager::getSession();
3293
    $lang = new Language($session->get('user-language') ?? 'english');
3294
    
3295
    if (isUserIdValid($post_user_id) === true) {
3296
        // Get user info
3297
        $userData = DB::queryFirstRow(
3298
            'SELECT special, auth_type, is_ready_for_usage, ongoing_process_id, otp_provided, keys_recovery_time
3299
            FROM ' . prefixTable('users') . '
3300
            WHERE id = %i',
3301
            $post_user_id
3302
        );
3303
        if (DB::count() > 0) {
3304
            return prepareExchangedData(
3305
                array(
3306
                    'error' => false,
3307
                    'message' => '',
3308
                    'queryResults' => $userData,
3309
                ),
3310
                'encode'
3311
            );
3312
        }
3313
    }
3314
    return prepareExchangedData(
3315
        array(
3316
            'error' => true,
3317
            'message' => $lang->get('error_no_user'),
3318
        ),
3319
        'encode'
3320
    );
3321
}
3322
3323
/**
3324
 * Change user auth password
3325
 *
3326
 * @param integer $post_user_id
3327
 * @param string $post_current_pwd
3328
 * @param string $post_new_pwd
3329
 * @param array $SETTINGS
3330
 * @return string
3331
 */
3332
function changeUserAuthenticationPassword(
3333
    int $post_user_id,
3334
    string $post_current_pwd,
3335
    string $post_new_pwd,
3336
    array $SETTINGS
3337
)
3338
{
3339
    $session = SessionManager::getSession();
3340
    $lang = new Language($session->get('user-language') ?? 'english');
3341
 
3342
    if (isUserIdValid($post_user_id) === true) {
3343
        // Get user info
3344
        $userData = DB::queryFirstRow(
3345
            'SELECT auth_type, login, private_key
3346
            FROM ' . prefixTable('users') . '
3347
            WHERE id = %i',
3348
            $post_user_id
3349
        );
3350
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
3351
            // Now check if current password is correct
3352
            // For this, just check if it is possible to decrypt the privatekey
3353
            // And compare it to the one in session
3354
            try {
3355
                $privateKey = decryptPrivateKey($post_current_pwd, $userData['private_key']);
3356
            } catch (Exception $e) {
3357
                return prepareExchangedData(
3358
                    array(
3359
                        'error' => true,
3360
                        'message' => $lang->get('bad_password'),
3361
                    ),
3362
                    'encode'
3363
                );
3364
            }
3365
3366
            $lang = new Language($session->get('user-language') ?? 'english');
3367
3368
            if ($session->get('user-private_key') === $privateKey) {
3369
                // Encrypt it with new password
3370
                $hashedPrivateKey = encryptPrivateKey($post_new_pwd, $privateKey);
3371
3372
                // Generate new hash for auth password
3373
                $passwordManager = new PasswordManager();
3374
3375
                // Prepare variables
3376
                $newPw = $passwordManager->hashPassword($post_new_pwd);
3377
3378
                // Update user account
3379
                DB::update(
3380
                    prefixTable('users'),
3381
                    array(
3382
                        'private_key' => $hashedPrivateKey,
3383
                        'pw' => $newPw,
3384
                        'special' => 'none',
3385
                        'last_pw_change' => time(),
3386
                    ),
3387
                    'id = %i',
3388
                    $post_user_id
3389
                );
3390
3391
                $session->set('user-private_key', $privateKey);
3392
3393
                return prepareExchangedData(
3394
                    array(
3395
                        'error' => false,
3396
                        'message' => $lang->get('done'),'',
3397
                    ),
3398
                    'encode'
3399
                );
3400
            }
3401
            
3402
            // ERROR
3403
            return prepareExchangedData(
3404
                array(
3405
                    'error' => true,
3406
                    'message' => $lang->get('bad_password'),
3407
                ),
3408
                'encode'
3409
            );
3410
        }
3411
    }
3412
        
3413
    return prepareExchangedData(
3414
        array(
3415
            'error' => true,
3416
            'message' => $lang->get('error_no_user'),
3417
        ),
3418
        'encode'
3419
    );
3420
}
3421
3422
/**
3423
 * Change user LDAP auth password
3424
 *
3425
 * @param integer $post_user_id
3426
 * @param string $post_previous_pwd
3427
 * @param string $post_current_pwd
3428
 * @param array $SETTINGS
3429
 * @return string
3430
 */            
3431
function changeUserLDAPAuthenticationPassword(
3432
    int $post_user_id,
3433
    string $post_previous_pwd,
3434
    string $post_current_pwd,
3435
    array $SETTINGS
3436
)
3437
{
3438
    $session = SessionManager::getSession();
3439
    // Load user's language
3440
    $lang = new Language($session->get('user-language') ?? 'english');
3441
    
3442
    if (isUserIdValid($post_user_id) === true) {
3443
        // Get user info
3444
        $userData = DB::queryFirstRow(
3445
            'SELECT auth_type, login, private_key, special
3446
            FROM ' . prefixTable('users') . '
3447
            WHERE id = %i',
3448
            $post_user_id
3449
        );
3450
        
3451
        if (DB::count() > 0 && empty($userData['private_key']) === false) {
3452
            // Now check if current password is correct (only if not ldap)
3453
            if ($userData['auth_type'] === 'ldap' && $userData['special'] === 'auth-pwd-change') {
3454
                // As it is a change for an LDAP user
3455
                
3456
                // Now check if current password is correct
3457
                // For this, just check if it is possible to decrypt the privatekey
3458
                // And compare it to the one in session
3459
                $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3460
3461
                // Encrypt it with new password
3462
                $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3463
3464
                // Update user account
3465
                DB::update(
3466
                    prefixTable('users'),
3467
                    array(
3468
                        'private_key' => $hashedPrivateKey,
3469
                        'special' => 'none',
3470
                    ),
3471
                    'id = %i',
3472
                    $post_user_id
3473
                );
3474
3475
                $session->set('user-private_key', $privateKey);
3476
3477
                return prepareExchangedData(
3478
                    array(
3479
                        'error' => false,
3480
                        'message' => $lang->get('done'),'',
3481
                    ),
3482
                    'encode'
3483
                );
3484
            }
3485
3486
            // For this, just check if it is possible to decrypt the privatekey
3487
            // And try to decrypt one existing key
3488
            $privateKey = decryptPrivateKey($post_previous_pwd, $userData['private_key']);
3489
3490
            if (empty($privateKey) === true) {
3491
                return prepareExchangedData(
3492
                    array(
3493
                        'error' => true,
3494
                        'message' => $lang->get('password_is_not_correct'),
3495
                    ),
3496
                    'encode'
3497
                );
3498
            }
3499
3500
            // Get one itemKey from current user
3501
            $currentUserKey = DB::queryFirstRow(
3502
                'SELECT share_key, increment_id
3503
                FROM ' . prefixTable('sharekeys_items') . '
3504
                WHERE user_id = %i
3505
                LIMIT 1',
3506
                $post_user_id
3507
            );
3508
3509
            if (is_countable($currentUserKey) && count($currentUserKey) > 0) {
3510
                // Decrypt itemkey with user key
3511
                // use old password to decrypt private_key
3512
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $privateKey);
3513
                
3514
                if (empty(base64_decode($itemKey)) === false) {
3515
                    // GOOD password
3516
                    // Encrypt it with current password
3517
                    $hashedPrivateKey = encryptPrivateKey($post_current_pwd, $privateKey);
3518
                    
3519
                    // Update user account
3520
                    DB::update(
3521
                        prefixTable('users'),
3522
                        array(
3523
                            'private_key' => $hashedPrivateKey,
3524
                            'special' => 'none',
3525
                        ),
3526
                        'id = %i',
3527
                        $post_user_id
3528
                    );
3529
                    
3530
                    $lang = new Language($session->get('user-language') ?? 'english');
3531
                    $session->set('user-private_key', $privateKey);
3532
3533
                    return prepareExchangedData(
3534
                        array(
3535
                            'error' => false,
3536
                            'message' => $lang->get('done'),
3537
                        ),
3538
                        'encode'
3539
                    );
3540
                }
3541
            }
3542
            
3543
            // ERROR
3544
            return prepareExchangedData(
3545
                array(
3546
                    'error' => true,
3547
                    'message' => $lang->get('bad_password'),
3548
                ),
3549
                'encode'
3550
            );
3551
        }
3552
    }
3553
3554
    // ERROR
3555
    return prepareExchangedData(
3556
        array(
3557
            'error' => true,
3558
            'message' => $lang->get('error_no_user'),
3559
        ),
3560
        'encode'
3561
    );
3562
}
3563
3564
/**
3565
 * Change user LDAP auth password
3566
 *
3567
 * @param integer $post_user_id
3568
 * @param string $post_current_pwd
3569
 * @param string $post_new_pwd
3570
 * @param array $SETTINGS
3571
 * @return string
3572
 */
3573
function increaseSessionDuration(
3574
    int $duration
3575
): string
3576
{
3577
    $session = SessionManager::getSession();
3578
    // check if session is not already expired.
3579
    if ($session->get('user-session_duration') > time()) {
3580
        // Calculate end of session
3581
        $session->set('user-session_duration', (int) $session->get('user-session_duration') + $duration);
3582
        // Update table
3583
        DB::update(
3584
            prefixTable('users'),
3585
            array(
3586
                'session_end' => $session->get('user-session_duration'),
3587
            ),
3588
            'id = %i',
3589
            $session->get('user-id')
3590
        );
3591
        // Return data
3592
        return '[{"new_value":"' . $session->get('user-session_duration') . '"}]';
3593
    }
3594
    
3595
    return '[{"new_value":"expired"}]';
3596
}
3597
3598
function generateAnOTP(string $label, bool $with_qrcode = false, string $secretKey = ''): string
3599
{
3600
    // generate new secret
3601
    $tfa = new TwoFactorAuth();
3602
    if ($secretKey === '') {
3603
        $secretKey = $tfa->createSecret();
3604
    }
3605
3606
    // generate new QR
3607
    if ($with_qrcode === true) {
3608
        $qrcode = $tfa->getQRCodeImageAsDataUri(
3609
            $label,
3610
            $secretKey
3611
        );
3612
    }
3613
3614
    // ERROR
3615
    return prepareExchangedData(
3616
        array(
3617
            'error' => false,
3618
            'message' => '',
3619
            'secret' => $secretKey,
3620
            'qrcode' => $qrcode ?? '',
3621
        ),
3622
        'encode'
3623
    );
3624
}