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

handleUserKeys()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 81
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 33
nc 12
nop 12
dl 0
loc 81
rs 8.1475
c 2
b 0
f 0

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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.functions.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 LdapRecord\Connection;
33
use ForceUTF8\Encoding;
34
use Elegant\Sanitizer\Sanitizer;
35
use voku\helper\AntiXSS;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use TeampassClasses\SessionManager\SessionManager;
39
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
40
use TeampassClasses\Language\Language;
41
use TeampassClasses\NestedTree\NestedTree;
42
use Defuse\Crypto\Key;
43
use Defuse\Crypto\Crypto;
44
use Defuse\Crypto\KeyProtectedByPassword;
45
use Defuse\Crypto\File as CryptoFile;
46
use Defuse\Crypto\Exception as CryptoException;
47
use Elegant\Sanitizer\Filters\Uppercase;
48
use PHPMailer\PHPMailer\PHPMailer;
49
use TeampassClasses\PasswordManager\PasswordManager;
50
use Symfony\Component\Process\Exception\ProcessFailedException;
51
use Symfony\Component\Process\Process;
52
use Symfony\Component\Process\PhpExecutableFinder;
53
use TeampassClasses\Encryption\Encryption;
54
use TeampassClasses\ConfigManager\ConfigManager;
55
use TeampassClasses\EmailService\EmailService;
56
use TeampassClasses\EmailService\EmailSettings;
57
58
header('Content-type: text/html; charset=utf-8');
59
header('Cache-Control: no-cache, must-revalidate');
60
61
loadClasses('DB');
62
$session = SessionManager::getSession();
63
64
// Load config if $SETTINGS not defined
65
$configManager = new ConfigManager($session);
66
$SETTINGS = $configManager->getAllSettings();
67
68
/**
69
 * genHash().
70
 *
71
 * Generate a hash for user login
72
 *
73
 * @param string $password What password
74
 * @param string $cost     What cost
75
 *
76
 * @return string|void
77
 */
78
/* TODO - Remove this function
79
function bCrypt(
80
    string $password,
81
    string $cost
82
): ?string
83
{
84
    $salt = sprintf('$2y$%02d$', $cost);
85
    if (function_exists('openssl_random_pseudo_bytes')) {
86
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
87
    } else {
88
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
89
        for ($i = 0; $i < 22; ++$i) {
90
            $salt .= $chars[mt_rand(0, 63)];
91
        }
92
    }
93
94
    return crypt($password, $salt);
95
}
96
*/
97
98
/**
99
 * Checks if a string is hex encoded
100
 *
101
 * @param string $str
102
 * @return boolean
103
 */
104
function isHex(string $str): bool
105
{
106
    if (str_starts_with(strtolower($str), '0x')) {
107
        $str = substr($str, 2);
108
    }
109
110
    return ctype_xdigit($str);
111
}
112
113
/**
114
 * Defuse cryption function.
115
 *
116
 * @param string $message   what to de/crypt
117
 * @param string $ascii_key key to use
118
 * @param string $type      operation to perform
119
 * @param array  $SETTINGS  Teampass settings
120
 *
121
 * @return array
122
 */
123
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
124
{
125
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
126
    $err = false;
127
    
128
    // convert KEY
129
    $key = Key::loadFromAsciiSafeString($ascii_key);
130
    try {
131
        if ($type === 'encrypt') {
132
            $text = Crypto::encrypt($message, $key);
133
        } elseif ($type === 'decrypt') {
134
            $text = Crypto::decrypt($message, $key);
135
        }
136
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
137
        error_log('TEAMPASS-Error-Wrong key or modified ciphertext: ' . $ex->getMessage());
138
        $err = 'wrong_key_or_modified_ciphertext';
139
    } catch (CryptoException\BadFormatException $ex) {
140
        error_log('TEAMPASS-Error-Bad format exception: ' . $ex->getMessage());
141
        $err = 'bad_format';
142
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
143
        error_log('TEAMPASS-Error-Environment: ' . $ex->getMessage());
144
        $err = 'environment_error';
145
    } catch (CryptoException\IOException $ex) {
146
        error_log('TEAMPASS-Error-IO: ' . $ex->getMessage());
147
        $err = 'io_error';
148
    } catch (Exception $ex) {
149
        error_log('TEAMPASS-Error-Unexpected exception: ' . $ex->getMessage());
150
        $err = 'unexpected_error';
151
    }
152
153
    return [
154
        'string' => $text ?? '',
155
        'error' => $err,
156
    ];
157
}
158
159
/**
160
 * Generating a defuse key.
161
 *
162
 * @return string
163
 */
164
function defuse_generate_key()
165
{
166
    $key = Key::createNewRandomKey();
167
    $key = $key->saveToAsciiSafeString();
168
    return $key;
169
}
170
171
/**
172
 * Generate a Defuse personal key.
173
 *
174
 * @param string $psk psk used
175
 *
176
 * @return string
177
 */
178
function defuse_generate_personal_key(string $psk): string
179
{
180
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
181
    return $protected_key->saveToAsciiSafeString(); // save this in user table
182
}
183
184
/**
185
 * Validate persoanl key with defuse.
186
 *
187
 * @param string $psk                   the user's psk
188
 * @param string $protected_key_encoded special key
189
 *
190
 * @return string
191
 */
192
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
193
{
194
    try {
195
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
196
        $user_key = $protected_key_encoded->unlockKey($psk);
197
        $user_key_encoded = $user_key->saveToAsciiSafeString();
198
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
199
        return 'Error - Major issue as the encryption is broken.';
200
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
201
        return 'Error - The saltkey is not the correct one.';
202
    }
203
204
    return $user_key_encoded;
205
    // store it in session once user has entered his psk
206
}
207
208
/**
209
 * Decrypt a defuse string if encrypted.
210
 *
211
 * @param string $value Encrypted string
212
 *
213
 * @return string Decrypted string
214
 */
215
function defuseReturnDecrypted(string $value, $SETTINGS): string
216
{
217
    if (substr($value, 0, 3) === 'def') {
218
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
219
    }
220
221
    return $value;
222
}
223
224
/**
225
 * Trims a string depending on a specific string.
226
 *
227
 * @param string|array $chaine  what to trim
228
 * @param string       $element trim on what
229
 *
230
 * @return string
231
 */
232
function trimElement($chaine, string $element): string
233
{
234
    if (! empty($chaine)) {
235
        if (is_array($chaine) === true) {
236
            $chaine = implode(';', $chaine);
237
        }
238
        $chaine = trim($chaine);
239
        if (substr($chaine, 0, 1) === $element) {
240
            $chaine = substr($chaine, 1);
241
        }
242
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
243
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
244
        }
245
    }
246
247
    return $chaine;
248
}
249
250
/**
251
 * Permits to suppress all "special" characters from string.
252
 *
253
 * @param string $string  what to clean
254
 * @param bool   $special use of special chars?
255
 *
256
 * @return string
257
 */
258
function cleanString(string $string, bool $special = false): string
259
{
260
    // Create temporary table for special characters escape
261
    $tabSpecialChar = [];
262
    for ($i = 0; $i <= 31; ++$i) {
263
        $tabSpecialChar[] = chr($i);
264
    }
265
    array_push($tabSpecialChar, '<br />');
266
    if ((int) $special === 1) {
267
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
268
    }
269
270
    return str_replace($tabSpecialChar, "\n", $string);
271
}
272
273
/**
274
 * Erro manager for DB.
275
 *
276
 * @param array $params output from query
277
 *
278
 * @return void
279
 */
280
function db_error_handler(array $params): void
281
{
282
    echo 'Error: ' . $params['error'] . "<br>\n";
283
    echo 'Query: ' . $params['query'] . "<br>\n";
284
    throw new Exception('Error - Query', 1);
285
}
286
287
/**
288
 * Identify user's rights
289
 *
290
 * @param string|array $groupesVisiblesUser  [description]
291
 * @param string|array $groupesInterditsUser [description]
292
 * @param string       $isAdmin              [description]
293
 * @param string       $idFonctions          [description]
294
 *
295
 * @return bool
296
 */
297
function identifyUserRights(
298
    $groupesVisiblesUser,
299
    $groupesInterditsUser,
300
    $isAdmin,
301
    $idFonctions,
302
    $SETTINGS
303
) {
304
    $session = SessionManager::getSession();
305
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
306
307
    // Check if user is ADMINISTRATOR    
308
    (int) $isAdmin === 1 ?
309
        identAdmin(
310
            $idFonctions,
311
            $SETTINGS, /** @scrutinizer ignore-type */
312
            $tree
313
        )
314
        :
315
        identUser(
316
            $groupesVisiblesUser,
317
            $groupesInterditsUser,
318
            $idFonctions,
319
            $SETTINGS, /** @scrutinizer ignore-type */
320
            $tree
321
        );
322
323
    // update user's timestamp
324
    DB::update(
325
        prefixTable('users'),
326
        [
327
            'timestamp' => time(),
328
        ],
329
        'id=%i',
330
        $session->get('user-id')
331
    );
332
333
    return true;
334
}
335
336
/**
337
 * Identify administrator.
338
 *
339
 * @param string $idFonctions Roles of user
340
 * @param array  $SETTINGS    Teampass settings
341
 * @param object $tree        Tree of folders
342
 *
343
 * @return bool
344
 */
345
function identAdmin($idFonctions, $SETTINGS, $tree)
346
{
347
    
348
    $session = SessionManager::getSession();
349
    $groupesVisibles = [];
350
    $session->set('user-personal_folders', []);
351
    $session->set('user-accessible_folders', []);
352
    $session->set('user-no_access_folders', []);
353
    $session->set('user-personal_visible_folders', []);
354
    $session->set('user-read_only_folders', []);
355
    $session->set('system-list_restricted_folders_for_items', []);
356
    $session->set('system-list_folders_editable_by_role', []);
357
    $session->set('user-list_folders_limited', []);
358
    $session->set('user-forbiden_personal_folders', []);
359
    
360
    // Get list of Folders
361
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
362
    foreach ($rows as $record) {
363
        array_push($groupesVisibles, $record['id']);
364
    }
365
    $session->set('user-accessible_folders', $groupesVisibles);
366
    $session->set('user-all_non_personal_folders', $groupesVisibles);
367
368
    // get complete list of ROLES
369
    $tmp = explode(';', $idFonctions);
370
    $rows = DB::query(
371
        'SELECT * FROM ' . prefixTable('roles_title') . '
372
        ORDER BY title ASC'
373
    );
374
    foreach ($rows as $record) {
375
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
376
            array_push($tmp, $record['id']);
377
        }
378
    }
379
    $session->set('user-roles', implode(';', $tmp));
380
    $session->set('user-admin', 1);
381
    // Check if admin has created Folders and Roles
382
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
383
    $session->set('user-nb_folders', DB::count());
384
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
385
    $session->set('user-nb_roles', DB::count());
386
387
    return true;
388
}
389
390
/**
391
 * Permits to convert an element to array.
392
 *
393
 * @param string|array $element Any value to be returned as array
394
 *
395
 * @return array
396
 */
397
function convertToArray($element): ?array
398
{
399
    if (is_string($element) === true) {
400
        if (empty($element) === true) {
401
            return [];
402
        }
403
        return explode(
404
            ';',
405
            trimElement($element, ';')
406
        );
407
    }
408
    return $element;
409
}
410
411
/**
412
 * Defines the rights the user has.
413
 *
414
 * @param string|array $allowedFolders  Allowed folders
415
 * @param string|array $noAccessFolders Not allowed folders
416
 * @param string|array $userRoles       Roles of user
417
 * @param array        $SETTINGS        Teampass settings
418
 * @param object       $tree            Tree of folders
419
 * 
420
 * @return bool
421
 */
422
function identUser(
423
    $allowedFolders,
424
    $noAccessFolders,
425
    $userRoles,
426
    array $SETTINGS,
427
    object $tree
428
) {
429
    $session = SessionManager::getSession();
430
    // Init
431
    $session->set('user-accessible_folders', []);
432
    $session->set('user-personal_folders', []);
433
    $session->set('user-no_access_folders', []);
434
    $session->set('user-personal_visible_folders', []);
435
    $session->set('user-read_only_folders', []);
436
    $session->set('user-user-roles', $userRoles);
437
    $session->set('user-admin', 0);
438
    // init
439
    $personalFolders = [];
440
    $readOnlyFolders = [];
441
    $noAccessPersonalFolders = [];
442
    $restrictedFoldersForItems = [];
443
    $foldersLimited = [];
444
    $foldersLimitedFull = [];
445
    $allowedFoldersByRoles = [];
446
    $globalsUserId = $session->get('user-id');
447
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
448
    // Ensure consistency in array format
449
    $noAccessFolders = convertToArray($noAccessFolders);
450
    $userRoles = convertToArray($userRoles);
451
    $allowedFolders = convertToArray($allowedFolders);
452
    $session->set('user-allowed_folders_by_definition', $allowedFolders);
453
    
454
    // Get list of folders depending on Roles
455
    $arrays = identUserGetFoldersFromRoles(
456
        $userRoles,
457
        $allowedFoldersByRoles,
458
        $readOnlyFolders,
459
        $allowedFolders
460
    );
461
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
462
    $readOnlyFolders = $arrays['readOnlyFolders'];
463
464
    // Does this user is allowed to see other items
465
    $inc = 0;
466
    $rows = DB::query(
467
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
468
            WHERE restricted_to LIKE %ss AND inactif = %s'.
469
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
470
        $globalsUserId,
471
        '0'
472
    );
473
    foreach ($rows as $record) {
474
        // Exclude restriction on item if folder is fully accessible
475
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
476
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
477
            ++$inc;
478
        //}
479
    }
480
481
    // Check for the users roles if some specific rights exist on items
482
    $rows = DB::query(
483
        'SELECT i.id_tree, r.item_id
484
        FROM ' . prefixTable('items') . ' as i
485
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
486
        WHERE i.id_tree <> "" '.
487
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
488
        'ORDER BY i.id_tree ASC',
489
        $userRoles
490
    );
491
    $inc = 0;
492
    foreach ($rows as $record) {
493
        //if (isset($record['id_tree'])) {
494
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
495
            array_push($foldersLimitedFull, $record['id_tree']);
496
            ++$inc;
497
        //}
498
    }
499
500
    // Get list of Personal Folders
501
    $arrays = identUserGetPFList(
502
        $globalsPersonalFolders,
503
        $allowedFolders,
504
        $globalsUserId,
505
        $personalFolders,
506
        $noAccessPersonalFolders,
507
        $foldersLimitedFull,
508
        $allowedFoldersByRoles,
509
        array_keys($restrictedFoldersForItems),
510
        $readOnlyFolders,
511
        $noAccessFolders,
512
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
513
        $tree
514
    );
515
    $allowedFolders = $arrays['allowedFolders'];
516
    $personalFolders = $arrays['personalFolders'];
517
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
518
519
    // Return data
520
    $session->set('user-all_non_personal_folders', $allowedFolders);
521
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
522
    $session->set('user-read_only_folders', $readOnlyFolders);
523
    $session->set('user-no_access_folders', $noAccessFolders);
524
    $session->set('user-personal_folders', $personalFolders);
525
    $session->set('user-list_folders_limited', $foldersLimited);
526
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
527
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
528
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
529
    $session->set(
530
        'all_folders_including_no_access',
531
        array_unique(array_merge(
532
            $allowedFolders,
533
            $personalFolders,
534
            $noAccessFolders,
535
            $readOnlyFolders
536
        ), SORT_NUMERIC)
537
    );
538
    // Folders and Roles numbers
539
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
540
    $session->set('user-nb_folders', DB::count());
541
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
542
    $session->set('user-nb_roles', DB::count());
543
    // check if change proposals on User's items
544
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
545
        $countNewItems = DB::query(
546
            'SELECT COUNT(*)
547
            FROM ' . prefixTable('items_change') . ' AS c
548
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
549
            WHERE i.action = %s AND i.id_user = %i',
550
            'at_creation',
551
            $globalsUserId
552
        );
553
        $session->set('user-nb_item_change_proposals', $countNewItems);
554
    } else {
555
        $session->set('user-nb_item_change_proposals', 0);
556
    }
557
558
    return true;
559
}
560
561
/**
562
 * Get list of folders depending on Roles
563
 * 
564
 * @param array $userRoles
565
 * @param array $allowedFoldersByRoles
566
 * @param array $readOnlyFolders
567
 * @param array $allowedFolders
568
 * 
569
 * @return array
570
 */
571
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
572
{
573
    $rows = DB::query(
574
        'SELECT *
575
        FROM ' . prefixTable('roles_values') . '
576
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
577
        ['W', 'ND', 'NE', 'NDNE', 'R'],
578
        $userRoles,
579
    );
580
    foreach ($rows as $record) {
581
        if ($record['type'] === 'R') {
582
            array_push($readOnlyFolders, $record['folder_id']);
583
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
584
            array_push($allowedFoldersByRoles, $record['folder_id']);
585
        }
586
    }
587
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
588
    $readOnlyFolders = array_unique($readOnlyFolders);
589
    
590
    // Clean arrays
591
    foreach ($allowedFoldersByRoles as $value) {
592
        $key = array_search($value, $readOnlyFolders);
593
        if ($key !== false) {
594
            unset($readOnlyFolders[$key]);
595
        }
596
    }
597
    return [
598
        'readOnlyFolders' => $readOnlyFolders,
599
        'allowedFoldersByRoles' => $allowedFoldersByRoles
600
    ];
601
}
602
603
/**
604
 * Get list of Personal Folders
605
 * 
606
 * @param int $globalsPersonalFolders
607
 * @param array $allowedFolders
608
 * @param int $globalsUserId
609
 * @param array $personalFolders
610
 * @param array $noAccessPersonalFolders
611
 * @param array $foldersLimitedFull
612
 * @param array $allowedFoldersByRoles
613
 * @param array $restrictedFoldersForItems
614
 * @param array $readOnlyFolders
615
 * @param array $noAccessFolders
616
 * @param int $enablePfFeature
617
 * @param object $tree
618
 * 
619
 * @return array
620
 */
621
function identUserGetPFList(
622
    $globalsPersonalFolders,
623
    $allowedFolders,
624
    $globalsUserId,
625
    $personalFolders,
626
    $noAccessPersonalFolders,
627
    $foldersLimitedFull,
628
    $allowedFoldersByRoles,
629
    $restrictedFoldersForItems,
630
    $readOnlyFolders,
631
    $noAccessFolders,
632
    $enablePfFeature,
633
    $tree
634
)
635
{
636
    if (
637
        (int) $enablePfFeature === 1
638
        && (int) $globalsPersonalFolders === 1
639
    ) {
640
        $persoFld = DB::queryfirstrow(
641
            'SELECT id
642
            FROM ' . prefixTable('nested_tree') . '
643
            WHERE title = %s AND personal_folder = %i'.
644
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
645
            $globalsUserId,
646
            1
647
        );
648
        if (empty($persoFld['id']) === false) {
649
            array_push($personalFolders, $persoFld['id']);
650
            array_push($allowedFolders, $persoFld['id']);
651
            // get all descendants
652
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
653
            foreach ($ids as $id) {
654
                //array_push($allowedFolders, $id);
655
                array_push($personalFolders, $id);
656
            }
657
        }
658
    }
659
    
660
    // Exclude all other PF
661
    $where = new WhereClause('and');
662
    $where->add('personal_folder=%i', 1);
663
    if (count($personalFolders) > 0) {
664
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
665
    }
666
    if (
667
        (int) $enablePfFeature === 1
668
        && (int) $globalsPersonalFolders === 1
669
    ) {
670
        $where->add('title=%s', $globalsUserId);
671
        $where->negateLast();
672
    }
673
    $persoFlds = DB::query(
674
        'SELECT id
675
        FROM ' . prefixTable('nested_tree') . '
676
        WHERE %l',
677
        $where
678
    );
679
    foreach ($persoFlds as $persoFldId) {
680
        array_push($noAccessPersonalFolders, $persoFldId['id']);
681
    }
682
683
    // All folders visibles
684
    $allowedFolders = array_unique(array_merge(
685
        $allowedFolders,
686
        $foldersLimitedFull,
687
        $allowedFoldersByRoles,
688
        $restrictedFoldersForItems,
689
        $readOnlyFolders
690
    ), SORT_NUMERIC);
691
    // Exclude from allowed folders all the specific user forbidden folders
692
    if (count($noAccessFolders) > 0) {
693
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
694
    }
695
696
    return [
697
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
698
        'personalFolders' => $personalFolders,
699
        'noAccessPersonalFolders' => $noAccessPersonalFolders
700
    ];
701
}
702
703
704
/**
705
 * Update the CACHE table.
706
 *
707
 * @param string $action   What to do
708
 * @param array  $SETTINGS Teampass settings
709
 * @param int    $ident    Ident format
710
 * 
711
 * @return void
712
 */
713
function updateCacheTable(string $action, ?int $ident = null): void
714
{
715
    if ($action === 'reload') {
716
        // Rebuild full cache table
717
        cacheTableRefresh();
718
    } elseif ($action === 'update_value' && is_null($ident) === false) {
719
        // UPDATE an item
720
        cacheTableUpdate($ident);
721
    } elseif ($action === 'add_value' && is_null($ident) === false) {
722
        // ADD an item
723
        cacheTableAdd($ident);
724
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
725
        // DELETE an item
726
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
727
    }
728
}
729
730
/**
731
 * Cache table - refresh.
732
 *
733
 * @return void
734
 */
735
function cacheTableRefresh(): void
736
{
737
    // Load class DB
738
    loadClasses('DB');
739
740
    //Load Tree
741
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
742
    // truncate table
743
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
744
    // reload date
745
    $rows = DB::query(
746
        'SELECT *
747
        FROM ' . prefixTable('items') . ' as i
748
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
749
        AND l.action = %s
750
        AND i.inactif = %i',
751
        'at_creation',
752
        0
753
    );
754
    foreach ($rows as $record) {
755
        if (empty($record['id_tree']) === false) {
756
            // Get all TAGS
757
            $tags = '';
758
            $itemTags = DB::query(
759
                'SELECT tag
760
                FROM ' . prefixTable('tags') . '
761
                WHERE item_id = %i AND tag != ""',
762
                $record['id']
763
            );
764
            foreach ($itemTags as $itemTag) {
765
                $tags .= $itemTag['tag'] . ' ';
766
            }
767
768
            // Get renewal period
769
            $resNT = DB::queryfirstrow(
770
                'SELECT renewal_period
771
                FROM ' . prefixTable('nested_tree') . '
772
                WHERE id = %i',
773
                $record['id_tree']
774
            );
775
            // form id_tree to full foldername
776
            $folder = [];
777
            $arbo = $tree->getPath($record['id_tree'], true);
778
            foreach ($arbo as $elem) {
779
                // Check if title is the ID of a user
780
                if (is_numeric($elem->title) === true) {
781
                    // Is this a User id?
782
                    $user = DB::queryfirstrow(
783
                        'SELECT id, login
784
                        FROM ' . prefixTable('users') . '
785
                        WHERE id = %i',
786
                        $elem->title
787
                    );
788
                    if (count($user) > 0) {
789
                        $elem->title = $user['login'];
790
                    }
791
                }
792
                // Build path
793
                array_push($folder, stripslashes($elem->title));
794
            }
795
            // store data
796
            DB::insert(
797
                prefixTable('cache'),
798
                [
799
                    'id' => $record['id'],
800
                    'label' => $record['label'],
801
                    'description' => $record['description'] ?? '',
802
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
803
                    'tags' => $tags,
804
                    'id_tree' => $record['id_tree'],
805
                    'perso' => $record['perso'],
806
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
807
                    'login' => $record['login'] ?? '',
808
                    'folder' => implode(' > ', $folder),
809
                    'author' => $record['id_user'],
810
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
811
                    'timestamp' => $record['date'],
812
                ]
813
            );
814
        }
815
    }
816
}
817
818
/**
819
 * Cache table - update existing value.
820
 *
821
 * @param int    $ident    Ident format
822
 * 
823
 * @return void
824
 */
825
function cacheTableUpdate(?int $ident = null): void
826
{
827
    $session = SessionManager::getSession();
828
    loadClasses('DB');
829
830
    //Load Tree
831
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
832
    // get new value from db
833
    $data = DB::queryfirstrow(
834
        'SELECT label, description, id_tree, perso, restricted_to, login, url
835
        FROM ' . prefixTable('items') . '
836
        WHERE id=%i',
837
        $ident
838
    );
839
    // Get all TAGS
840
    $tags = '';
841
    $itemTags = DB::query(
842
        'SELECT tag
843
            FROM ' . prefixTable('tags') . '
844
            WHERE item_id = %i AND tag != ""',
845
        $ident
846
    );
847
    foreach ($itemTags as $itemTag) {
848
        $tags .= $itemTag['tag'] . ' ';
849
    }
850
    // form id_tree to full foldername
851
    $folder = [];
852
    $arbo = $tree->getPath($data['id_tree'], true);
853
    foreach ($arbo as $elem) {
854
        // Check if title is the ID of a user
855
        if (is_numeric($elem->title) === true) {
856
            // Is this a User id?
857
            $user = DB::queryfirstrow(
858
                'SELECT id, login
859
                FROM ' . prefixTable('users') . '
860
                WHERE id = %i',
861
                $elem->title
862
            );
863
            if (count($user) > 0) {
864
                $elem->title = $user['login'];
865
            }
866
        }
867
        // Build path
868
        array_push($folder, stripslashes($elem->title));
869
    }
870
    // finaly update
871
    DB::update(
872
        prefixTable('cache'),
873
        [
874
            'label' => $data['label'],
875
            'description' => $data['description'],
876
            'tags' => $tags,
877
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
878
            'id_tree' => $data['id_tree'],
879
            'perso' => $data['perso'],
880
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
881
            'login' => $data['login'] ?? '',
882
            'folder' => implode(' » ', $folder),
883
            'author' => $session->get('user-id'),
884
        ],
885
        'id = %i',
886
        $ident
887
    );
888
}
889
890
/**
891
 * Cache table - add new value.
892
 *
893
 * @param int    $ident    Ident format
894
 * 
895
 * @return void
896
 */
897
function cacheTableAdd(?int $ident = null): void
898
{
899
    $session = SessionManager::getSession();
900
    $globalsUserId = $session->get('user-id');
901
902
    // Load class DB
903
    loadClasses('DB');
904
905
    //Load Tree
906
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
907
    // get new value from db
908
    $data = DB::queryFirstRow(
909
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
910
        FROM ' . prefixTable('items') . ' as i
911
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
912
        WHERE i.id = %i
913
        AND l.action = %s',
914
        $ident,
915
        'at_creation'
916
    );
917
    // Get all TAGS
918
    $tags = '';
919
    $itemTags = DB::query(
920
        'SELECT tag
921
            FROM ' . prefixTable('tags') . '
922
            WHERE item_id = %i AND tag != ""',
923
        $ident
924
    );
925
    foreach ($itemTags as $itemTag) {
926
        $tags .= $itemTag['tag'] . ' ';
927
    }
928
    // form id_tree to full foldername
929
    $folder = [];
930
    $arbo = $tree->getPath($data['id_tree'], true);
931
    foreach ($arbo as $elem) {
932
        // Check if title is the ID of a user
933
        if (is_numeric($elem->title) === true) {
934
            // Is this a User id?
935
            $user = DB::queryfirstrow(
936
                'SELECT id, login
937
                FROM ' . prefixTable('users') . '
938
                WHERE id = %i',
939
                $elem->title
940
            );
941
            if (count($user) > 0) {
942
                $elem->title = $user['login'];
943
            }
944
        }
945
        // Build path
946
        array_push($folder, stripslashes($elem->title));
947
    }
948
    // finaly update
949
    DB::insert(
950
        prefixTable('cache'),
951
        [
952
            'id' => $data['id'],
953
            'label' => $data['label'],
954
            'description' => $data['description'],
955
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
956
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
957
            'id_tree' => $data['id_tree'],
958
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
959
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
960
            'login' => $data['login'] ?? '',
961
            'folder' => implode(' » ', $folder),
962
            'author' => $globalsUserId,
963
            'timestamp' => $data['date'],
964
        ]
965
    );
966
}
967
968
/**
969
 * Do statistics.
970
 *
971
 * @param array $SETTINGS Teampass settings
972
 *
973
 * @return array
974
 */
975
function getStatisticsData(array $SETTINGS): array
976
{
977
    DB::query(
978
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
979
        0
980
    );
981
    $counter_folders = DB::count();
982
    DB::query(
983
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
984
        1
985
    );
986
    $counter_folders_perso = DB::count();
987
    DB::query(
988
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
989
        0
990
    );
991
    $counter_items = DB::count();
992
        DB::query(
993
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
994
        1
995
    );
996
    $counter_items_perso = DB::count();
997
        DB::query(
998
        'SELECT id FROM ' . prefixTable('users') . ' WHERE login NOT IN (%s, %s, %s)',
999
        'OTV', 'TP', 'API'
1000
    );
1001
    $counter_users = DB::count();
1002
        DB::query(
1003
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1004
        1
1005
    );
1006
    $admins = DB::count();
1007
    DB::query(
1008
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1009
        1
1010
    );
1011
    $managers = DB::count();
1012
    DB::query(
1013
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1014
        1
1015
    );
1016
    $readOnly = DB::count();
1017
    // list the languages
1018
    $usedLang = [];
1019
    $tp_languages = DB::query(
1020
        'SELECT name FROM ' . prefixTable('languages')
1021
    );
1022
    foreach ($tp_languages as $tp_language) {
1023
        DB::query(
1024
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1025
            $tp_language['name']
1026
        );
1027
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1028
    }
1029
1030
    // get list of ips
1031
    $usedIp = [];
1032
    $tp_ips = DB::query(
1033
        'SELECT user_ip FROM ' . prefixTable('users')
1034
    );
1035
    foreach ($tp_ips as $ip) {
1036
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1037
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1038
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1039
            $usedIp[$ip['user_ip']] = 1;
1040
        }
1041
    }
1042
1043
    return [
1044
        'error' => '',
1045
        'stat_phpversion' => phpversion(),
1046
        'stat_folders' => $counter_folders,
1047
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1048
        'stat_items' => $counter_items,
1049
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1050
        'stat_users' => $counter_users,
1051
        'stat_admins' => $admins,
1052
        'stat_managers' => $managers,
1053
        'stat_ro' => $readOnly,
1054
        'stat_kb' => $SETTINGS['enable_kb'],
1055
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1056
        'stat_fav' => $SETTINGS['enable_favourites'],
1057
        'stat_teampassversion' => TP_VERSION,
1058
        'stat_ldap' => $SETTINGS['ldap_mode'],
1059
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1060
        'stat_duo' => $SETTINGS['duo'],
1061
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1062
        'stat_api' => $SETTINGS['api'],
1063
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1064
        'stat_syslog' => $SETTINGS['syslog_enable'],
1065
        'stat_2fa' => $SETTINGS['google_authentication'],
1066
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1067
        'stat_mysqlversion' => DB::serverVersion(),
1068
        'stat_languages' => $usedLang,
1069
        'stat_country' => $usedIp,
1070
    ];
1071
}
1072
1073
/**
1074
 * Permits to prepare the way to send the email
1075
 * 
1076
 * @param string $subject       email subject
1077
 * @param string $body          email message
1078
 * @param string $email         email
1079
 * @param string $receiverName  Receiver name
1080
 * @param string $encryptedUserPassword      encryptedUserPassword
1081
 *
1082
 * @return void
1083
 */
1084
function prepareSendingEmail(
1085
    $subject,
1086
    $body,
1087
    $email,
1088
    $receiverName = '',
1089
    $encryptedUserPassword = ''
1090
): void 
1091
{
1092
    DB::insert(
1093
        prefixTable('background_tasks'),
1094
        array(
1095
            'created_at' => time(),
1096
            'process_type' => 'send_email',
1097
            'arguments' => json_encode([
1098
                'subject' => $subject,
1099
                'receivers' => $email,
1100
                'body' => $body,
1101
                'receiver_name' => $receiverName,
1102
                'encryptedUserPassword' => $encryptedUserPassword,
1103
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1104
        )
1105
    );
1106
}
1107
1108
/**
1109
 * Returns the email body.
1110
 *
1111
 * @param string $textMail Text for the email
1112
 */
1113
function emailBody(string $textMail): string
1114
{
1115
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1116
    w3.org/TR/html4/loose.dtd"><html>
1117
    <head><title>Email Template</title>
1118
    <style type="text/css">
1119
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1120
    </style></head>
1121
    <body style="-ms-text-size-adjust: none; size-adjust: none; margin: 0; padding: 10px 0; background-color: #f0f0f0;" bgcolor="#f0f0f0" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
1122
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1123
    <tr><td style="border-collapse: collapse;"><br>
1124
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1125
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1126
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1127
        </td></tr></table></td>
1128
    </tr>
1129
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1130
        <table width="600" cellpadding="0" cellspacing="0" border="0" class="container" bgcolor="#ffffff" style="border-spacing: 0; border-bottom: 1px solid #e0e0e0; box-shadow: 0 0 3px #ddd; color: #434343; font-family: Helvetica, Verdana, sans-serif;">
1131
        <tr><td class="container-padding" bgcolor="#ffffff" style="border-collapse: collapse; border-left: 1px solid #e0e0e0; background-color: #ffffff; padding-left: 30px; padding-right: 30px;">
1132
        <br><div style="float:right;">' .
1133
        $textMail .
1134
        '<br><br></td></tr></table>
1135
    </td></tr></table>
1136
    <br></body></html>';
1137
}
1138
1139
/**
1140
 * Convert date to timestamp.
1141
 *
1142
 * @param string $date        The date
1143
 * @param string $date_format Date format
1144
 *
1145
 * @return int
1146
 */
1147
function dateToStamp(string $date, string $date_format): int
1148
{
1149
    $date = date_parse_from_format($date_format, $date);
1150
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1151
        return mktime(
1152
            empty($date['hour']) === false ? $date['hour'] : 23,
1153
            empty($date['minute']) === false ? $date['minute'] : 59,
1154
            empty($date['second']) === false ? $date['second'] : 59,
1155
            $date['month'],
1156
            $date['day'],
1157
            $date['year']
1158
        );
1159
    }
1160
    return 0;
1161
}
1162
1163
/**
1164
 * Is this a date.
1165
 *
1166
 * @param string $date Date
1167
 *
1168
 * @return bool
1169
 */
1170
function isDate(string $date): bool
1171
{
1172
    return strtotime($date) !== false;
1173
}
1174
1175
/**
1176
 * Check if isUTF8().
1177
 *
1178
 * @param string|array $string Is the string
1179
 *
1180
 * @return int is the string in UTF8 format
1181
 */
1182
function isUTF8($string): int
1183
{
1184
    if (is_array($string) === true) {
1185
        $string = $string['string'];
1186
    }
1187
1188
    return preg_match(
1189
        '%^(?:
1190
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1191
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1192
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1193
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1194
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1195
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1196
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1197
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1198
        )*$%xs',
1199
        $string
1200
    );
1201
}
1202
1203
/**
1204
 * Prepare an array to UTF8 format before JSON_encode.
1205
 *
1206
 * @param array $array Array of values
1207
 *
1208
 * @return array
1209
 */
1210
function utf8Converter(array $array): array
1211
{
1212
    array_walk_recursive(
1213
        $array,
1214
        static function (&$item): void {
1215
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1216
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1217
            }
1218
        }
1219
    );
1220
    return $array;
1221
}
1222
1223
/**
1224
 * Permits to prepare data to be exchanged.
1225
 *
1226
 * @param array|string $data Text
1227
 * @param string       $type Parameter
1228
 * @param string       $key  Optional key
1229
 *
1230
 * @return string|array
1231
 */
1232
function prepareExchangedData($data, string $type, ?string $key = null)
1233
{
1234
    $session = SessionManager::getSession();
1235
    $key = empty($key) ? $session->get('key') : $key;
1236
    
1237
    // Perform
1238
    if ($type === 'encode' && is_array($data) === true) {
1239
        // json encoding
1240
        $data = json_encode(
1241
            $data,
1242
            JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1243
        );
1244
1245
        // Now encrypt
1246
        if ($session->get('encryptClientServer') === 1) {
1247
            $data = Encryption::encrypt(
1248
                $data,
1249
                $key
1250
            );
1251
        }
1252
1253
        return $data;
1254
    }
1255
1256
    if ($type === 'decode' && is_array($data) === false) {
1257
        // Decrypt if needed
1258
        if ($session->get('encryptClientServer') === 1) {
1259
            $data = (string) Encryption::decrypt(
1260
                (string) $data,
1261
                $key
1262
            );
1263
        } else {
1264
            // Double html encoding received
1265
            $data = html_entity_decode(html_entity_decode(/** @scrutinizer ignore-type */$data)); // @codeCoverageIgnore Is always a string (not an array)
1266
        }
1267
1268
        // Check if $data is a valid string before json_decode
1269
        if (is_string($data) && !empty($data)) {
1270
            // Return data array
1271
            return json_decode($data, true);
1272
        }
1273
    }
1274
1275
    return '';
1276
}
1277
1278
1279
/**
1280
 * Create a thumbnail.
1281
 *
1282
 * @param string  $src           Source
1283
 * @param string  $dest          Destination
1284
 * @param int $desired_width Size of width
1285
 * 
1286
 * @return void|string|bool
1287
 */
1288
function makeThumbnail(string $src, string $dest, int $desired_width)
1289
{
1290
    /* read the source image */
1291
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1292
        $source_image = imagecreatefrompng($src);
1293
        if ($source_image === false) {
1294
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1295
        }
1296
    } else {
1297
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1298
    }
1299
1300
    // Get height and width
1301
    $width = imagesx($source_image);
1302
    $height = imagesy($source_image);
1303
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1304
    $desired_height = (int) floor($height * $desired_width / $width);
1305
    /* create a new, "virtual" image */
1306
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1307
    if ($virtual_image === false) {
1308
        return false;
1309
    }
1310
    /* copy source image at a resized size */
1311
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1312
    /* create the physical thumbnail image to its destination */
1313
    imagejpeg($virtual_image, $dest);
1314
}
1315
1316
/**
1317
 * Check table prefix in SQL query.
1318
 *
1319
 * @param string $table Table name
1320
 * 
1321
 * @return string
1322
 */
1323
function prefixTable(string $table): string
1324
{
1325
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1326
    if (empty($safeTable) === false) {
1327
        // sanitize string
1328
        return $safeTable;
1329
    }
1330
    // stop error no table
1331
    return 'table_not_exists';
1332
}
1333
1334
/**
1335
 * GenerateCryptKey
1336
 *
1337
 * @param int     $size      Length
1338
 * @param bool $secure Secure
1339
 * @param bool $numerals Numerics
1340
 * @param bool $uppercase Uppercase letters
1341
 * @param bool $symbols Symbols
1342
 * @param bool $lowercase Lowercase
1343
 * 
1344
 * @return string
1345
 */
1346
function GenerateCryptKey(
1347
    int $size = 20,
1348
    bool $secure = false,
1349
    bool $numerals = false,
1350
    bool $uppercase = false,
1351
    bool $symbols = false,
1352
    bool $lowercase = false
1353
): string {
1354
    $generator = new ComputerPasswordGenerator();
1355
    $generator->setRandomGenerator(new Php7RandomGenerator());
1356
    
1357
    // Manage size
1358
    $generator->setLength((int) $size);
1359
    if ($secure === true) {
1360
        $generator->setSymbols(true);
1361
        $generator->setLowercase(true);
1362
        $generator->setUppercase(true);
1363
        $generator->setNumbers(true);
1364
    } else {
1365
        $generator->setLowercase($lowercase);
1366
        $generator->setUppercase($uppercase);
1367
        $generator->setNumbers($numerals);
1368
        $generator->setSymbols($symbols);
1369
    }
1370
1371
    return $generator->generatePasswords()[0];
1372
}
1373
1374
/**
1375
 * GenerateGenericPassword
1376
 *
1377
 * @param int     $size      Length
1378
 * @param bool $secure Secure
1379
 * @param bool $numerals Numerics
1380
 * @param bool $uppercase Uppercase letters
1381
 * @param bool $symbols Symbols
1382
 * @param bool $lowercase Lowercase
1383
 * @param array   $SETTINGS  SETTINGS
1384
 * 
1385
 * @return string
1386
 */
1387
function generateGenericPassword(
1388
    int $size,
1389
    bool $secure,
1390
    bool $lowercase,
1391
    bool $capitalize,
1392
    bool $numerals,
1393
    bool $symbols,
1394
    array $SETTINGS
1395
): string
1396
{
1397
    if ((int) $size > (int) $SETTINGS['pwd_maximum_length']) {
1398
        return prepareExchangedData(
1399
            array(
1400
                'error_msg' => 'Password length is too long! ',
1401
                'error' => 'true',
1402
            ),
1403
            'encode'
1404
        );
1405
    }
1406
    // Load libraries
1407
    $generator = new ComputerPasswordGenerator();
1408
    $generator->setRandomGenerator(new Php7RandomGenerator());
1409
1410
    // Manage size
1411
    $generator->setLength(($size <= 0) ? 10 : $size);
1412
1413
    if ($secure === true) {
1414
        $generator->setSymbols(true);
1415
        $generator->setLowercase(true);
1416
        $generator->setUppercase(true);
1417
        $generator->setNumbers(true);
1418
    } else {
1419
        $generator->setLowercase($lowercase);
1420
        $generator->setUppercase($capitalize);
1421
        $generator->setNumbers($numerals);
1422
        $generator->setSymbols($symbols);
1423
    }
1424
1425
    return prepareExchangedData(
1426
        array(
1427
            'key' => $generator->generatePasswords(),
1428
            'error' => '',
1429
        ),
1430
        'encode'
1431
    );
1432
}
1433
1434
/**
1435
 * Send sysLOG message
1436
 *
1437
 * @param string    $message
1438
 * @param string    $host
1439
 * @param int       $port
1440
 * @param string    $component
1441
 * 
1442
 * @return void
1443
*/
1444
function send_syslog($message, $host, $port, $component = 'teampass'): void
1445
{
1446
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1447
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1448
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1449
    socket_close($sock);
1450
}
1451
1452
/**
1453
 * Permits to log events into DB
1454
 *
1455
 * @param array  $SETTINGS Teampass settings
1456
 * @param string $type     Type
1457
 * @param string $label    Label
1458
 * @param string $who      Who
1459
 * @param string $login    Login
1460
 * @param string|int $field_1  Field
1461
 * 
1462
 * @return void
1463
 */
1464
function logEvents(
1465
    array $SETTINGS, 
1466
    string $type, 
1467
    string $label, 
1468
    string $who, 
1469
    ?string $login = null, 
1470
    $field_1 = null
1471
): void
1472
{
1473
    if (empty($who)) {
1474
        $who = getClientIpServer();
1475
    }
1476
1477
    // Load class DB
1478
    loadClasses('DB');
1479
1480
    DB::insert(
1481
        prefixTable('log_system'),
1482
        [
1483
            'type' => $type,
1484
            'date' => time(),
1485
            'label' => $label,
1486
            'qui' => $who,
1487
            'field_1' => $field_1 === null ? '' : $field_1,
1488
        ]
1489
    );
1490
    // If SYSLOG
1491
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1492
        if ($type === 'user_mngt') {
1493
            send_syslog(
1494
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1495
                $SETTINGS['syslog_host'],
1496
                $SETTINGS['syslog_port'],
1497
                'teampass'
1498
            );
1499
        } else {
1500
            send_syslog(
1501
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1502
                $SETTINGS['syslog_host'],
1503
                $SETTINGS['syslog_port'],
1504
                'teampass'
1505
            );
1506
        }
1507
    }
1508
}
1509
1510
/**
1511
 * Log events.
1512
 *
1513
 * @param array  $SETTINGS        Teampass settings
1514
 * @param int    $item_id         Item id
1515
 * @param string $item_label      Item label
1516
 * @param int    $id_user         User id
1517
 * @param string $action          Code for reason
1518
 * @param string $login           User login
1519
 * @param string $raison          Code for reason
1520
 * @param string $encryption_type Encryption on
1521
 * @param string $time Encryption Time
1522
 * @param string $old_value       Old value
1523
 * 
1524
 * @return void
1525
 */
1526
function logItems(
1527
    array $SETTINGS,
1528
    int $item_id,
1529
    string $item_label,
1530
    int $id_user,
1531
    string $action,
1532
    ?string $login = null,
1533
    ?string $raison = null,
1534
    ?string $encryption_type = null,
1535
    ?string $time = null,
1536
    ?string $old_value = null
1537
): void {
1538
    // Load class DB
1539
    loadClasses('DB');
1540
1541
    // Insert log in DB
1542
    DB::insert(
1543
        prefixTable('log_items'),
1544
        [
1545
            'id_item' => $item_id,
1546
            'date' => is_null($time) === true ? time() : $time,
1547
            'id_user' => $id_user,
1548
            'action' => $action,
1549
            'raison' => $raison,
1550
            'old_value' => $old_value,
1551
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1552
        ]
1553
    );
1554
    // Timestamp the last change
1555
    if (in_array($action, ['at_creation', 'at_modifiation', 'at_delete', 'at_import'], true)) {
1556
        DB::update(
1557
            prefixTable('misc'),
1558
            [
1559
                'valeur' => time(),
1560
                'updated_at' => time(),
1561
            ],
1562
            'type = %s AND intitule = %s',
1563
            'timestamp',
1564
            'last_item_change'
1565
        );
1566
    }
1567
1568
    // SYSLOG
1569
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1570
        // Extract reason
1571
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1572
        // Get item info if not known
1573
        if (empty($item_label) === true) {
1574
            $dataItem = DB::queryfirstrow(
1575
                'SELECT id, id_tree, label
1576
                FROM ' . prefixTable('items') . '
1577
                WHERE id = %i',
1578
                $item_id
1579
            );
1580
            $item_label = $dataItem['label'];
1581
        }
1582
1583
        send_syslog(
1584
            'action=' . str_replace('at_', '', $action) .
1585
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1586
                ' itemno=' . $item_id .
1587
                ' user=' . (is_null($login) === true ? '' : addslashes((string) $login)) .
1588
                ' itemname="' . addslashes($item_label) . '"',
1589
            $SETTINGS['syslog_host'],
1590
            $SETTINGS['syslog_port'],
1591
            'teampass'
1592
        );
1593
    }
1594
1595
    // send notification if enabled
1596
    //notifyOnChange($item_id, $action, $SETTINGS);
1597
}
1598
1599
/**
1600
 * Prepare notification email to subscribers.
1601
 *
1602
 * @param int    $item_id  Item id
1603
 * @param string $label    Item label
1604
 * @param array  $changes  List of changes
1605
 * @param array  $SETTINGS Teampass settings
1606
 * 
1607
 * @return void
1608
 */
1609
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1610
{
1611
    $session = SessionManager::getSession();
1612
    $lang = new Language($session->get('user-language') ?? 'english');
1613
    $globalsUserId = $session->get('user-id');
1614
    $globalsLastname = $session->get('user-lastname');
1615
    $globalsName = $session->get('user-name');
1616
    // send email to user that what to be notified
1617
    $notification = DB::queryOneColumn(
1618
        'email',
1619
        'SELECT *
1620
        FROM ' . prefixTable('notification') . ' AS n
1621
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1622
        WHERE n.item_id = %i AND n.user_id != %i',
1623
        $item_id,
1624
        $globalsUserId
1625
    );
1626
    if (DB::count() > 0) {
1627
        // Prepare path
1628
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1629
        // Get list of changes
1630
        $htmlChanges = '<ul>';
1631
        foreach ($changes as $change) {
1632
            $htmlChanges .= '<li>' . $change . '</li>';
1633
        }
1634
        $htmlChanges .= '</ul>';
1635
        // send email
1636
        DB::insert(
1637
            prefixTable('emails'),
1638
            [
1639
                'timestamp' => time(),
1640
                'subject' => $lang->get('email_subject_item_updated'),
1641
                'body' => str_replace(
1642
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1643
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1644
                    $lang->get('email_body_item_updated')
1645
                ),
1646
                'receivers' => implode(',', $notification),
1647
                'status' => '',
1648
            ]
1649
        );
1650
    }
1651
}
1652
1653
/**
1654
 * Returns the Item + path.
1655
 *
1656
 * @param int    $id_tree  Node id
1657
 * @param string $label    Label
1658
 * @param array  $SETTINGS TP settings
1659
 * 
1660
 * @return string
1661
 */
1662
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1663
{
1664
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1665
    $arbo = $tree->getPath($id_tree, true);
1666
    $path = '';
1667
    foreach ($arbo as $elem) {
1668
        if (empty($path) === true) {
1669
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1670
        } else {
1671
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1672
        }
1673
    }
1674
1675
    // Build text to show user
1676
    if (empty($label) === false) {
1677
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1678
    }
1679
    return empty($path) === true ? '' : $path;
1680
}
1681
1682
/**
1683
 * Get the client ip address.
1684
 *
1685
 * @return string IP address
1686
 */
1687
function getClientIpServer(): string
1688
{
1689
    if (getenv('HTTP_CLIENT_IP')) {
1690
        $ipaddress = getenv('HTTP_CLIENT_IP');
1691
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1692
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1693
    } elseif (getenv('HTTP_X_FORWARDED')) {
1694
        $ipaddress = getenv('HTTP_X_FORWARDED');
1695
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1696
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1697
    } elseif (getenv('HTTP_FORWARDED')) {
1698
        $ipaddress = getenv('HTTP_FORWARDED');
1699
    } elseif (getenv('REMOTE_ADDR')) {
1700
        $ipaddress = getenv('REMOTE_ADDR');
1701
    } else {
1702
        $ipaddress = 'UNKNOWN';
1703
    }
1704
1705
    return $ipaddress;
1706
}
1707
1708
/**
1709
 * Escape all HTML, JavaScript, and CSS.
1710
 *
1711
 * @param string $input    The input string
1712
 * @param string $encoding Which character encoding are we using?
1713
 * 
1714
 * @return string
1715
 */
1716
function noHTML(string $input, string $encoding = 'UTF-8'): string
1717
{
1718
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1719
}
1720
1721
/**
1722
 * Rebuilds the Teampass config file.
1723
 *
1724
 * @param string $configFilePath Path to the config file.
1725
 * @param array  $settings       Teampass settings.
1726
 *
1727
 * @return string|bool
1728
 */
1729
function rebuildConfigFile(string $configFilePath, array $settings)
1730
{
1731
    // Perform a copy if the file exists
1732
    if (file_exists($configFilePath)) {
1733
        $backupFilePath = $configFilePath . '.' . date('Y_m_d_His', time());
1734
        if (!copy($configFilePath, $backupFilePath)) {
1735
            return "ERROR: Could not copy file '$configFilePath'";
1736
        }
1737
    }
1738
1739
    // Regenerate the config file
1740
    $data = ["<?php\n", "global \$SETTINGS;\n", "\$SETTINGS = array (\n"];
1741
    $rows = DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s', 'admin');
1742
    foreach ($rows as $record) {
1743
        $value = getEncryptedValue($record['valeur'], $record['is_encrypted']);
1744
        $data[] = "    '{$record['intitule']}' => '". htmlspecialchars_decode($value, ENT_COMPAT) . "',\n";
1745
    }
1746
    $data[] = ");\n";
1747
    $data = array_unique($data);
1748
1749
    // Update the file
1750
    file_put_contents($configFilePath, implode('', $data));
1751
1752
    return true;
1753
}
1754
1755
/**
1756
 * Returns the encrypted value if needed.
1757
 *
1758
 * @param string $value       Value to encrypt.
1759
 * @param int   $isEncrypted Is the value encrypted?
1760
 *
1761
 * @return string
1762
 */
1763
function getEncryptedValue(string $value, int $isEncrypted): string
1764
{
1765
    return $isEncrypted ? cryption($value, '', 'encrypt')['string'] : $value;
1766
}
1767
1768
/**
1769
 * Permits to replace &#92; to permit correct display
1770
 *
1771
 * @param string $input Some text
1772
 * 
1773
 * @return string
1774
 */
1775
function handleBackslash(string $input): string
1776
{
1777
    return str_replace('&amp;#92;', '&#92;', $input);
1778
}
1779
1780
/**
1781
 * Permits to load settings
1782
 * 
1783
 * @return void
1784
*/
1785
function loadSettings(): void
1786
{
1787
    global $SETTINGS;
1788
    /* LOAD CPASSMAN SETTINGS */
1789
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1790
        $SETTINGS = [];
1791
        $SETTINGS['duplicate_folder'] = 0;
1792
        //by default, this is set to 0;
1793
        $SETTINGS['duplicate_item'] = 0;
1794
        //by default, this is set to 0;
1795
        $SETTINGS['number_of_used_pw'] = 5;
1796
        //by default, this value is set to 5;
1797
        $settings = [];
1798
        $rows = DB::query(
1799
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1800
            [
1801
                'type' => 'admin',
1802
                'type2' => 'settings',
1803
            ]
1804
        );
1805
        foreach ($rows as $record) {
1806
            if ($record['type'] === 'admin') {
1807
                $SETTINGS[$record['intitule']] = $record['valeur'];
1808
            } else {
1809
                $settings[$record['intitule']] = $record['valeur'];
1810
            }
1811
        }
1812
        $SETTINGS['loaded'] = 1;
1813
        $SETTINGS['default_session_expiration_time'] = 5;
1814
    }
1815
}
1816
1817
/**
1818
 * check if folder has custom fields.
1819
 * Ensure that target one also has same custom fields
1820
 * 
1821
 * @param int $source_id
1822
 * @param int $target_id 
1823
 * 
1824
 * @return bool
1825
*/
1826
function checkCFconsistency(int $source_id, int $target_id): bool
1827
{
1828
    $source_cf = [];
1829
    $rows = DB::QUERY(
1830
        'SELECT id_category
1831
            FROM ' . prefixTable('categories_folders') . '
1832
            WHERE id_folder = %i',
1833
        $source_id
1834
    );
1835
    foreach ($rows as $record) {
1836
        array_push($source_cf, $record['id_category']);
1837
    }
1838
1839
    $target_cf = [];
1840
    $rows = DB::QUERY(
1841
        'SELECT id_category
1842
            FROM ' . prefixTable('categories_folders') . '
1843
            WHERE id_folder = %i',
1844
        $target_id
1845
    );
1846
    foreach ($rows as $record) {
1847
        array_push($target_cf, $record['id_category']);
1848
    }
1849
1850
    $cf_diff = array_diff($source_cf, $target_cf);
1851
    if (count($cf_diff) > 0) {
1852
        return false;
1853
    }
1854
1855
    return true;
1856
}
1857
1858
/**
1859
 * Will encrypte/decrypt a fil eusing Defuse.
1860
 *
1861
 * @param string $type        can be either encrypt or decrypt
1862
 * @param string $source_file path to source file
1863
 * @param string $target_file path to target file
1864
 * @param array  $SETTINGS    Settings
1865
 * @param string $password    A password
1866
 *
1867
 * @return string|bool
1868
 */
1869
function prepareFileWithDefuse(
1870
    string $type,
1871
    string $source_file,
1872
    string $target_file,
1873
    string $password = null
1874
) {
1875
    // Load AntiXSS
1876
    $antiXss = new AntiXSS();
1877
    // Protect against bad inputs
1878
    if (is_array($source_file) === true || is_array($target_file) === true) {
1879
        return 'error_cannot_be_array';
1880
    }
1881
1882
    // Sanitize
1883
    $source_file = $antiXss->xss_clean($source_file);
1884
    $target_file = $antiXss->xss_clean($target_file);
1885
    if (empty($password) === true || is_null($password) === true) {
1886
        // get KEY to define password
1887
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
1888
        $password = Key::loadFromAsciiSafeString($ascii_key);
1889
    }
1890
1891
    $err = '';
1892
    if ($type === 'decrypt') {
1893
        // Decrypt file
1894
        $err = defuseFileDecrypt(
1895
            $source_file,
1896
            $target_file,
1897
            $password
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type Defuse\Crypto\Key; however, parameter $password of defuseFileDecrypt() does only seem to accept null|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

1897
            /** @scrutinizer ignore-type */ $password
Loading history...
1898
        );
1899
    } elseif ($type === 'encrypt') {
1900
        // Encrypt file
1901
        $err = defuseFileEncrypt(
1902
            $source_file,
1903
            $target_file,
1904
            $password
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type Defuse\Crypto\Key; however, parameter $password of defuseFileEncrypt() does only seem to accept null|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

1904
            /** @scrutinizer ignore-type */ $password
Loading history...
1905
        );
1906
    }
1907
1908
    // return error
1909
    return $err === true ? $err : '';
1910
}
1911
1912
/**
1913
 * Encrypt a file with Defuse.
1914
 *
1915
 * @param string $source_file path to source file
1916
 * @param string $target_file path to target file
1917
 * @param array  $SETTINGS    Settings
1918
 * @param string $password    A password
1919
 *
1920
 * @return string|bool
1921
 */
1922
function defuseFileEncrypt(
1923
    string $source_file,
1924
    string $target_file,
1925
    string $password = null
1926
) {
1927
    $err = '';
1928
    try {
1929
        CryptoFile::encryptFileWithPassword(
1930
            $source_file,
1931
            $target_file,
1932
            $password
1933
        );
1934
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1935
        $err = 'wrong_key';
1936
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1937
        error_log('TEAMPASS-Error-Environment: ' . $ex->getMessage());
1938
        $err = 'environment_error';
1939
    } catch (CryptoException\IOException $ex) {
1940
        error_log('TEAMPASS-Error-General: ' . $ex->getMessage());
1941
        $err = 'general_error';
1942
    }
1943
1944
    // return error
1945
    return empty($err) === false ? $err : true;
1946
}
1947
1948
/**
1949
 * Decrypt a file with Defuse.
1950
 *
1951
 * @param string $source_file path to source file
1952
 * @param string $target_file path to target file
1953
 * @param array  $SETTINGS    Settings
1954
 * @param string $password    A password
1955
 *
1956
 * @return string|bool
1957
 */
1958
function defuseFileDecrypt(
1959
    string $source_file,
1960
    string $target_file,
1961
    string $password = null
1962
) {
1963
    $err = '';
1964
    try {
1965
        CryptoFile::decryptFileWithPassword(
1966
            $source_file,
1967
            $target_file,
1968
            $password
1969
        );
1970
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1971
        $err = 'wrong_key';
1972
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1973
        error_log('TEAMPASS-Error-Environment: ' . $ex->getMessage());
1974
        $err = 'environment_error';
1975
    } catch (CryptoException\IOException $ex) {
1976
        error_log('TEAMPASS-Error-General: ' . $ex->getMessage());
1977
        $err = 'general_error';
1978
    }
1979
1980
    // return error
1981
    return empty($err) === false ? $err : true;
1982
}
1983
1984
/*
1985
* NOT TO BE USED
1986
*/
1987
/**
1988
 * Undocumented function.
1989
 *
1990
 * @param string $text Text to debug
1991
 */
1992
function debugTeampass(string $text): void
1993
{
1994
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
1995
    if ($debugFile !== false) {
1996
        fputs($debugFile, $text);
1997
        fclose($debugFile);
1998
    }
1999
}
2000
2001
/**
2002
 * DELETE the file with expected command depending on server type.
2003
 *
2004
 * @param string $file     Path to file
2005
 * @param array  $SETTINGS Teampass settings
2006
 *
2007
 * @return void
2008
 */
2009
function fileDelete(string $file, array $SETTINGS): void
2010
{
2011
    // Load AntiXSS
2012
    $antiXss = new AntiXSS();
2013
    $file = $antiXss->xss_clean($file);
2014
    if (is_file($file)) {
2015
        unlink($file);
2016
    }
2017
}
2018
2019
/**
2020
 * Permits to extract the file extension.
2021
 *
2022
 * @param string $file File name
2023
 *
2024
 * @return string
2025
 */
2026
function getFileExtension(string $file): string
2027
{
2028
    if (strpos($file, '.') === false) {
2029
        return $file;
2030
    }
2031
2032
    return substr($file, strrpos($file, '.') + 1);
2033
}
2034
2035
/**
2036
 * Chmods files and folders with different permissions.
2037
 *
2038
 * This is an all-PHP alternative to using: \n
2039
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2040
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2041
 *
2042
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2043
  *
2044
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2045
 * @param int    $filePerm The permissions any found files should get.
2046
 * @param int    $dirPerm  The permissions any found folder should get.
2047
 *
2048
 * @return bool Returns TRUE if the path if found and FALSE if not.
2049
 *
2050
 * @warning The permission levels has to be entered in octal format, which
2051
 * normally means adding a zero ("0") in front of the permission level. \n
2052
 * More info at: http://php.net/chmod.
2053
*/
2054
2055
function recursiveChmod(
2056
    string $path,
2057
    int $filePerm = 0644,
2058
    int  $dirPerm = 0755
2059
) {
2060
    // Check if the path exists
2061
    $path = basename($path);
2062
    if (! file_exists($path)) {
2063
        return false;
2064
    }
2065
2066
    // See whether this is a file
2067
    if (is_file($path)) {
2068
        // Chmod the file with our given filepermissions
2069
        try {
2070
            chmod($path, $filePerm);
2071
        } catch (Exception $e) {
2072
            return false;
2073
        }
2074
    // If this is a directory...
2075
    } elseif (is_dir($path)) {
2076
        // Then get an array of the contents
2077
        $foldersAndFiles = scandir($path);
2078
        // Remove "." and ".." from the list
2079
        $entries = array_slice($foldersAndFiles, 2);
2080
        // Parse every result...
2081
        foreach ($entries as $entry) {
2082
            // And call this function again recursively, with the same permissions
2083
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2084
        }
2085
2086
        // When we are done with the contents of the directory, we chmod the directory itself
2087
        try {
2088
            chmod($path, $filePerm);
2089
        } catch (Exception $e) {
2090
            return false;
2091
        }
2092
    }
2093
2094
    // Everything seemed to work out well, return true
2095
    return true;
2096
}
2097
2098
/**
2099
 * Check if user can access to this item.
2100
 *
2101
 * @param int   $item_id ID of item
2102
 * @param array $SETTINGS
2103
 *
2104
 * @return bool|string
2105
 */
2106
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2107
{
2108
    
2109
    $session = SessionManager::getSession();
2110
    $session_groupes_visibles = $session->get('user-accessible_folders');
2111
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2112
    // Load item data
2113
    $data = DB::queryFirstRow(
2114
        'SELECT id_tree
2115
        FROM ' . prefixTable('items') . '
2116
        WHERE id = %i',
2117
        $item_id
2118
    );
2119
    // Check if user can access this folder
2120
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2121
        // Now check if this folder is restricted to user
2122
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2123
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2124
        ) {
2125
            return 'ERR_FOLDER_NOT_ALLOWED';
2126
        }
2127
    }
2128
2129
    return true;
2130
}
2131
2132
/**
2133
 * Creates a unique key.
2134
 *
2135
 * @param int $lenght Key lenght
2136
 *
2137
 * @return string
2138
 */
2139
function uniqidReal(int $lenght = 13): string
2140
{
2141
    if (function_exists('random_bytes')) {
2142
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2143
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2144
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2145
    } else {
2146
        throw new Exception('no cryptographically secure random function available');
2147
    }
2148
2149
    return substr(bin2hex($bytes), 0, $lenght);
2150
}
2151
2152
/**
2153
 * Obfuscate an email.
2154
 *
2155
 * @param string $email Email address
2156
 *
2157
 * @return string
2158
 */
2159
function obfuscateEmail(string $email): string
2160
{
2161
    $email = explode("@", $email);
2162
    $name = $email[0];
2163
    if (strlen($name) > 3) {
2164
        $name = substr($name, 0, 2);
2165
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2166
            $name .= "*";
2167
        }
2168
        $name .= substr($email[0], -1, 1);
2169
    }
2170
    $host = explode(".", $email[1])[0];
2171
    if (strlen($host) > 3) {
2172
        $host = substr($host, 0, 1);
2173
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2174
            $host .= "*";
2175
        }
2176
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2177
    }
2178
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2179
    return $email;
2180
}
2181
2182
/**
2183
 * Get id and title from role_titles table.
2184
 *
2185
 * @return array
2186
 */
2187
function getRolesTitles(): array
2188
{
2189
    // Load class DB
2190
    loadClasses('DB');
2191
    
2192
    // Insert log in DB
2193
    return DB::query(
2194
        'SELECT id, title
2195
        FROM ' . prefixTable('roles_title')
2196
    );
2197
}
2198
2199
/**
2200
 * Undocumented function.
2201
 *
2202
 * @param int $bytes Size of file
2203
 *
2204
 * @return string
2205
 */
2206
function formatSizeUnits(int $bytes): string
2207
{
2208
    if ($bytes >= 1073741824) {
2209
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2210
    } elseif ($bytes >= 1048576) {
2211
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2212
    } elseif ($bytes >= 1024) {
2213
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2214
    } elseif ($bytes > 1) {
2215
        $bytes .= ' bytes';
2216
    } elseif ($bytes === 1) {
2217
        $bytes .= ' byte';
2218
    } else {
2219
        $bytes = '0 bytes';
2220
    }
2221
2222
    return $bytes;
2223
}
2224
2225
/**
2226
 * Generate user pair of keys.
2227
 *
2228
 * @param string $userPwd User password
2229
 *
2230
 * @return array
2231
 */
2232
function generateUserKeys(string $userPwd): array
2233
{
2234
    // Sanitize
2235
    $antiXss = new AntiXSS();
2236
    $userPwd = $antiXss->xss_clean($userPwd);
2237
    // Load classes
2238
    $rsa = new Crypt_RSA();
2239
    $cipher = new Crypt_AES();
2240
    // Create the private and public key
2241
    $res = $rsa->createKey(4096);
2242
    // Encrypt the privatekey
2243
    $cipher->setPassword($userPwd);
2244
    $privatekey = $cipher->encrypt($res['privatekey']);
2245
    return [
2246
        'private_key' => base64_encode($privatekey),
2247
        'public_key' => base64_encode($res['publickey']),
2248
        'private_key_clear' => base64_encode($res['privatekey']),
2249
    ];
2250
}
2251
2252
/**
2253
 * Permits to decrypt the user's privatekey.
2254
 *
2255
 * @param string $userPwd        User password
2256
 * @param string $userPrivateKey User private key
2257
 *
2258
 * @return string|object
2259
 */
2260
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2261
{
2262
    // Sanitize
2263
    $antiXss = new AntiXSS();
2264
    $userPwd = $antiXss->xss_clean($userPwd);
2265
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2266
2267
    if (empty($userPwd) === false) {
2268
        // Load classes
2269
        $cipher = new Crypt_AES();
2270
        // Encrypt the privatekey
2271
        $cipher->setPassword($userPwd);
2272
        try {
2273
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2274
        } catch (Exception $e) {
2275
            return $e;
2276
        }
2277
    }
2278
    return '';
2279
}
2280
2281
/**
2282
 * Permits to encrypt the user's privatekey.
2283
 *
2284
 * @param string $userPwd        User password
2285
 * @param string $userPrivateKey User private key
2286
 *
2287
 * @return string
2288
 */
2289
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2290
{
2291
    // Sanitize
2292
    $antiXss = new AntiXSS();
2293
    $userPwd = $antiXss->xss_clean($userPwd);
2294
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2295
2296
    if (empty($userPwd) === false) {
2297
        // Load classes
2298
        $cipher = new Crypt_AES();
2299
        // Encrypt the privatekey
2300
        $cipher->setPassword($userPwd);        
2301
        try {
2302
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2303
        } catch (Exception $e) {
2304
            return $e;
2305
        }
2306
    }
2307
    return '';
2308
}
2309
2310
/**
2311
 * Encrypts a string using AES.
2312
 *
2313
 * @param string $data String to encrypt
2314
 * @param string $key
2315
 *
2316
 * @return array
2317
 */
2318
function doDataEncryption(string $data, string $key = NULL): array
2319
{
2320
    // Sanitize
2321
    $antiXss = new AntiXSS();
2322
    $data = $antiXss->xss_clean($data);
2323
    
2324
    // Load classes
2325
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2326
    // Generate an object key
2327
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2328
    // Set it as password
2329
    $cipher->setPassword($objectKey);
2330
    return [
2331
        'encrypted' => base64_encode($cipher->encrypt($data)),
2332
        'objectKey' => base64_encode($objectKey),
2333
    ];
2334
}
2335
2336
/**
2337
 * Decrypts a string using AES.
2338
 *
2339
 * @param string $data Encrypted data
2340
 * @param string $key  Key to uncrypt
2341
 *
2342
 * @return string
2343
 */
2344
function doDataDecryption(string $data, string $key): string
2345
{
2346
    // Sanitize
2347
    $antiXss = new AntiXSS();
2348
    $data = $antiXss->xss_clean($data);
2349
    $key = $antiXss->xss_clean($key);
2350
2351
    // Load classes
2352
    $cipher = new Crypt_AES();
2353
    // Set the object key
2354
    $cipher->setPassword(base64_decode($key));
2355
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2356
}
2357
2358
/**
2359
 * Encrypts using RSA a string using a public key.
2360
 *
2361
 * @param string $key       Key to be encrypted
2362
 * @param string $publicKey User public key
2363
 *
2364
 * @return string
2365
 */
2366
function encryptUserObjectKey(string $key, string $publicKey): string
2367
{
2368
    // Empty password
2369
    if (empty($key)) return '';
2370
2371
    // Sanitize
2372
    $antiXss = new AntiXSS();
2373
    $publicKey = $antiXss->xss_clean($publicKey);
2374
    // Load classes
2375
    $rsa = new Crypt_RSA();
2376
    // Load the public key
2377
    $decodedPublicKey = base64_decode($publicKey, true);
2378
    if ($decodedPublicKey === false) {
2379
        throw new InvalidArgumentException("Error while decoding key.");
2380
    }
2381
    $rsa->loadKey($decodedPublicKey);
2382
    // Encrypt
2383
    $encrypted = $rsa->encrypt(base64_decode($key));
2384
    if (empty($encrypted)) {  // Check if key is empty or null
2385
        throw new RuntimeException("Error while encrypting key.");
2386
    }
2387
    // Return
2388
    return base64_encode($encrypted);
2389
}
2390
2391
/**
2392
 * Decrypts using RSA an encrypted string using a private key.
2393
 *
2394
 * @param string $key        Encrypted key
2395
 * @param string $privateKey User private key
2396
 *
2397
 * @return string
2398
 */
2399
function decryptUserObjectKey(string $key, string $privateKey): string
2400
{
2401
    // Sanitize
2402
    $antiXss = new AntiXSS();
2403
    $privateKey = $antiXss->xss_clean($privateKey);
2404
2405
    // Load classes
2406
    $rsa = new Crypt_RSA();
2407
    // Load the private key
2408
    $decodedPrivateKey = base64_decode($privateKey, true);
2409
    if ($decodedPrivateKey === false) {
2410
        throw new InvalidArgumentException("Error while decoding private key.");
2411
    }
2412
2413
    $rsa->loadKey($decodedPrivateKey);
2414
2415
    // Decrypt
2416
    try {
2417
        $decodedKey = base64_decode($key, true);
2418
        if ($decodedKey === false) {
2419
            throw new InvalidArgumentException("Error while decoding key.");
2420
        }
2421
2422
        // This check is needed as decrypt() in version 2 can return false in case of error
2423
        $tmpValue = $rsa->decrypt($decodedKey);
2424
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2425
            return base64_encode($tmpValue);
2426
        } else {
2427
            return '';
2428
        }
2429
    } catch (Exception $e) {
2430
        if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2431
            error_log('TEAMPASS Error - ldap - '.$e->getMessage());
2432
        }
2433
        return 'Exception: could not decrypt object';
2434
    }
2435
}
2436
2437
/**
2438
 * Encrypts a file.
2439
 *
2440
 * @param string $fileInName File name
2441
 * @param string $fileInPath Path to file
2442
 *
2443
 * @return array
2444
 */
2445
function encryptFile(string $fileInName, string $fileInPath): array
2446
{
2447
    if (defined('FILE_BUFFER_SIZE') === false) {
2448
        define('FILE_BUFFER_SIZE', 128 * 1024);
2449
    }
2450
2451
    // Load classes
2452
    $cipher = new Crypt_AES();
2453
2454
    // Generate an object key
2455
    $objectKey = uniqidReal(32);
2456
    // Set it as password
2457
    $cipher->setPassword($objectKey);
2458
    // Prevent against out of memory
2459
    $cipher->enableContinuousBuffer();
2460
2461
    // Encrypt the file content
2462
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2463
    $fileContent = file_get_contents($filePath);
2464
    $plaintext = $fileContent;
2465
    $ciphertext = $cipher->encrypt($plaintext);
2466
2467
    // Save new file
2468
    // deepcode ignore InsecureHash: is simply used to get a unique name
2469
    $hash = uniqid('', true);
2470
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2471
    file_put_contents($fileOut, $ciphertext);
2472
    unlink($fileInPath . '/' . $fileInName);
2473
    return [
2474
        'fileHash' => base64_encode($hash),
2475
        'objectKey' => base64_encode($objectKey),
2476
    ];
2477
}
2478
2479
/**
2480
 * Decrypt a file.
2481
 *
2482
 * @param string $fileName File name
2483
 * @param string $filePath Path to file
2484
 * @param string $key      Key to use
2485
 *
2486
 * @return string|array
2487
 */
2488
function decryptFile(string $fileName, string $filePath, string $key): string|array
2489
{
2490
    if (! defined('FILE_BUFFER_SIZE')) {
2491
        define('FILE_BUFFER_SIZE', 128 * 1024);
2492
    }
2493
    
2494
    // Load classes
2495
    $cipher = new Crypt_AES();
2496
    $antiXSS = new AntiXSS();
2497
    
2498
    // Get file name
2499
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2500
2501
    // Set the object key
2502
    $cipher->setPassword(base64_decode($key));
2503
    // Prevent against out of memory
2504
    $cipher->enableContinuousBuffer();
2505
    $cipher->disablePadding();
2506
    // Get file content
2507
    $safeFilePath = realpath($filePath . '/' . TP_FILE_PREFIX . $safeFileName);
2508
    if ($safeFilePath !== false && file_exists($safeFilePath)) {
2509
        $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2510
    } else {
2511
        // Handle the error: file doesn't exist or path is invalid
2512
        return [
2513
            'error' => true,
2514
            'message' => 'This file has not been found.',
2515
        ];
2516
    }
2517
2518
    if (WIP) error_log('DEBUG: File image url -> '.filter_var($safeFilePath, FILTER_SANITIZE_URL));
2519
2520
    // Decrypt file content and return
2521
    return base64_encode($cipher->decrypt($ciphertext));
2522
}
2523
2524
/**
2525
 * Generate a simple password
2526
 *
2527
 * @param int $length Length of string
2528
 * @param bool $symbolsincluded Allow symbols
2529
 *
2530
 * @return string
2531
 */
2532
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2533
{
2534
    // Generate new user password
2535
    $small_letters = range('a', 'z');
2536
    $big_letters = range('A', 'Z');
2537
    $digits = range(0, 9);
2538
    $symbols = $symbolsincluded === true ?
2539
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2540
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2541
    $count = count($res);
2542
    // first variant
2543
2544
    $random_string = '';
2545
    for ($i = 0; $i < $length; ++$i) {
2546
        $random_string .= $res[random_int(0, $count - 1)];
2547
    }
2548
2549
    return $random_string;
2550
}
2551
2552
/**
2553
 * Permit to store the sharekey of an object for users.
2554
 *
2555
 * @param string $object_name             Type for table selection
2556
 * @param int    $post_folder_is_personal Personal
2557
 * @param int    $post_object_id          Object
2558
 * @param string $objectKey               Object key
2559
 * @param array  $SETTINGS                Teampass settings
2560
 * @param int    $user_id                 User ID if needed
2561
 * @param bool   $onlyForUser             If is TRUE, then the sharekey is only for the user
2562
 * @param bool   $deleteAll               If is TRUE, then all existing entries are deleted
2563
 * @param array  $objectKeyArray          Array of objects
2564
 * @param int    $all_users_except_id     All users except this one
2565
 * @param int    $apiUserId               API User ID
2566
 *
2567
 * @return void
2568
 */
2569
function storeUsersShareKey(
2570
    string $object_name,
2571
    int $post_folder_is_personal,
2572
    int $post_object_id,
2573
    string $objectKey,
2574
    bool $onlyForUser = false,
2575
    bool $deleteAll = true,
2576
    array $objectKeyArray = [],
2577
    int $all_users_except_id = -1,
2578
    int $apiUserId = -1
2579
): void {
2580
    
2581
    $session = SessionManager::getSession();
2582
    loadClasses('DB');
2583
2584
    // Delete existing entries for this object
2585
    if ($deleteAll === true) {
2586
        DB::delete(
2587
            $object_name,
2588
            'object_id = %i',
2589
            $post_object_id
2590
        );
2591
    }
2592
2593
    // Get the user ID
2594
    $userId = ($apiUserId === -1) ? (int) $session->get('user-id') : $apiUserId;
2595
    
2596
    // $onlyForUser is only dynamically set by external calls
2597
    if (
2598
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2599
    ) {
2600
        // Only create the sharekey for a user
2601
        $user = DB::queryFirstRow(
2602
            'SELECT public_key
2603
            FROM ' . prefixTable('users') . '
2604
            WHERE id = %i
2605
            AND public_key != ""',
2606
            $userId
2607
        );
2608
2609
        if (empty($objectKey) === false) {
2610
            DB::insert(
2611
                $object_name,
2612
                [
2613
                    'object_id' => (int) $post_object_id,
2614
                    'user_id' => $userId,
2615
                    'share_key' => encryptUserObjectKey(
2616
                        $objectKey,
2617
                        $user['public_key']
2618
                    ),
2619
                ]
2620
            );
2621
        } else if (count($objectKeyArray) > 0) {
2622
            foreach ($objectKeyArray as $object) {
2623
                DB::insert(
2624
                    $object_name,
2625
                    [
2626
                        'object_id' => (int) $object['objectId'],
2627
                        'user_id' => $userId,
2628
                        'share_key' => encryptUserObjectKey(
2629
                            $object['objectKey'],
2630
                            $user['public_key']
2631
                        ),
2632
                    ]
2633
                );
2634
            }
2635
        }
2636
    } else {
2637
        // Create sharekey for each user
2638
        $user_ids = [OTV_USER_ID, SSH_USER_ID, API_USER_ID];
2639
        if ($all_users_except_id !== -1) {
2640
            array_push($user_ids, (int) $all_users_except_id);
2641
        }
2642
        $users = DB::query(
2643
            'SELECT id, public_key
2644
            FROM ' . prefixTable('users') . '
2645
            WHERE id NOT IN %li
2646
            AND public_key != ""',
2647
            $user_ids
2648
        );
2649
        //DB::debugmode(false);
2650
        foreach ($users as $user) {
2651
            // Insert in DB the new object key for this item by user
2652
            if (count($objectKeyArray) === 0) {
2653
                if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case1 - ' . $object_name . ' - ' . $post_object_id . ' - ' . $user['id'] . ' - ' . $objectKey);
2654
                DB::insert(
2655
                    $object_name,
2656
                    [
2657
                        'object_id' => $post_object_id,
2658
                        'user_id' => (int) $user['id'],
2659
                        'share_key' => encryptUserObjectKey(
2660
                            $objectKey,
2661
                            $user['public_key']
2662
                        ),
2663
                    ]
2664
                );
2665
            } else {
2666
                foreach ($objectKeyArray as $object) {
2667
                    if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case2 - ' . $object_name . ' - ' . $object['objectId'] . ' - ' . $user['id'] . ' - ' . $object['objectKey']);
2668
                    DB::insert(
2669
                        $object_name,
2670
                        [
2671
                            'object_id' => (int) $object['objectId'],
2672
                            'user_id' => (int) $user['id'],
2673
                            'share_key' => encryptUserObjectKey(
2674
                                $object['objectKey'],
2675
                                $user['public_key']
2676
                            ),
2677
                        ]
2678
                    );
2679
                }
2680
            }
2681
        }
2682
    }
2683
}
2684
2685
/**
2686
 * Is this string base64 encoded?
2687
 *
2688
 * @param string $str Encoded string?
2689
 *
2690
 * @return bool
2691
 */
2692
function isBase64(string $str): bool
2693
{
2694
    $str = (string) trim($str);
2695
    if (! isset($str[0])) {
2696
        return false;
2697
    }
2698
2699
    $base64String = (string) base64_decode($str, true);
2700
    if ($base64String && base64_encode($base64String) === $str) {
2701
        return true;
2702
    }
2703
2704
    return false;
2705
}
2706
2707
/**
2708
 * Undocumented function
2709
 *
2710
 * @param string $field Parameter
2711
 *
2712
 * @return array|bool|resource|string
2713
 */
2714
function filterString(string $field)
2715
{
2716
    // Sanitize string
2717
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2718
    if (empty($field) === false) {
2719
        // Load AntiXSS
2720
        $antiXss = new AntiXSS();
2721
        // Return
2722
        return $antiXss->xss_clean($field);
2723
    }
2724
2725
    return false;
2726
}
2727
2728
/**
2729
 * CHeck if provided credentials are allowed on server
2730
 *
2731
 * @param string $login    User Login
2732
 * @param string $password User Pwd
2733
 * @param array  $SETTINGS Teampass settings
2734
 *
2735
 * @return bool
2736
 */
2737
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2738
{
2739
    // Build ldap configuration array
2740
    $config = [
2741
        // Mandatory Configuration Options
2742
        'hosts' => [$SETTINGS['ldap_hosts']],
2743
        'base_dn' => $SETTINGS['ldap_bdn'],
2744
        'username' => $SETTINGS['ldap_username'],
2745
        'password' => $SETTINGS['ldap_password'],
2746
2747
        // Optional Configuration Options
2748
        'port' => $SETTINGS['ldap_port'],
2749
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2750
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2751
        'version' => 3,
2752
        'timeout' => 5,
2753
        'follow_referrals' => false,
2754
2755
        // Custom LDAP Options
2756
        'options' => [
2757
            // See: http://php.net/ldap_set_option
2758
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2759
        ],
2760
    ];
2761
    
2762
    $connection = new Connection($config);
2763
    // Connect to LDAP
2764
    try {
2765
        $connection->connect();
2766
    } catch (\LdapRecord\Auth\BindException $e) {
2767
        $error = $e->getDetailedError();
2768
        if ($error && defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2769
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2770
        }
2771
        // deepcode ignore ServerLeak: No important data is sent
2772
        echo 'An error occurred.';
2773
        return false;
2774
    }
2775
2776
    // Authenticate user
2777
    try {
2778
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2779
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2780
        } else {
2781
            $connection->auth()->attempt($SETTINGS['ldap_user_attribute'].'='.$login.','.(isset($SETTINGS['ldap_dn_additional_user_dn']) && !empty($SETTINGS['ldap_dn_additional_user_dn']) ? $SETTINGS['ldap_dn_additional_user_dn'].',' : '').$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
2782
        }
2783
    } catch (\LdapRecord\Auth\BindException $e) {
2784
        $error = $e->getDetailedError();
2785
        if ($error && defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2786
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2787
        }
2788
        // deepcode ignore ServerLeak: No important data is sent
2789
        echo 'An error occurred.';
2790
        return false;
2791
    }
2792
2793
    return true;
2794
}
2795
2796
/**
2797
 * Removes from DB all sharekeys of this user
2798
 *
2799
 * @param int $userId User's id
2800
 * @param array   $SETTINGS Teampass settings
2801
 *
2802
 * @return bool
2803
 */
2804
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2805
{
2806
    // Load class DB
2807
    loadClasses('DB');
2808
2809
    // Remove all item sharekeys items
2810
    // expect if personal item
2811
    DB::delete(
2812
        prefixTable('sharekeys_items'),
2813
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2814
        $userId
2815
    );
2816
    // Remove all item sharekeys files
2817
    DB::delete(
2818
        prefixTable('sharekeys_files'),
2819
        'user_id = %i AND object_id NOT IN (
2820
            SELECT f.id 
2821
            FROM ' . prefixTable('items') . ' AS i 
2822
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2823
            WHERE i.perso = 1
2824
        )',
2825
        $userId
2826
    );
2827
    // Remove all item sharekeys fields
2828
    DB::delete(
2829
        prefixTable('sharekeys_fields'),
2830
        'user_id = %i AND object_id NOT IN (
2831
            SELECT c.id 
2832
            FROM ' . prefixTable('items') . ' AS i 
2833
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2834
            WHERE i.perso = 1
2835
        )',
2836
        $userId
2837
    );
2838
    // Remove all item sharekeys logs
2839
    DB::delete(
2840
        prefixTable('sharekeys_logs'),
2841
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2842
        $userId
2843
    );
2844
    // Remove all item sharekeys suggestions
2845
    DB::delete(
2846
        prefixTable('sharekeys_suggestions'),
2847
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2848
        $userId
2849
    );
2850
    return false;
2851
}
2852
2853
/**
2854
 * Manage list of timezones   $SETTINGS Teampass settings
2855
 *
2856
 * @return array
2857
 */
2858
function timezone_list()
2859
{
2860
    static $timezones = null;
2861
    if ($timezones === null) {
2862
        $timezones = [];
2863
        $offsets = [];
2864
        $now = new DateTime('now', new DateTimeZone('UTC'));
2865
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2866
            $now->setTimezone(new DateTimeZone($timezone));
2867
            $offsets[] = $offset = $now->getOffset();
2868
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2869
        }
2870
2871
        array_multisort($offsets, $timezones);
2872
    }
2873
2874
    return $timezones;
2875
}
2876
2877
/**
2878
 * Provide timezone offset
2879
 *
2880
 * @param int $offset Timezone offset
2881
 *
2882
 * @return string
2883
 */
2884
function format_GMT_offset($offset): string
2885
{
2886
    $hours = intval($offset / 3600);
2887
    $minutes = abs(intval($offset % 3600 / 60));
2888
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2889
}
2890
2891
/**
2892
 * Provides timezone name
2893
 *
2894
 * @param string $name Timezone name
2895
 *
2896
 * @return string
2897
 */
2898
function format_timezone_name($name): string
2899
{
2900
    $name = str_replace('/', ', ', $name);
2901
    $name = str_replace('_', ' ', $name);
2902
2903
    return str_replace('St ', 'St. ', $name);
2904
}
2905
2906
/**
2907
 * Provides info if user should use MFA based on roles
2908
 *
2909
 * @param string $userRolesIds  User roles ids
2910
 * @param string $mfaRoles      Roles for which MFA is requested
2911
 *
2912
 * @return bool
2913
 */
2914
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
2915
{
2916
    if (empty($mfaRoles) === true) {
2917
        return true;
2918
    }
2919
2920
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2921
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2922
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2923
        return true;
2924
    }
2925
2926
    return false;
2927
}
2928
2929
/**
2930
 * Permits to clean a string for export purpose
2931
 *
2932
 * @param string $text
2933
 * @param bool $emptyCheckOnly
2934
 * 
2935
 * @return string
2936
 */
2937
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
2938
{
2939
    if (is_null($text) === true || empty($text) === true) {
2940
        return '';
2941
    }
2942
    // only expected to check if $text was empty
2943
    elseif ($emptyCheckOnly === true) {
2944
        return $text;
2945
    }
2946
2947
    return strip_tags(
2948
        cleanString(
2949
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
2950
            true)
2951
        );
2952
}
2953
2954
/**
2955
 * Permits to check if user ID is valid
2956
 *
2957
 * @param integer $post_user_id
2958
 * @return bool
2959
 */
2960
function isUserIdValid($userId): bool
2961
{
2962
    if (is_null($userId) === false
2963
        && isset($userId) === true
2964
        && empty($userId) === false
2965
    ) {
2966
        return true;
2967
    }
2968
    return false;
2969
}
2970
2971
/**
2972
 * Check if a key exists and if its value equal the one expected
2973
 *
2974
 * @param string $key
2975
 * @param integer|string $value
2976
 * @param array $array
2977
 * 
2978
 * @return boolean
2979
 */
2980
function isKeyExistingAndEqual(
2981
    string $key,
2982
    /*PHP8 - integer|string*/$value,
2983
    array $array
2984
): bool
2985
{
2986
    if (isset($array[$key]) === true
2987
        && (is_int($value) === true ?
2988
            (int) $array[$key] === $value :
2989
            (string) $array[$key] === $value)
2990
    ) {
2991
        return true;
2992
    }
2993
    return false;
2994
}
2995
2996
/**
2997
 * Check if a variable is not set or equal to a value
2998
 *
2999
 * @param string|null $var
3000
 * @param integer|string $value
3001
 * 
3002
 * @return boolean
3003
 */
3004
function isKeyNotSetOrEqual(
3005
    /*PHP8 - string|null*/$var,
3006
    /*PHP8 - integer|string*/$value
3007
): bool
3008
{
3009
    if (isset($var) === false
3010
        || (is_int($value) === true ?
3011
            (int) $var === $value :
3012
            (string) $var === $value)
3013
    ) {
3014
        return true;
3015
    }
3016
    return false;
3017
}
3018
3019
/**
3020
 * Check if a key exists and if its value < to the one expected
3021
 *
3022
 * @param string $key
3023
 * @param integer $value
3024
 * @param array $array
3025
 * 
3026
 * @return boolean
3027
 */
3028
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3029
{
3030
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3031
        return true;
3032
    }
3033
    return false;
3034
}
3035
3036
/**
3037
 * Check if a key exists and if its value > to the one expected
3038
 *
3039
 * @param string $key
3040
 * @param integer $value
3041
 * @param array $array
3042
 * 
3043
 * @return boolean
3044
 */
3045
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3046
{
3047
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3048
        return true;
3049
    }
3050
    return false;
3051
}
3052
3053
/**
3054
 * Check if values in array are set
3055
 * Return true if all set
3056
 * Return false if one of them is not set
3057
 *
3058
 * @param array $arrayOfValues
3059
 * @return boolean
3060
 */
3061
function isSetArrayOfValues(array $arrayOfValues): bool
3062
{
3063
    foreach($arrayOfValues as $value) {
3064
        if (isset($value) === false) {
3065
            return false;
3066
        }
3067
    }
3068
    return true;
3069
}
3070
3071
/**
3072
 * Check if values in array are set
3073
 * Return true if all set
3074
 * Return false if one of them is not set
3075
 *
3076
 * @param array $arrayOfValues
3077
 * @param integer|string $value
3078
 * @return boolean
3079
 */
3080
function isArrayOfVarsEqualToValue(
3081
    array $arrayOfVars,
3082
    /*PHP8 - integer|string*/$value
3083
) : bool
3084
{
3085
    foreach($arrayOfVars as $variable) {
3086
        if ($variable !== $value) {
3087
            return false;
3088
        }
3089
    }
3090
    return true;
3091
}
3092
3093
/**
3094
 * Checks if at least one variable in array is equal to value
3095
 *
3096
 * @param array $arrayOfValues
3097
 * @param integer|string $value
3098
 * @return boolean
3099
 */
3100
function isOneVarOfArrayEqualToValue(
3101
    array $arrayOfVars,
3102
    /*PHP8 - integer|string*/$value
3103
) : bool
3104
{
3105
    foreach($arrayOfVars as $variable) {
3106
        if ($variable === $value) {
3107
            return true;
3108
        }
3109
    }
3110
    return false;
3111
}
3112
3113
/**
3114
 * Checks is value is null, not set OR empty
3115
 *
3116
 * @param string|int|null $value
3117
 * @return boolean
3118
 */
3119
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3120
{
3121
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3122
        return true;
3123
    }
3124
    return false;
3125
}
3126
3127
/**
3128
 * Checks if value is set and if empty is equal to passed boolean
3129
 *
3130
 * @param string|int $value
3131
 * @param boolean $boolean
3132
 * @return boolean
3133
 */
3134
function isValueSetEmpty($value, $boolean = true) : bool
3135
{
3136
    if (isset($value) === true && empty($value) === $boolean) {
3137
        return true;
3138
    }
3139
    return false;
3140
}
3141
3142
/**
3143
 * Ensure Complexity is translated
3144
 *
3145
 * @return void
3146
 */
3147
function defineComplexity() : void
3148
{
3149
    // Load user's language
3150
    $session = SessionManager::getSession();
3151
    $lang = new Language($session->get('user-language') ?? 'english');
3152
    
3153
    if (defined('TP_PW_COMPLEXITY') === false) {
3154
        define(
3155
            'TP_PW_COMPLEXITY',
3156
            [
3157
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3158
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3159
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3160
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3161
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3162
            ]
3163
        );
3164
    }
3165
}
3166
3167
/**
3168
 * Uses Sanitizer to perform data sanitization
3169
 *
3170
 * @param array     $data
3171
 * @param array     $filters
3172
 * @return array|string
3173
 */
3174
function dataSanitizer(array $data, array $filters): array|string
3175
{
3176
    // Load Sanitizer library
3177
    $sanitizer = new Sanitizer($data, $filters);
3178
3179
    // Load AntiXSS
3180
    $antiXss = new AntiXSS();
3181
3182
    // Sanitize post and get variables
3183
    return $antiXss->xss_clean($sanitizer->sanitize());
3184
}
3185
3186
/**
3187
 * Permits to manage the cache tree for a user
3188
 *
3189
 * @param integer $user_id
3190
 * @param string $data
3191
 * @param array $SETTINGS
3192
 * @param string $field_update
3193
 * @return void
3194
 */
3195
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3196
{
3197
    // Load class DB
3198
    loadClasses('DB');
3199
3200
    // Exists ?
3201
    $userCacheId = DB::queryfirstrow(
3202
        'SELECT increment_id
3203
        FROM ' . prefixTable('cache_tree') . '
3204
        WHERE user_id = %i',
3205
        $user_id
3206
    );
3207
    
3208
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3209
        // insert in table
3210
        DB::insert(
3211
            prefixTable('cache_tree'),
3212
            array(
3213
                'data' => $data,
3214
                'timestamp' => time(),
3215
                'user_id' => $user_id,
3216
                'visible_folders' => '',
3217
            )
3218
        );
3219
    } else {
3220
        if (empty($field_update) === true) {
3221
            DB::update(
3222
                prefixTable('cache_tree'),
3223
                [
3224
                    'timestamp' => time(),
3225
                    'data' => $data,
3226
                ],
3227
                'increment_id = %i',
3228
                $userCacheId['increment_id']
3229
            );
3230
        /* USELESS
3231
        } else {
3232
            DB::update(
3233
                prefixTable('cache_tree'),
3234
                [
3235
                    $field_update => $data,
3236
                ],
3237
                'increment_id = %i',
3238
                $userCacheId['increment_id']
3239
            );*/
3240
        }
3241
    }
3242
}
3243
3244
/**
3245
 * Permits to calculate a %
3246
 *
3247
 * @param float $nombre
3248
 * @param float $total
3249
 * @param float $pourcentage
3250
 * @return float
3251
 */
3252
function pourcentage(float $nombre, float $total, float $pourcentage): float
3253
{ 
3254
    $resultat = ($nombre/$total) * $pourcentage;
3255
    return round($resultat);
3256
}
3257
3258
/**
3259
 * Load the folders list from the cache
3260
 *
3261
 * @param string $fieldName
3262
 * @param string $sessionName
3263
 * @param boolean $forceRefresh
3264
 * @return array
3265
 */
3266
function loadFoldersListByCache(
3267
    string $fieldName,
3268
    string $sessionName,
3269
    bool $forceRefresh = false
3270
): array
3271
{
3272
    // Case when refresh is EXPECTED / MANDATORY
3273
    if ($forceRefresh === true) {
3274
        return [
3275
            'state' => false,
3276
            'data' => [],
3277
        ];
3278
    }
3279
    
3280
    $session = SessionManager::getSession();
3281
3282
    // Get last folder update
3283
    $lastFolderChange = DB::queryfirstrow(
3284
        'SELECT valeur FROM ' . prefixTable('misc') . '
3285
        WHERE type = %s AND intitule = %s',
3286
        'timestamp',
3287
        'last_folder_change'
3288
    );
3289
    if (DB::count() === 0) {
3290
        $lastFolderChange['valeur'] = 0;
3291
    }
3292
3293
    // Case when an update in the tree has been done
3294
    // Refresh is then mandatory
3295
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3296
        return [
3297
            'state' => false,
3298
            'data' => [],
3299
        ];
3300
    }
3301
3302
    // Does this user has the tree structure in session?
3303
    // If yes then use it
3304
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3305
        return [
3306
            'state' => true,
3307
            'data' => json_encode($session->get('user-folders_list')[0]),
3308
            'extra' => 'to_be_parsed',
3309
        ];
3310
    }
3311
    
3312
    // Does this user has a tree cache
3313
    $userCacheTree = DB::queryfirstrow(
3314
        'SELECT '.$fieldName.'
3315
        FROM ' . prefixTable('cache_tree') . '
3316
        WHERE user_id = %i',
3317
        $session->get('user-id')
3318
    );
3319
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3320
        SessionManager::addRemoveFromSessionAssociativeArray(
3321
            'user-folders_list',
3322
            [$userCacheTree[$fieldName]],
3323
            'add'
3324
        );
3325
        return [
3326
            'state' => true,
3327
            'data' => $userCacheTree[$fieldName],
3328
            'extra' => '',
3329
        ];
3330
    }
3331
3332
    return [
3333
        'state' => false,
3334
        'data' => [],
3335
    ];
3336
}
3337
3338
3339
/**
3340
 * Permits to refresh the categories of folders
3341
 *
3342
 * @param array $folderIds
3343
 * @return void
3344
 */
3345
function handleFoldersCategories(
3346
    array $folderIds
3347
)
3348
{
3349
    // Load class DB
3350
    loadClasses('DB');
3351
3352
    $arr_data = array();
3353
3354
    // force full list of folders
3355
    if (count($folderIds) === 0) {
3356
        $folderIds = DB::queryFirstColumn(
3357
            'SELECT id
3358
            FROM ' . prefixTable('nested_tree') . '
3359
            WHERE personal_folder=%i',
3360
            0
3361
        );
3362
    }
3363
3364
    // Get complexity
3365
    defineComplexity();
3366
3367
    // update
3368
    foreach ($folderIds as $folder) {
3369
        // Do we have Categories
3370
        // get list of associated Categories
3371
        $arrCatList = array();
3372
        $rows_tmp = DB::query(
3373
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3374
            f.id_category AS category_id
3375
            FROM ' . prefixTable('categories_folders') . ' AS f
3376
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3377
            WHERE id_folder=%i',
3378
            $folder
3379
        );
3380
        if (DB::count() > 0) {
3381
            foreach ($rows_tmp as $row) {
3382
                $arrCatList[$row['id']] = array(
3383
                    'id' => $row['id'],
3384
                    'title' => $row['title'],
3385
                    'level' => $row['level'],
3386
                    'type' => $row['type'],
3387
                    'masked' => $row['masked'],
3388
                    'order' => $row['order'],
3389
                    'encrypted_data' => $row['encrypted_data'],
3390
                    'role_visibility' => $row['role_visibility'],
3391
                    'is_mandatory' => $row['is_mandatory'],
3392
                    'category_id' => $row['category_id'],
3393
                );
3394
            }
3395
        }
3396
        $arr_data['categories'] = $arrCatList;
3397
3398
        // Now get complexity
3399
        $valTemp = '';
3400
        $data = DB::queryFirstRow(
3401
            'SELECT valeur
3402
            FROM ' . prefixTable('misc') . '
3403
            WHERE type = %s AND intitule=%i',
3404
            'complex',
3405
            $folder
3406
        );
3407
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3408
            $valTemp = array(
3409
                'value' => $data['valeur'],
3410
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3411
            );
3412
        }
3413
        $arr_data['complexity'] = $valTemp;
3414
3415
        // Now get Roles
3416
        $valTemp = '';
3417
        $rows_tmp = DB::query(
3418
            'SELECT t.title
3419
            FROM ' . prefixTable('roles_values') . ' as v
3420
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3421
            WHERE v.folder_id = %i
3422
            GROUP BY title',
3423
            $folder
3424
        );
3425
        foreach ($rows_tmp as $record) {
3426
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3427
        }
3428
        $arr_data['visibilityRoles'] = $valTemp;
3429
3430
        // now save in DB
3431
        DB::update(
3432
            prefixTable('nested_tree'),
3433
            array(
3434
                'categories' => json_encode($arr_data),
3435
            ),
3436
            'id = %i',
3437
            $folder
3438
        );
3439
    }
3440
}
3441
3442
/**
3443
 * List all users that have specific roles
3444
 *
3445
 * @param array $roles
3446
 * @return array
3447
 */
3448
function getUsersWithRoles(
3449
    array $roles
3450
): array
3451
{
3452
    $session = SessionManager::getSession();
3453
    $arrUsers = array();
3454
3455
    foreach ($roles as $role) {
3456
        // loop on users and check if user has this role
3457
        $rows = DB::query(
3458
            'SELECT id, fonction_id
3459
            FROM ' . prefixTable('users') . '
3460
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3461
            $session->get('user-id')
3462
        );
3463
        foreach ($rows as $user) {
3464
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3465
            if (in_array($role, $userRoles, true) === true) {
3466
                array_push($arrUsers, $user['id']);
3467
            }
3468
        }
3469
    }
3470
3471
    return $arrUsers;
3472
}
3473
3474
3475
/**
3476
 * Get all users informations
3477
 *
3478
 * @param integer $userId
3479
 * @return array
3480
 */
3481
function getFullUserInfos(
3482
    int $userId
3483
): array
3484
{
3485
    if (empty($userId) === true) {
3486
        return array();
3487
    }
3488
3489
    $val = DB::queryfirstrow(
3490
        'SELECT *
3491
        FROM ' . prefixTable('users') . '
3492
        WHERE id = %i',
3493
        $userId
3494
    );
3495
3496
    return $val;
3497
}
3498
3499
/**
3500
 * Is required an upgrade
3501
 *
3502
 * @return boolean
3503
 */
3504
function upgradeRequired(): bool
3505
{
3506
    // Get settings.php
3507
    include_once __DIR__. '/../includes/config/settings.php';
3508
3509
    // Get timestamp in DB
3510
    $val = DB::queryfirstrow(
3511
        'SELECT valeur
3512
        FROM ' . prefixTable('misc') . '
3513
        WHERE type = %s AND intitule = %s',
3514
        'admin',
3515
        'upgrade_timestamp'
3516
    );
3517
3518
    // Check if upgrade is required
3519
    return (
3520
        is_null($val) || count($val) === 0 || !defined('UPGRADE_MIN_DATE') || 
3521
        empty($val['valeur']) || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE
3522
    );
3523
}
3524
3525
/**
3526
 * Permits to change the user keys on his demand
3527
 *
3528
 * @param integer $userId
3529
 * @param string $passwordClear
3530
 * @param integer $nbItemsToTreat
3531
 * @param string $encryptionKey
3532
 * @param boolean $deleteExistingKeys
3533
 * @param boolean $sendEmailToUser
3534
 * @param boolean $encryptWithUserPassword
3535
 * @param boolean $generate_user_new_password
3536
 * @param string $emailBody
3537
 * @param boolean $user_self_change
3538
 * @param string $recovery_public_key
3539
 * @param string $recovery_private_key
3540
 * @return string
3541
 */
3542
function handleUserKeys(
3543
    int $userId,
3544
    string $passwordClear,
3545
    int $nbItemsToTreat,
3546
    string $encryptionKey = '',
3547
    bool $deleteExistingKeys = false,
3548
    bool $sendEmailToUser = true,
3549
    bool $encryptWithUserPassword = false,
3550
    bool $generate_user_new_password = false,
3551
    string $emailBody = '',
3552
    bool $user_self_change = false,
3553
    string $recovery_public_key = '',
3554
    string $recovery_private_key = ''
3555
): string
3556
{
3557
    $session = SessionManager::getSession();
3558
    $lang = new Language($session->get('user-language') ?? 'english');
3559
3560
    // Generate new user password if required
3561
    if ($generate_user_new_password === true) {
3562
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3563
    }
3564
3565
    // Create password hash
3566
    $passwordManager = new PasswordManager();
3567
    $hashedPassword = $passwordManager->hashPassword($passwordClear);
3568
    if ($passwordManager->verifyPassword($hashedPassword, $passwordClear) === false) {
3569
        return prepareExchangedData(
3570
            array(
3571
                'error' => true,
3572
                'message' => $lang->get('pw_hash_not_correct'),
3573
            ),
3574
            'encode'
3575
        );
3576
    }
3577
3578
    // Validate public/private keys if provided
3579
    if ($recovery_public_key !== '' && $recovery_private_key !== '') {
3580
        if (!validateKeyPair($recovery_public_key, $recovery_private_key)) {
3581
            return prepareExchangedData([
3582
                    'error' => true,
3583
                    'message' => $lang->get('pw_encryption_error'),
3584
                ],
3585
                'encode'
3586
            );
3587
        }
3588
    }
3589
3590
    // Generate new keys
3591
    $userKeys = generateNewKeys($passwordClear, $user_self_change, $recovery_public_key, $recovery_private_key);
3592
3593
    // Save new keys and password in DB
3594
    saveUserKeysInDB($userId, $hashedPassword, $userKeys);
3595
3596
    // Update session if the user is the current user
3597
    updateSessionKeys($userId, $userKeys);
3598
3599
    // Manage encryption key
3600
    $encryptionKey = manageEncryptionKey($encryptWithUserPassword, $encryptionKey, $passwordClear);
3601
3602
    // Create background process
3603
    $processId = createBackgroundProcess($userId, $passwordClear, $encryptionKey, $sendEmailToUser, $emailBody, $user_self_change);
3604
3605
    // Delete existing keys if required
3606
    if ($deleteExistingKeys === true) {
3607
        deleteUserObjetsKeys($userId);
3608
    }
3609
3610
    // Create background tasks
3611
    createUserTasks($processId, $nbItemsToTreat);
3612
3613
    // Update user's status
3614
    updateUserStatus($userId, $processId);
3615
3616
    return prepareExchangedData(
3617
        array(
3618
            'error' => false,
3619
            'message' => '',
3620
            'user_password' => $generate_user_new_password === true ? $passwordClear : '',
3621
        ),
3622
        'encode'
3623
    );
3624
}
3625
3626
/**
3627
 * Validates a pair of public and private keys.
3628
 *
3629
 * @param string $publicKey The public key to validate.
3630
 * @param string $privateKey The private key to validate.
3631
 * @return bool Returns true if the key pair is valid, false otherwise.
3632
 */
3633
function validateKeyPair(string $publicKey, string $privateKey): bool
3634
{
3635
    try {
3636
        $random_str = generateQuickPassword(12, false);
3637
        $encrypted = encryptUserObjectKey($random_str, $publicKey);
3638
        $decrypted = decryptUserObjectKey($encrypted, $privateKey);
3639
        return $decrypted === $random_str;
3640
    } catch (Exception $e) {
3641
        if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
3642
            error_log('ERROR: ' . $e->getMessage());
3643
        }
3644
        return false;
3645
    }
3646
}
3647
3648
/**
3649
 * Generates new encryption keys.
3650
 *
3651
 * @param string $passwordClear The clear text password.
3652
 * @param bool $user_self_change Indicates if the user is changing the password themselves.
3653
 * @param string $recovery_public_key The public key used for recovery.
3654
 * @param string $recovery_private_key The private key used for recovery.
3655
 * 
3656
 * @return array An array containing the new keys.
3657
 */
3658
function generateNewKeys(string $passwordClear, bool $user_self_change, string $recovery_public_key, string $recovery_private_key): array
3659
{
3660
    if ($user_self_change === true && !empty($recovery_public_key) && !empty($recovery_private_key)) {
3661
        return [
3662
            'public_key' => $recovery_public_key,
3663
            'private_key_clear' => $recovery_private_key,
3664
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3665
        ];
3666
    } else {
3667
        return generateUserKeys($passwordClear);
3668
    }
3669
}
3670
3671
/**
3672
 * Saves the user's keys in the database.
3673
 *
3674
 * @param int $userId The user's ID.
3675
 * @param string $hashedPassword The hashed password.
3676
 * @param array $userKeys The user's keys.
3677
 * @return void
3678
 */
3679
function saveUserKeysInDB(int $userId, string $hashedPassword, array $userKeys): void
3680
{
3681
    DB::update(
3682
        prefixTable('users'),
3683
        array(
3684
            'pw' => $hashedPassword,
3685
            'public_key' => $userKeys['public_key'],
3686
            'private_key' => $userKeys['private_key'],
3687
            'keys_recovery_time' => NULL,
3688
        ),
3689
        'id=%i',
3690
        $userId
3691
    );
3692
}
3693
3694
/**
3695
 * Updates the session keys.
3696
 *
3697
 * @param int $userId The user's ID.
3698
 * @param array $userKeys The user's keys.
3699
 * @return void
3700
 */
3701
function updateSessionKeys(int $userId, array $userKeys): void
3702
{
3703
    $session = SessionManager::getSession();
3704
    if ($userId === $session->get('user-id')) {
3705
        $session->set('user-private_key', $userKeys['private_key_clear']);
3706
        $session->set('user-public_key', $userKeys['public_key']);
3707
        $session->set('user-keys_recovery_time', NULL);
3708
    }
3709
}
3710
3711
/**
3712
 * Manages the encryption key.
3713
 *
3714
 * @param bool $encryptWithUserPassword Indicates if the encryption key should be encrypted with the user's password.
3715
 * @param string $encryptionKey The encryption key.
3716
 * @param string $passwordClear The clear text password.
3717
 * @return string The encryption key.
3718
 */
3719
function manageEncryptionKey(bool $encryptWithUserPassword, string $encryptionKey, string $passwordClear): string
3720
{
3721
    return $encryptWithUserPassword && empty($encryptionKey) ? $passwordClear : $encryptionKey;
3722
}
3723
3724
/**
3725
 * Creates a background process.
3726
 *
3727
 * @param int $userId The user's ID.
3728
 * @param string $passwordClear The clear text password.
3729
 * @param string $encryptionKey The encryption key.
3730
 * @param bool $sendEmailToUser Indicates if an email should be sent to the user.
3731
 * @param string $emailBody The email body.
3732
 * @param bool $user_self_change Indicates if the user is changing the password themselves.
3733
 * @return int The ID of the background process.
3734
 */
3735
function createBackgroundProcess(int $userId, string $passwordClear, string $encryptionKey, bool $sendEmailToUser, string $emailBody, bool $user_self_change): int
3736
{
3737
    $session = SessionManager::getSession();
3738
    $lang = new Language($session->get('user-language') ?? 'english');
3739
    $userTP = DB::queryFirstRow(
3740
        'SELECT pw, public_key, private_key
3741
        FROM ' . prefixTable('users') . '
3742
        WHERE id = %i',
3743
        TP_USER_ID
3744
    );
3745
3746
    DB::insert(
3747
        prefixTable('background_tasks'),
3748
        array(
3749
            'created_at' => time(),
3750
            'process_type' => 'create_user_keys',
3751
            'arguments' => json_encode([
3752
                'new_user_id' => $userId,
3753
                'new_user_pwd' => cryption($passwordClear, '', 'encrypt')['string'],
3754
                'new_user_code' => cryption(empty($encryptionKey) ? uniqidReal(20) : $encryptionKey, '', 'encrypt')['string'],
3755
                'owner_id' => TP_USER_ID,
3756
                'creator_pwd' => $userTP['pw'],
3757
                'send_email' => $sendEmailToUser ? 1 : 0,
3758
                'otp_provided_new_value' => 1,
3759
                'email_body' => empty($emailBody) ? '' : $lang->get($emailBody),
3760
                'user_self_change' => $user_self_change ? 1 : 0,
3761
            ]),
3762
        )
3763
    );
3764
3765
    return DB::insertId();
3766
}
3767
3768
3769
function updateUserStatus(int $userId, int $processId): void
3770
{    	
3771
    DB::update(
3772
        prefixTable('users'),
3773
        [
3774
            'is_ready_for_usage' => 0,
3775
            'otp_provided' => 1,
3776
            'ongoing_process_id' => $processId,
3777
            'special' => 'generate-keys',
3778
        ],
3779
        'id=%i',
3780
        $userId
3781
    );
3782
}
3783
3784
/**
3785
 * Updates the status of a user based on the given user ID and process ID.
3786
 *
3787
 * @param int $userId The ID of the user whose status is to be updated.
3788
 * @param int $processId The ID of the process that determines the new status of the user.
3789
 *
3790
 * @return void
3791
 */
3792
function createUserTasks(int $processId, int $nbItemsToTreat): void
3793
{
3794
    DB::insert(
3795
        prefixTable('background_subtasks'),
3796
        array(
3797
            'task_id' => $processId,
3798
            'created_at' => time(),
3799
            'task' => json_encode([
3800
                'step' => 'step0',
3801
                'index' => 0,
3802
                'nb' => $nbItemsToTreat,
3803
            ]),
3804
        )
3805
    );
3806
3807
    DB::insert(
3808
        prefixTable('background_subtasks'),
3809
        array(
3810
            'task_id' => $processId,
3811
            'created_at' => time(),
3812
            'task' => json_encode([
3813
                'step' => 'step10',
3814
                'index' => 0,
3815
                'nb' => $nbItemsToTreat,
3816
            ]),
3817
        )
3818
    );
3819
3820
    DB::insert(
3821
        prefixTable('background_subtasks'),
3822
        array(
3823
            'task_id' => $processId,
3824
            'created_at' => time(),
3825
            'task' => json_encode([
3826
                'step' => 'step20',
3827
                'index' => 0,
3828
                'nb' => $nbItemsToTreat,
3829
            ]),
3830
        )
3831
    );
3832
3833
    DB::insert(
3834
        prefixTable('background_subtasks'),
3835
        array(
3836
            'task_id' => $processId,
3837
            'created_at' => time(),
3838
            'task' => json_encode([
3839
                'step' => 'step30',
3840
                'index' => 0,
3841
                'nb' => $nbItemsToTreat,
3842
            ]),
3843
        )
3844
    );
3845
3846
    DB::insert(
3847
        prefixTable('background_subtasks'),
3848
        array(
3849
            'task_id' => $processId,
3850
            'created_at' => time(),
3851
            'task' => json_encode([
3852
                'step' => 'step40',
3853
                'index' => 0,
3854
                'nb' => $nbItemsToTreat,
3855
            ]),
3856
        )
3857
    );
3858
3859
    DB::insert(
3860
        prefixTable('background_subtasks'),
3861
        array(
3862
            'task_id' => $processId,
3863
            'created_at' => time(),
3864
            'task' => json_encode([
3865
                'step' => 'step50',
3866
                'index' => 0,
3867
                'nb' => $nbItemsToTreat,
3868
            ]),
3869
        )
3870
    );
3871
3872
    DB::insert(
3873
        prefixTable('background_subtasks'),
3874
        array(
3875
            'task_id' => $processId,
3876
            'created_at' => time(),
3877
            'task' => json_encode([
3878
                'step' => 'step60',
3879
                'index' => 0,
3880
                'nb' => $nbItemsToTreat,
3881
            ]),
3882
        )
3883
    );
3884
}
3885
3886
/**
3887
 * Permits to check the consistency of date versus columns definition
3888
 *
3889
 * @param string $table
3890
 * @param array $dataFields
3891
 * @return array
3892
 */
3893
function validateDataFields(
3894
    string $table,
3895
    array $dataFields
3896
): array
3897
{
3898
    // Get table structure
3899
    $result = DB::query(
3900
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3901
        DB_NAME,
3902
        $table
3903
    );
3904
3905
    foreach ($result as $row) {
3906
        $field = $row['COLUMN_NAME'];
3907
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3908
3909
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3910
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3911
                return [
3912
                    'state' => false,
3913
                    'field' => $field,
3914
                    'maxLength' => $maxLength,
3915
                    'currentLength' => strlen((string) $dataFields[$field]),
3916
                ];
3917
            }
3918
        }
3919
    }
3920
    
3921
    return [
3922
        'state' => true,
3923
        'message' => '',
3924
    ];
3925
}
3926
3927
/**
3928
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3929
 *
3930
 * @param string $string
3931
 * @return string
3932
 */
3933
function filterVarBack(string $string): string
3934
{
3935
    $arr = [
3936
        '&#060;' => '<',
3937
        '&#062;' => '>',
3938
        '&#034;' => '"',
3939
        '&#039;' => "'",
3940
        '&#038;' => '&',
3941
    ];
3942
3943
    foreach ($arr as $key => $value) {
3944
        $string = str_replace($key, $value, $string);
3945
    }
3946
3947
    return $string;
3948
}
3949
3950
/**
3951
 * 
3952
 */
3953
function storeTask(
3954
    string $taskName,
3955
    int $user_id,
3956
    int $is_personal_folder,
3957
    int $folder_destination_id,
3958
    int $item_id,
3959
    string $object_keys,
3960
    array $fields_keys = [],
3961
    array $files_keys = []
3962
)
3963
{
3964
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3965
        // Create process
3966
        DB::insert(
3967
            prefixTable('background_tasks'),
3968
            array(
3969
                'created_at' => time(),
3970
                'process_type' => $taskName,
3971
                'arguments' => json_encode([
3972
                    'item_id' => $item_id,
3973
                    'object_key' => $object_keys,
3974
                ]),
3975
                'item_id' => $item_id,
3976
            )
3977
        );
3978
        $processId = DB::insertId();
3979
3980
        // Create tasks
3981
        // 1- Create password sharekeys for users of this new ITEM
3982
        DB::insert(
3983
            prefixTable('background_subtasks'),
3984
            array(
3985
                'task_id' => $processId,
3986
                'created_at' => time(),
3987
                'task' => json_encode([
3988
                    'step' => 'create_users_pwd_key',
3989
                    'index' => 0,
3990
                ]),
3991
            )
3992
        );
3993
3994
        // 2- Create fields sharekeys for users of this new ITEM
3995
        DB::insert(
3996
            prefixTable('background_subtasks'),
3997
            array(
3998
                'task_id' => $processId,
3999
                'created_at' => time(),
4000
                'task' => json_encode([
4001
                    'step' => 'create_users_fields_key',
4002
                    'index' => 0,
4003
                    'fields_keys' => $fields_keys,
4004
                ]),
4005
            )
4006
        );
4007
4008
        // 3- Create files sharekeys for users of this new ITEM
4009
        DB::insert(
4010
            prefixTable('background_subtasks'),
4011
            array(
4012
                'task_id' => $processId,
4013
                'created_at' => time(),
4014
                'task' => json_encode([
4015
                    'step' => 'create_users_files_key',
4016
                    'index' => 0,
4017
                    'files_keys' => $files_keys,
4018
                ]),
4019
            )
4020
        );
4021
    }
4022
}
4023
4024
/**
4025
 * 
4026
 */
4027
function createTaskForItem(
4028
    string $processType,
4029
    string|array $taskName,
4030
    int $itemId,
4031
    int $userId,
4032
    string $objectKey,
4033
    int $parentId = -1,
4034
    array $fields_keys = [],
4035
    array $files_keys = []
4036
)
4037
{
4038
    // 1- Create main process
4039
    // ---
4040
    
4041
    // Create process
4042
    DB::insert(
4043
        prefixTable('background_tasks'),
4044
        array(
4045
            'created_at' => time(),
4046
            'process_type' => $processType,
4047
            'arguments' => json_encode([
4048
                'all_users_except_id' => (int) $userId,
4049
                'item_id' => (int) $itemId,
4050
                'object_key' => $objectKey,
4051
                'author' => (int) $userId,
4052
            ]),
4053
            'item_id' => (int) $parentId !== -1 ?  $parentId : null,
4054
        )
4055
    );
4056
    $processId = DB::insertId();
4057
4058
    // 2- Create expected tasks
4059
    // ---
4060
    if (is_array($taskName) === false) {
0 ignored issues
show
introduced by
The condition is_array($taskName) === false is always false.
Loading history...
4061
        $taskName = [$taskName];
4062
    }
4063
    foreach($taskName as $task) {
4064
        if (WIP === true) error_log('createTaskForItem - task: '.$task);
4065
        switch ($task) {
4066
            case 'item_password':
4067
                
4068
                DB::insert(
4069
                    prefixTable('background_subtasks'),
4070
                    array(
4071
                        'task_id' => $processId,
4072
                        'created_at' => time(),
4073
                        'task' => json_encode([
4074
                            'step' => 'create_users_pwd_key',
4075
                            'index' => 0,
4076
                        ]),
4077
                    )
4078
                );
4079
4080
                break;
4081
            case 'item_field':
4082
                
4083
                DB::insert(
4084
                    prefixTable('background_subtasks'),
4085
                    array(
4086
                        'task_id' => $processId,
4087
                        'created_at' => time(),
4088
                        'task' => json_encode([
4089
                            'step' => 'create_users_fields_key',
4090
                            'index' => 0,
4091
                            'fields_keys' => $fields_keys,
4092
                        ]),
4093
                    )
4094
                );
4095
4096
                break;
4097
            case 'item_file':
4098
4099
                DB::insert(
4100
                    prefixTable('background_subtasks'),
4101
                    array(
4102
                        'task_id' => $processId,
4103
                        'created_at' => time(),
4104
                        'task' => json_encode([
4105
                            'step' => 'create_users_files_key',
4106
                            'index' => 0,
4107
                            'fields_keys' => $files_keys,
4108
                        ]),
4109
                    )
4110
                );
4111
                break;
4112
            default:
4113
                # code...
4114
                break;
4115
        }
4116
    }
4117
}
4118
4119
4120
function deleteProcessAndRelatedTasks(int $processId)
4121
{
4122
    // Delete process
4123
    DB::delete(
4124
        prefixTable('background_tasks'),
4125
        'id=%i',
4126
        $processId
4127
    );
4128
4129
    // Delete tasks
4130
    DB::delete(
4131
        prefixTable('background_subtasks'),
4132
        'task_id=%i',
4133
        $processId
4134
    );
4135
4136
}
4137
4138
/**
4139
 * Return PHP binary path
4140
 *
4141
 * @return string
4142
 */
4143
function getPHPBinary(): string
4144
{
4145
    // Get PHP binary path
4146
    $phpBinaryFinder = new PhpExecutableFinder();
4147
    $phpBinaryPath = $phpBinaryFinder->find();
4148
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4149
}
4150
4151
4152
4153
/**
4154
 * Delete unnecessary keys for personal items
4155
 *
4156
 * @param boolean $allUsers
4157
 * @param integer $user_id
4158
 * @return void
4159
 */
4160
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4161
{
4162
    if ($allUsers === true) {
4163
        // Load class DB
4164
        if (class_exists('DB') === false) {
4165
            loadClasses('DB');
4166
        }
4167
4168
        $users = DB::query(
4169
            'SELECT id
4170
            FROM ' . prefixTable('users') . '
4171
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4172
            ORDER BY login ASC'
4173
        );
4174
        foreach ($users as $user) {
4175
            purgeUnnecessaryKeysForUser((int) $user['id']);
4176
        }
4177
    } else {
4178
        purgeUnnecessaryKeysForUser((int) $user_id);
4179
    }
4180
}
4181
4182
/**
4183
 * Delete unnecessary keys for personal items
4184
 *
4185
 * @param integer $user_id
4186
 * @return void
4187
 */
4188
function purgeUnnecessaryKeysForUser(int $user_id=0)
4189
{
4190
    if ($user_id === 0) {
4191
        return;
4192
    }
4193
4194
    // Load class DB
4195
    loadClasses('DB');
4196
4197
    $personalItems = DB::queryFirstColumn(
4198
        'SELECT id
4199
        FROM ' . prefixTable('items') . ' AS i
4200
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4201
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4202
        $user_id
4203
    );
4204
    if (count($personalItems) > 0) {
4205
        // Item keys
4206
        DB::delete(
4207
            prefixTable('sharekeys_items'),
4208
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4209
            $personalItems,
4210
            $user_id
4211
        );
4212
        // Files keys
4213
        DB::delete(
4214
            prefixTable('sharekeys_files'),
4215
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4216
            $personalItems,
4217
            $user_id
4218
        );
4219
        // Fields keys
4220
        DB::delete(
4221
            prefixTable('sharekeys_fields'),
4222
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4223
            $personalItems,
4224
            $user_id
4225
        );
4226
        // Logs keys
4227
        DB::delete(
4228
            prefixTable('sharekeys_logs'),
4229
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4230
            $personalItems,
4231
            $user_id
4232
        );
4233
    }
4234
}
4235
4236
/**
4237
 * Generate recovery keys file
4238
 *
4239
 * @param integer $userId
4240
 * @param array $SETTINGS
4241
 * @return string
4242
 */
4243
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4244
{
4245
    $session = SessionManager::getSession();
4246
    // Check if user exists
4247
    $userInfo = DB::queryFirstRow(
4248
        'SELECT login
4249
        FROM ' . prefixTable('users') . '
4250
        WHERE id = %i',
4251
        $userId
4252
    );
4253
4254
    if (DB::count() > 0) {
4255
        $now = (int) time();
4256
        // Prepare file content
4257
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4258
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4259
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4260
            "Public Key:\n".$session->get('user-public_key')."\n\n".
4261
            "Private Key:\n".$session->get('user-private_key')."\n\n";
4262
4263
        // Update user's keys_recovery_time
4264
        DB::update(
4265
            prefixTable('users'),
4266
            [
4267
                'keys_recovery_time' => $now,
4268
            ],
4269
            'id=%i',
4270
            $userId
4271
        );
4272
        $session->set('user-keys_recovery_time', $now);
4273
4274
        //Log into DB the user's disconnection
4275
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4276
        
4277
        // Return data
4278
        return prepareExchangedData(
4279
            array(
4280
                'error' => false,
4281
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4282
                'timestamp' => $now,
4283
                'content' => base64_encode($export_value),
4284
                'login' => $userInfo['login'],
4285
            ),
4286
            'encode'
4287
        );
4288
    }
4289
4290
    return prepareExchangedData(
4291
        array(
4292
            'error' => true,
4293
            'datetime' => '',
4294
        ),
4295
        'encode'
4296
    );
4297
}
4298
4299
/**
4300
 * Permits to load expected classes
4301
 *
4302
 * @param string $className
4303
 * @return void
4304
 */
4305
function loadClasses(string $className = ''): void
4306
{
4307
    require_once __DIR__. '/../includes/config/include.php';
4308
    require_once __DIR__. '/../includes/config/settings.php';
4309
    require_once __DIR__.'/../vendor/autoload.php';
4310
4311
    if (defined('DB_PASSWD_CLEAR') === false) {
4312
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4313
    }
4314
4315
    if (empty($className) === false) {
4316
        // Load class DB
4317
        if ((string) $className === 'DB') {
4318
            //Connect to DB
4319
            DB::$host = DB_HOST;
4320
            DB::$user = DB_USER;
4321
            DB::$password = DB_PASSWD_CLEAR;
4322
            DB::$dbName = DB_NAME;
4323
            DB::$port = DB_PORT;
4324
            DB::$encoding = DB_ENCODING;
4325
            DB::$ssl = DB_SSL;
4326
            DB::$connect_options = DB_CONNECT_OPTIONS;
4327
        }
4328
    }
4329
}
4330
4331
/**
4332
 * Returns the page the user is visiting.
4333
 *
4334
 * @return string The page name
4335
 */
4336
function getCurrectPage($SETTINGS)
4337
{
4338
    
4339
    $request = SymfonyRequest::createFromGlobals();
4340
4341
    // Parse the url
4342
    parse_str(
4343
        substr(
4344
            (string) $request->getRequestUri(),
4345
            strpos((string) $request->getRequestUri(), '?') + 1
4346
        ),
4347
        $result
4348
    );
4349
4350
    return $result['page'];
4351
}
4352
4353
/**
4354
 * Permits to return value if set
4355
 *
4356
 * @param string|int $value
4357
 * @param string|int|null $retFalse
4358
 * @param string|int $retTrue
4359
 * @return mixed
4360
 */
4361
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4362
{
4363
4364
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4365
}
4366
4367
4368
/**
4369
 * SEnd email to user
4370
 *
4371
 * @param string $post_receipt
4372
 * @param string $post_body
4373
 * @param string $post_subject
4374
 * @param array $post_replace
4375
 * @param boolean $immediate_email
4376
 * @param string $encryptedUserPassword
4377
 * @return string
4378
 */
4379
function sendMailToUser(
4380
    string $post_receipt,
4381
    string $post_body,
4382
    string $post_subject,
4383
    array $post_replace,
4384
    bool $immediate_email = false,
4385
    $encryptedUserPassword = ''
4386
): ?string {
4387
    global $SETTINGS;
4388
    $emailSettings = new EmailSettings($SETTINGS);
4389
    $emailService = new EmailService();
4390
    $antiXss = new AntiXSS();
4391
4392
    // Sanitize inputs
4393
    $post_receipt = filter_var($post_receipt, FILTER_SANITIZE_EMAIL);
4394
    $post_subject = $antiXss->xss_clean($post_subject);
4395
    $post_body = $antiXss->xss_clean($post_body);
4396
4397
    if (count($post_replace) > 0) {
4398
        $post_body = str_replace(
4399
            array_keys($post_replace),
4400
            array_values($post_replace),
4401
            $post_body
4402
        );
4403
    }
4404
4405
    // Remove newlines to prevent header injection
4406
    $post_body = str_replace(array("\r", "\n"), '', $post_body);    
4407
4408
    if ($immediate_email === true) {
4409
        // Send email
4410
        $ret = $emailService->sendMail(
4411
            $post_subject,
4412
            $post_body,
0 ignored issues
show
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 76
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 76
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 77
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 77
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 80
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 80
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 78
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 78
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 79
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 79
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4338
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 98
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 113
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 113
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 119
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 119
  7. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3071
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3071
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3071
  10. file_put_contents() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3088

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 77
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 77
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 79
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 79
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 80
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 80
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 78
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 78
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 76
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 76
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4381
  10. Data is passed through xss_clean(), and $antiXss->xss_clean($post_body) is assigned to $post_body
    in sources/main.functions.php on line 4395
  11. Data is passed through str_replace(), and ``str_replace(array(' ', ' '), '', $post_body)`` is assigned to $post_body
    in sources/main.functions.php on line 4406

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4338
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 98
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 113
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 113
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 119
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 119
  7. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3071
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3071
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3071
  10. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3131
  11. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1620
  12. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1685
  13. PHPMailer::sendmailSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1685
  14. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1724
  15. fwrite() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1776

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
4413
            $post_receipt,
4414
            $emailSettings,
4415
            '',
4416
            false
4417
        );
4418
    
4419
        $ret = json_decode($ret, true);
4420
    
4421
        return prepareExchangedData(
4422
            array(
4423
                'error' => empty($ret['error']) === true ? false : true,
4424
                'message' => $ret['message'],
4425
            ),
4426
            'encode'
4427
        );
4428
    } else {
4429
        // Send through task handler
4430
        prepareSendingEmail(
4431
            $post_subject,
4432
            $post_body,
4433
            $post_receipt,
4434
            "",
4435
            $encryptedUserPassword,
4436
        );
4437
    }
4438
4439
    return null;
4440
}
4441
4442
/**
4443
 * Converts a password strengh value to zxcvbn level
4444
 * 
4445
 * @param integer $passwordStrength
4446
 * 
4447
 * @return integer
4448
 */
4449
function convertPasswordStrength($passwordStrength): int
4450
{
4451
    if ($passwordStrength === 0) {
4452
        return TP_PW_STRENGTH_1;
4453
    } else if ($passwordStrength === 1) {
4454
        return TP_PW_STRENGTH_2;
4455
    } else if ($passwordStrength === 2) {
4456
        return TP_PW_STRENGTH_3;
4457
    } else if ($passwordStrength === 3) {
4458
        return TP_PW_STRENGTH_4;
4459
    } else {
4460
        return TP_PW_STRENGTH_5;
4461
    }
4462
}
4463
4464
/**
4465
 * Check that a password is strong. The password needs to have at least :
4466
 *   - length >= 10.
4467
 *   - Uppercase and lowercase chars.
4468
 *   - Number or special char.
4469
 *   - Not contain username, name or mail part.
4470
 *   - Different from previous password.
4471
 * 
4472
 * @param string $password - Password to ckeck.
4473
 * @return bool - true if the password is strong, false otherwise.
4474
 */
4475
function isPasswordStrong($password) {
4476
    $session = SessionManager::getSession();
4477
4478
    // Password can't contain login, name or lastname
4479
    $forbiddenWords = [
4480
        $session->get('user-login'),
4481
        $session->get('user-name'),
4482
        $session->get('user-lastname'),
4483
    ];
4484
4485
    // Cut out the email
4486
    if ($email = $session->get('user-email')) {
4487
        $emailParts = explode('@', $email);
4488
4489
        if (count($emailParts) === 2) {
4490
            // Mail username (removed @domain.tld)
4491
            $forbiddenWords[] = $emailParts[0];
4492
4493
            // Organisation name (removed username@ and .tld)
4494
            $domain = explode('.', $emailParts[1]);
4495
            if (count($domain) > 1)
4496
                $forbiddenWords[] = $domain[0];
4497
        }
4498
    }
4499
4500
    // Search forbidden words in password
4501
    foreach ($forbiddenWords as $word) {
4502
        if (empty($word))
4503
            continue;
4504
4505
        // Stop if forbidden word found in password
4506
        if (stripos($password, $word) !== false)
4507
            return false;
4508
    }
4509
4510
    // Get password complexity
4511
    $length = strlen($password);
4512
    $hasUppercase = preg_match('/[A-Z]/', $password);
4513
    $hasLowercase = preg_match('/[a-z]/', $password);
4514
    $hasNumber = preg_match('/[0-9]/', $password);
4515
    $hasSpecialChar = preg_match('/[\W_]/', $password);
4516
4517
    // Get current user hash
4518
    $userHash = DB::queryFirstRow(
4519
        "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
4520
        $session->get('user-id')
4521
    )['pw'];
4522
4523
    $passwordManager = new PasswordManager();
4524
    
4525
    return $length >= 8
4526
           && $hasUppercase
4527
           && $hasLowercase
4528
           && ($hasNumber || $hasSpecialChar)
4529
           && !$passwordManager->verifyPassword($userHash, $password);
4530
}
4531