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

createBackgroundProcess()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 22
nc 1
nop 6
dl 0
loc 31
rs 9.2568
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.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