Passed
Pull Request — master (#4447)
by
unknown
06:54
created

identAdmin()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 32
c 2
b 0
f 0
nc 6
nop 3
dl 0
loc 45
rs 9.0968
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
        $err = 'an attack! either the wrong key was loaded, or the ciphertext has changed since it was created either corrupted in the database or intentionally modified by someone trying to carry out an attack.';
138
    } catch (CryptoException\BadFormatException $ex) {
139
        $err = $ex;
140
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
141
        $err = $ex;
142
    } catch (CryptoException\CryptoException $ex) {
143
        $err = $ex;
144
    } catch (CryptoException\IOException $ex) {
145
        $err = $ex;
146
    }
147
148
    return [
149
        'string' => $text ?? '',
150
        'error' => $err,
151
    ];
152
}
153
154
/**
155
 * Generating a defuse key.
156
 *
157
 * @return string
158
 */
159
function defuse_generate_key()
160
{
161
    $key = Key::createNewRandomKey();
162
    $key = $key->saveToAsciiSafeString();
163
    return $key;
164
}
165
166
/**
167
 * Generate a Defuse personal key.
168
 *
169
 * @param string $psk psk used
170
 *
171
 * @return string
172
 */
173
function defuse_generate_personal_key(string $psk): string
174
{
175
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
176
    return $protected_key->saveToAsciiSafeString(); // save this in user table
177
}
178
179
/**
180
 * Validate persoanl key with defuse.
181
 *
182
 * @param string $psk                   the user's psk
183
 * @param string $protected_key_encoded special key
184
 *
185
 * @return string
186
 */
187
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
188
{
189
    try {
190
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
191
        $user_key = $protected_key_encoded->unlockKey($psk);
192
        $user_key_encoded = $user_key->saveToAsciiSafeString();
193
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
194
        return 'Error - Major issue as the encryption is broken.';
195
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
196
        return 'Error - The saltkey is not the correct one.';
197
    }
198
199
    return $user_key_encoded;
200
    // store it in session once user has entered his psk
201
}
202
203
/**
204
 * Decrypt a defuse string if encrypted.
205
 *
206
 * @param string $value Encrypted string
207
 *
208
 * @return string Decrypted string
209
 */
210
function defuseReturnDecrypted(string $value, $SETTINGS): string
211
{
212
    if (substr($value, 0, 3) === 'def') {
213
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
214
    }
215
216
    return $value;
217
}
218
219
/**
220
 * Trims a string depending on a specific string.
221
 *
222
 * @param string|array $chaine  what to trim
223
 * @param string       $element trim on what
224
 *
225
 * @return string
226
 */
227
function trimElement($chaine, string $element): string
228
{
229
    if (! empty($chaine)) {
230
        if (is_array($chaine) === true) {
231
            $chaine = implode(';', $chaine);
232
        }
233
        $chaine = trim($chaine);
234
        if (substr($chaine, 0, 1) === $element) {
235
            $chaine = substr($chaine, 1);
236
        }
237
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
238
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
239
        }
240
    }
241
242
    return $chaine;
243
}
244
245
/**
246
 * Permits to suppress all "special" characters from string.
247
 *
248
 * @param string $string  what to clean
249
 * @param bool   $special use of special chars?
250
 *
251
 * @return string
252
 */
253
function cleanString(string $string, bool $special = false): string
254
{
255
    // Create temporary table for special characters escape
256
    $tabSpecialChar = [];
257
    for ($i = 0; $i <= 31; ++$i) {
258
        $tabSpecialChar[] = chr($i);
259
    }
260
    array_push($tabSpecialChar, '<br />');
261
    if ((int) $special === 1) {
262
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
263
    }
264
265
    return str_replace($tabSpecialChar, "\n", $string);
266
}
267
268
/**
269
 * Erro manager for DB.
270
 *
271
 * @param array $params output from query
272
 *
273
 * @return void
274
 */
275
function db_error_handler(array $params): void
276
{
277
    echo 'Error: ' . $params['error'] . "<br>\n";
278
    echo 'Query: ' . $params['query'] . "<br>\n";
279
    throw new Exception('Error - Query', 1);
280
}
281
282
/**
283
 * Identify user's rights
284
 *
285
 * @param string|array $groupesVisiblesUser  [description]
286
 * @param string|array $groupesInterditsUser [description]
287
 * @param string       $isAdmin              [description]
288
 * @param string       $idFonctions          [description]
289
 *
290
 * @return bool
291
 */
292
function identifyUserRights(
293
    $groupesVisiblesUser,
294
    $groupesInterditsUser,
295
    $isAdmin,
296
    $idFonctions,
297
    $SETTINGS
298
) {
299
    $session = SessionManager::getSession();
300
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
301
302
    // Check if user is ADMINISTRATOR    
303
    (int) $isAdmin === 1 ?
304
        identAdmin(
305
            $idFonctions,
306
            $SETTINGS, /** @scrutinizer ignore-type */
307
            $tree
308
        )
309
        :
310
        identUser(
311
            $groupesVisiblesUser,
312
            $groupesInterditsUser,
313
            $idFonctions,
314
            $SETTINGS, /** @scrutinizer ignore-type */
315
            $tree
316
        );
317
318
    // update user's timestamp
319
    DB::update(
320
        prefixTable('users'),
321
        [
322
            'timestamp' => time(),
323
        ],
324
        'id=%i',
325
        $session->get('user-id')
326
    );
327
328
    return true;
329
}
330
331
/**
332
 * Identify administrator.
333
 *
334
 * @param string $idFonctions Roles of user
335
 * @param array  $SETTINGS    Teampass settings
336
 * @param object $tree        Tree of folders
337
 *
338
 * @return bool
339
 */
340
function identAdmin($idFonctions, $SETTINGS, $tree)
341
{
342
    
343
    $session = SessionManager::getSession();
344
    $groupesVisibles = [];
345
    $session->set('user-personal_folders', []);
346
    $session->set('user-accessible_folders', []);
347
    $session->set('user-no_access_folders', []);
348
    $session->set('user-personal_visible_folders', []);
349
    $session->set('user-read_only_folders', []);
350
    $session->set('system-list_restricted_folders_for_items', []);
351
    $session->set('system-list_folders_editable_by_role', []);
352
    $session->set('user-list_folders_limited', []);
353
    $session->set('user-forbiden_personal_folders', []);
354
    $globalsUserId = $session->get('user-id');
0 ignored issues
show
Unused Code introduced by
The assignment to $globalsUserId is dead and can be removed.
Loading history...
355
    $globalsVisibleFolders = $session->get('user-accessible_folders');
0 ignored issues
show
Unused Code introduced by
The assignment to $globalsVisibleFolders is dead and can be removed.
Loading history...
356
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
0 ignored issues
show
Unused Code introduced by
The assignment to $globalsPersonalVisibleFolders is dead and can be removed.
Loading history...
357
    // Get list of Folders
358
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
359
    foreach ($rows as $record) {
360
        array_push($groupesVisibles, $record['id']);
361
    }
362
    $session->set('user-accessible_folders', $groupesVisibles);
363
    $session->set('user-all_non_personal_folders', $groupesVisibles);
364
365
    // get complete list of ROLES
366
    $tmp = explode(';', $idFonctions);
367
    $rows = DB::query(
368
        'SELECT * FROM ' . prefixTable('roles_title') . '
369
        ORDER BY title ASC'
370
    );
371
    foreach ($rows as $record) {
372
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
373
            array_push($tmp, $record['id']);
374
        }
375
    }
376
    $session->set('user-roles', implode(';', $tmp));
377
    $session->set('user-admin', 1);
378
    // Check if admin has created Folders and Roles
379
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
380
    $session->set('user-nb_folders', DB::count());
381
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
382
    $session->set('user-nb_roles', DB::count());
383
384
    return true;
385
}
386
387
/**
388
 * Permits to convert an element to array.
389
 *
390
 * @param string|array $element Any value to be returned as array
391
 *
392
 * @return array
393
 */
394
function convertToArray($element): ?array
395
{
396
    if (is_string($element) === true) {
397
        if (empty($element) === true) {
398
            return [];
399
        }
400
        return explode(
401
            ';',
402
            trimElement($element, ';')
403
        );
404
    }
405
    return $element;
406
}
407
408
/**
409
 * Defines the rights the user has.
410
 *
411
 * @param string|array $allowedFolders  Allowed folders
412
 * @param string|array $noAccessFolders Not allowed folders
413
 * @param string|array $userRoles       Roles of user
414
 * @param array        $SETTINGS        Teampass settings
415
 * @param object       $tree            Tree of folders
416
 * 
417
 * @return bool
418
 */
419
function identUser(
420
    $allowedFolders,
421
    $noAccessFolders,
422
    $userRoles,
423
    array $SETTINGS,
424
    object $tree
425
) {
426
    
427
    $session = SessionManager::getSession();
428
    // Init
429
    $session->set('user-accessible_folders', []);
430
    $session->set('user-personal_folders', []);
431
    $session->set('user-no_access_folders', []);
432
    $session->set('user-personal_visible_folders', []);
433
    $session->set('user-read_only_folders', []);
434
    $session->set('user-user-roles', $userRoles);
435
    $session->set('user-admin', 0);
436
    // init
437
    $personalFolders = [];
438
    $readOnlyFolders = [];
439
    $noAccessPersonalFolders = [];
440
    $restrictedFoldersForItems = [];
441
    $foldersLimited = [];
442
    $foldersLimitedFull = [];
443
    $allowedFoldersByRoles = [];
444
    $globalsUserId = $session->get('user-id');
445
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
446
    // Ensure consistency in array format
447
    $noAccessFolders = convertToArray($noAccessFolders);
448
    $userRoles = convertToArray($userRoles);
449
    $allowedFolders = convertToArray($allowedFolders);
450
    
451
    // Get list of folders depending on Roles
452
    $arrays = identUserGetFoldersFromRoles(
453
        $userRoles,
454
        $allowedFoldersByRoles,
455
        $readOnlyFolders,
456
        $allowedFolders
457
    );
458
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
459
    $readOnlyFolders = $arrays['readOnlyFolders'];
460
461
    // Does this user is allowed to see other items
462
    $inc = 0;
463
    $rows = DB::query(
464
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
465
            WHERE restricted_to LIKE %ss AND inactif = %s'.
466
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
467
        $globalsUserId,
468
        '0'
469
    );
470
    foreach ($rows as $record) {
471
        // Exclude restriction on item if folder is fully accessible
472
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
473
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
474
            ++$inc;
475
        //}
476
    }
477
478
    // Check for the users roles if some specific rights exist on items
479
    $rows = DB::query(
480
        'SELECT i.id_tree, r.item_id
481
        FROM ' . prefixTable('items') . ' as i
482
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
483
        WHERE i.id_tree <> "" '.
484
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
485
        'ORDER BY i.id_tree ASC',
486
        $userRoles
487
    );
488
    $inc = 0;
489
    foreach ($rows as $record) {
490
        //if (isset($record['id_tree'])) {
491
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
492
            array_push($foldersLimitedFull, $record['id_tree']);
493
            ++$inc;
494
        //}
495
    }
496
497
    // Get list of Personal Folders
498
    $arrays = identUserGetPFList(
499
        $globalsPersonalFolders,
500
        $allowedFolders,
501
        $globalsUserId,
502
        $personalFolders,
503
        $noAccessPersonalFolders,
504
        $foldersLimitedFull,
505
        $allowedFoldersByRoles,
506
        array_keys($restrictedFoldersForItems),
507
        $readOnlyFolders,
508
        $noAccessFolders,
509
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
510
        $tree
511
    );
512
    $allowedFolders = $arrays['allowedFolders'];
513
    $personalFolders = $arrays['personalFolders'];
514
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
515
516
    // Return data
517
    $session->set('user-all_non_personal_folders', $allowedFolders);
518
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
519
    $session->set('user-read_only_folders', $readOnlyFolders);
520
    $session->set('user-no_access_folders', $noAccessFolders);
521
    $session->set('user-personal_folders', $personalFolders);
522
    $session->set('user-list_folders_limited', $foldersLimited);
523
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
524
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
525
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
526
    $session->set(
527
        'all_folders_including_no_access',
528
        array_unique(array_merge(
529
            $allowedFolders,
530
            $personalFolders,
531
            $noAccessFolders,
532
            $readOnlyFolders
533
        ), SORT_NUMERIC)
534
    );
535
    // Folders and Roles numbers
536
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
537
    $session->set('user-nb_folders', DB::count());
538
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
539
    $session->set('user-nb_roles', DB::count());
540
    // check if change proposals on User's items
541
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
542
        $countNewItems = DB::query(
543
            'SELECT COUNT(*)
544
            FROM ' . prefixTable('items_change') . ' AS c
545
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
546
            WHERE i.action = %s AND i.id_user = %i',
547
            'at_creation',
548
            $globalsUserId
549
        );
550
        $session->set('user-nb_item_change_proposals', $countNewItems);
551
    } else {
552
        $session->set('user-nb_item_change_proposals', 0);
553
    }
554
555
    return true;
556
}
557
558
/**
559
 * Get list of folders depending on Roles
560
 * 
561
 * @param array $userRoles
562
 * @param array $allowedFoldersByRoles
563
 * @param array $readOnlyFolders
564
 * @param array $allowedFolders
565
 * 
566
 * @return array
567
 */
568
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
569
{
570
    $rows = DB::query(
571
        'SELECT *
572
        FROM ' . prefixTable('roles_values') . '
573
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
574
        ['W', 'ND', 'NE', 'NDNE', 'R'],
575
        $userRoles,
576
    );
577
    foreach ($rows as $record) {
578
        if ($record['type'] === 'R') {
579
            array_push($readOnlyFolders, $record['folder_id']);
580
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
581
            array_push($allowedFoldersByRoles, $record['folder_id']);
582
        }
583
    }
584
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
585
    $readOnlyFolders = array_unique($readOnlyFolders);
586
    
587
    // Clean arrays
588
    foreach ($allowedFoldersByRoles as $value) {
589
        $key = array_search($value, $readOnlyFolders);
590
        if ($key !== false) {
591
            unset($readOnlyFolders[$key]);
592
        }
593
    }
594
    return [
595
        'readOnlyFolders' => $readOnlyFolders,
596
        'allowedFoldersByRoles' => $allowedFoldersByRoles
597
    ];
598
}
599
600
/**
601
 * Get list of Personal Folders
602
 * 
603
 * @param int $globalsPersonalFolders
604
 * @param array $allowedFolders
605
 * @param int $globalsUserId
606
 * @param array $personalFolders
607
 * @param array $noAccessPersonalFolders
608
 * @param array $foldersLimitedFull
609
 * @param array $allowedFoldersByRoles
610
 * @param array $restrictedFoldersForItems
611
 * @param array $readOnlyFolders
612
 * @param array $noAccessFolders
613
 * @param int $enablePfFeature
614
 * @param object $tree
615
 * 
616
 * @return array
617
 */
618
function identUserGetPFList(
619
    $globalsPersonalFolders,
620
    $allowedFolders,
621
    $globalsUserId,
622
    $personalFolders,
623
    $noAccessPersonalFolders,
624
    $foldersLimitedFull,
625
    $allowedFoldersByRoles,
626
    $restrictedFoldersForItems,
627
    $readOnlyFolders,
628
    $noAccessFolders,
629
    $enablePfFeature,
630
    $tree
631
)
632
{
633
    if (
634
        (int) $enablePfFeature === 1
635
        && (int) $globalsPersonalFolders === 1
636
    ) {
637
        $persoFld = DB::queryfirstrow(
638
            'SELECT id
639
            FROM ' . prefixTable('nested_tree') . '
640
            WHERE title = %s AND personal_folder = %i'.
641
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
642
            $globalsUserId,
643
            1
644
        );
645
        if (empty($persoFld['id']) === false) {
646
            array_push($personalFolders, $persoFld['id']);
647
            array_push($allowedFolders, $persoFld['id']);
648
            // get all descendants
649
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
650
            foreach ($ids as $id) {
651
                //array_push($allowedFolders, $id);
652
                array_push($personalFolders, $id);
653
            }
654
        }
655
    }
656
    
657
    // Exclude all other PF
658
    $where = new WhereClause('and');
659
    $where->add('personal_folder=%i', 1);
660
    if (count($personalFolders) > 0) {
661
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
662
    }
663
    if (
664
        (int) $enablePfFeature === 1
665
        && (int) $globalsPersonalFolders === 1
666
    ) {
667
        $where->add('title=%s', $globalsUserId);
668
        $where->negateLast();
669
    }
670
    $persoFlds = DB::query(
671
        'SELECT id
672
        FROM ' . prefixTable('nested_tree') . '
673
        WHERE %l',
674
        $where
675
    );
676
    foreach ($persoFlds as $persoFldId) {
677
        array_push($noAccessPersonalFolders, $persoFldId['id']);
678
    }
679
680
    // All folders visibles
681
    $allowedFolders = array_unique(array_merge(
682
        $allowedFolders,
683
        $foldersLimitedFull,
684
        $allowedFoldersByRoles,
685
        $restrictedFoldersForItems,
686
        $readOnlyFolders
687
    ), SORT_NUMERIC);
688
    // Exclude from allowed folders all the specific user forbidden folders
689
    if (count($noAccessFolders) > 0) {
690
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
691
    }
692
693
    return [
694
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
695
        'personalFolders' => $personalFolders,
696
        'noAccessPersonalFolders' => $noAccessPersonalFolders
697
    ];
698
}
699
700
701
/**
702
 * Update the CACHE table.
703
 *
704
 * @param string $action   What to do
705
 * @param array  $SETTINGS Teampass settings
706
 * @param int    $ident    Ident format
707
 * 
708
 * @return void
709
 */
710
function updateCacheTable(string $action, ?int $ident = null): void
711
{
712
    if ($action === 'reload') {
713
        // Rebuild full cache table
714
        cacheTableRefresh();
715
    } elseif ($action === 'update_value' && is_null($ident) === false) {
716
        // UPDATE an item
717
        cacheTableUpdate($ident);
718
    } elseif ($action === 'add_value' && is_null($ident) === false) {
719
        // ADD an item
720
        cacheTableAdd($ident);
721
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
722
        // DELETE an item
723
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
724
    }
725
}
726
727
/**
728
 * Cache table - refresh.
729
 *
730
 * @return void
731
 */
732
function cacheTableRefresh(): void
733
{
734
    // Load class DB
735
    loadClasses('DB');
736
737
    //Load Tree
738
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
739
    // truncate table
740
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
741
    // reload date
742
    $rows = DB::query(
743
        'SELECT *
744
        FROM ' . prefixTable('items') . ' as i
745
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
746
        AND l.action = %s
747
        AND i.inactif = %i',
748
        'at_creation',
749
        0
750
    );
751
    foreach ($rows as $record) {
752
        if (empty($record['id_tree']) === false) {
753
            // Get all TAGS
754
            $tags = '';
755
            $itemTags = DB::query(
756
                'SELECT tag
757
                FROM ' . prefixTable('tags') . '
758
                WHERE item_id = %i AND tag != ""',
759
                $record['id']
760
            );
761
            foreach ($itemTags as $itemTag) {
762
                $tags .= $itemTag['tag'] . ' ';
763
            }
764
765
            // Get renewal period
766
            $resNT = DB::queryfirstrow(
767
                'SELECT renewal_period
768
                FROM ' . prefixTable('nested_tree') . '
769
                WHERE id = %i',
770
                $record['id_tree']
771
            );
772
            // form id_tree to full foldername
773
            $folder = [];
774
            $arbo = $tree->getPath($record['id_tree'], true);
775
            foreach ($arbo as $elem) {
776
                // Check if title is the ID of a user
777
                if (is_numeric($elem->title) === true) {
778
                    // Is this a User id?
779
                    $user = DB::queryfirstrow(
780
                        'SELECT id, login
781
                        FROM ' . prefixTable('users') . '
782
                        WHERE id = %i',
783
                        $elem->title
784
                    );
785
                    if (count($user) > 0) {
786
                        $elem->title = $user['login'];
787
                    }
788
                }
789
                // Build path
790
                array_push($folder, stripslashes($elem->title));
791
            }
792
            // store data
793
            DB::insert(
794
                prefixTable('cache'),
795
                [
796
                    'id' => $record['id'],
797
                    'label' => $record['label'],
798
                    'description' => $record['description'] ?? '',
799
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
800
                    'tags' => $tags,
801
                    'id_tree' => $record['id_tree'],
802
                    'perso' => $record['perso'],
803
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
804
                    'login' => $record['login'] ?? '',
805
                    'folder' => implode(' > ', $folder),
806
                    'author' => $record['id_user'],
807
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
808
                    'timestamp' => $record['date'],
809
                ]
810
            );
811
        }
812
    }
813
}
814
815
/**
816
 * Cache table - update existing value.
817
 *
818
 * @param int    $ident    Ident format
819
 * 
820
 * @return void
821
 */
822
function cacheTableUpdate(?int $ident = null): void
823
{
824
    $session = SessionManager::getSession();
825
    loadClasses('DB');
826
827
    //Load Tree
828
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
829
    // get new value from db
830
    $data = DB::queryfirstrow(
831
        'SELECT label, description, id_tree, perso, restricted_to, login, url
832
        FROM ' . prefixTable('items') . '
833
        WHERE id=%i',
834
        $ident
835
    );
836
    // Get all TAGS
837
    $tags = '';
838
    $itemTags = DB::query(
839
        'SELECT tag
840
            FROM ' . prefixTable('tags') . '
841
            WHERE item_id = %i AND tag != ""',
842
        $ident
843
    );
844
    foreach ($itemTags as $itemTag) {
845
        $tags .= $itemTag['tag'] . ' ';
846
    }
847
    // form id_tree to full foldername
848
    $folder = [];
849
    $arbo = $tree->getPath($data['id_tree'], true);
850
    foreach ($arbo as $elem) {
851
        // Check if title is the ID of a user
852
        if (is_numeric($elem->title) === true) {
853
            // Is this a User id?
854
            $user = DB::queryfirstrow(
855
                'SELECT id, login
856
                FROM ' . prefixTable('users') . '
857
                WHERE id = %i',
858
                $elem->title
859
            );
860
            if (count($user) > 0) {
861
                $elem->title = $user['login'];
862
            }
863
        }
864
        // Build path
865
        array_push($folder, stripslashes($elem->title));
866
    }
867
    // finaly update
868
    DB::update(
869
        prefixTable('cache'),
870
        [
871
            'label' => $data['label'],
872
            'description' => $data['description'],
873
            'tags' => $tags,
874
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
875
            'id_tree' => $data['id_tree'],
876
            'perso' => $data['perso'],
877
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
878
            'login' => $data['login'] ?? '',
879
            'folder' => implode(' » ', $folder),
880
            'author' => $session->get('user-id'),
881
        ],
882
        'id = %i',
883
        $ident
884
    );
885
}
886
887
/**
888
 * Cache table - add new value.
889
 *
890
 * @param int    $ident    Ident format
891
 * 
892
 * @return void
893
 */
894
function cacheTableAdd(?int $ident = null): void
895
{
896
    $session = SessionManager::getSession();
897
    $globalsUserId = $session->get('user-id');
898
899
    // Load class DB
900
    loadClasses('DB');
901
902
    //Load Tree
903
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
904
    // get new value from db
905
    $data = DB::queryFirstRow(
906
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
907
        FROM ' . prefixTable('items') . ' as i
908
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
909
        WHERE i.id = %i
910
        AND l.action = %s',
911
        $ident,
912
        'at_creation'
913
    );
914
    // Get all TAGS
915
    $tags = '';
916
    $itemTags = DB::query(
917
        'SELECT tag
918
            FROM ' . prefixTable('tags') . '
919
            WHERE item_id = %i AND tag != ""',
920
        $ident
921
    );
922
    foreach ($itemTags as $itemTag) {
923
        $tags .= $itemTag['tag'] . ' ';
924
    }
925
    // form id_tree to full foldername
926
    $folder = [];
927
    $arbo = $tree->getPath($data['id_tree'], true);
928
    foreach ($arbo as $elem) {
929
        // Check if title is the ID of a user
930
        if (is_numeric($elem->title) === true) {
931
            // Is this a User id?
932
            $user = DB::queryfirstrow(
933
                'SELECT id, login
934
                FROM ' . prefixTable('users') . '
935
                WHERE id = %i',
936
                $elem->title
937
            );
938
            if (count($user) > 0) {
939
                $elem->title = $user['login'];
940
            }
941
        }
942
        // Build path
943
        array_push($folder, stripslashes($elem->title));
944
    }
945
    // finaly update
946
    DB::insert(
947
        prefixTable('cache'),
948
        [
949
            'id' => $data['id'],
950
            'label' => $data['label'],
951
            'description' => $data['description'],
952
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
953
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
954
            'id_tree' => $data['id_tree'],
955
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
956
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
957
            'login' => $data['login'] ?? '',
958
            'folder' => implode(' » ', $folder),
959
            'author' => $globalsUserId,
960
            'timestamp' => $data['date'],
961
        ]
962
    );
963
}
964
965
/**
966
 * Do statistics.
967
 *
968
 * @param array $SETTINGS Teampass settings
969
 *
970
 * @return array
971
 */
972
function getStatisticsData(array $SETTINGS): array
973
{
974
    DB::query(
975
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
976
        0
977
    );
978
    $counter_folders = DB::count();
979
    DB::query(
980
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
981
        1
982
    );
983
    $counter_folders_perso = DB::count();
984
    DB::query(
985
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
986
        0
987
    );
988
    $counter_items = DB::count();
989
        DB::query(
990
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
991
        1
992
    );
993
    $counter_items_perso = DB::count();
994
        DB::query(
995
        'SELECT id FROM ' . prefixTable('users') . ' WHERE login NOT IN (%s, %s, %s)',
996
        'OTV', 'TP', 'API'
997
    );
998
    $counter_users = DB::count();
999
        DB::query(
1000
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1001
        1
1002
    );
1003
    $admins = DB::count();
1004
    DB::query(
1005
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1006
        1
1007
    );
1008
    $managers = DB::count();
1009
    DB::query(
1010
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1011
        1
1012
    );
1013
    $readOnly = DB::count();
1014
    // list the languages
1015
    $usedLang = [];
1016
    $tp_languages = DB::query(
1017
        'SELECT name FROM ' . prefixTable('languages')
1018
    );
1019
    foreach ($tp_languages as $tp_language) {
1020
        DB::query(
1021
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1022
            $tp_language['name']
1023
        );
1024
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1025
    }
1026
1027
    // get list of ips
1028
    $usedIp = [];
1029
    $tp_ips = DB::query(
1030
        'SELECT user_ip FROM ' . prefixTable('users')
1031
    );
1032
    foreach ($tp_ips as $ip) {
1033
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1034
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1035
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1036
            $usedIp[$ip['user_ip']] = 1;
1037
        }
1038
    }
1039
1040
    return [
1041
        'error' => '',
1042
        'stat_phpversion' => phpversion(),
1043
        'stat_folders' => $counter_folders,
1044
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1045
        'stat_items' => $counter_items,
1046
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1047
        'stat_users' => $counter_users,
1048
        'stat_admins' => $admins,
1049
        'stat_managers' => $managers,
1050
        'stat_ro' => $readOnly,
1051
        'stat_kb' => $SETTINGS['enable_kb'],
1052
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1053
        'stat_fav' => $SETTINGS['enable_favourites'],
1054
        'stat_teampassversion' => TP_VERSION,
1055
        'stat_ldap' => $SETTINGS['ldap_mode'],
1056
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1057
        'stat_duo' => $SETTINGS['duo'],
1058
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1059
        'stat_api' => $SETTINGS['api'],
1060
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1061
        'stat_syslog' => $SETTINGS['syslog_enable'],
1062
        'stat_2fa' => $SETTINGS['google_authentication'],
1063
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1064
        'stat_mysqlversion' => DB::serverVersion(),
1065
        'stat_languages' => $usedLang,
1066
        'stat_country' => $usedIp,
1067
    ];
1068
}
1069
1070
/**
1071
 * Permits to prepare the way to send the email
1072
 * 
1073
 * @param string $subject       email subject
1074
 * @param string $body          email message
1075
 * @param string $email         email
1076
 * @param string $receiverName  Receiver name
1077
 * @param string $encryptedUserPassword      encryptedUserPassword
1078
 *
1079
 * @return void
1080
 */
1081
function prepareSendingEmail(
1082
    $subject,
1083
    $body,
1084
    $email,
1085
    $receiverName = '',
1086
    $encryptedUserPassword = ''
1087
): void 
1088
{
1089
    DB::insert(
1090
        prefixTable('background_tasks'),
1091
        array(
1092
            'created_at' => time(),
1093
            'process_type' => 'send_email',
1094
            'arguments' => json_encode([
1095
                'subject' => $subject,
1096
                'receivers' => $email,
1097
                'body' => $body,
1098
                'receiver_name' => $receiverName,
1099
                'encryptedUserPassword' => $encryptedUserPassword,
1100
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1101
        )
1102
    );
1103
}
1104
1105
/**
1106
 * Returns the email body.
1107
 *
1108
 * @param string $textMail Text for the email
1109
 */
1110
function emailBody(string $textMail): string
1111
{
1112
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1113
    w3.org/TR/html4/loose.dtd"><html>
1114
    <head><title>Email Template</title>
1115
    <style type="text/css">
1116
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1117
    </style></head>
1118
    <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">
1119
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1120
    <tr><td style="border-collapse: collapse;"><br>
1121
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1122
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1123
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1124
        </td></tr></table></td>
1125
    </tr>
1126
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1127
        <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;">
1128
        <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;">
1129
        <br><div style="float:right;">' .
1130
        $textMail .
1131
        '<br><br></td></tr></table>
1132
    </td></tr></table>
1133
    <br></body></html>';
1134
}
1135
1136
/**
1137
 * Convert date to timestamp.
1138
 *
1139
 * @param string $date        The date
1140
 * @param string $date_format Date format
1141
 *
1142
 * @return int
1143
 */
1144
function dateToStamp(string $date, string $date_format): int
1145
{
1146
    $date = date_parse_from_format($date_format, $date);
1147
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1148
        return mktime(
1149
            empty($date['hour']) === false ? $date['hour'] : 23,
1150
            empty($date['minute']) === false ? $date['minute'] : 59,
1151
            empty($date['second']) === false ? $date['second'] : 59,
1152
            $date['month'],
1153
            $date['day'],
1154
            $date['year']
1155
        );
1156
    }
1157
    return 0;
1158
}
1159
1160
/**
1161
 * Is this a date.
1162
 *
1163
 * @param string $date Date
1164
 *
1165
 * @return bool
1166
 */
1167
function isDate(string $date): bool
1168
{
1169
    return strtotime($date) !== false;
1170
}
1171
1172
/**
1173
 * Check if isUTF8().
1174
 *
1175
 * @param string|array $string Is the string
1176
 *
1177
 * @return int is the string in UTF8 format
1178
 */
1179
function isUTF8($string): int
1180
{
1181
    if (is_array($string) === true) {
1182
        $string = $string['string'];
1183
    }
1184
1185
    return preg_match(
1186
        '%^(?:
1187
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1188
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1189
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1190
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1191
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1192
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1193
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1194
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1195
        )*$%xs',
1196
        $string
1197
    );
1198
}
1199
1200
/**
1201
 * Prepare an array to UTF8 format before JSON_encode.
1202
 *
1203
 * @param array $array Array of values
1204
 *
1205
 * @return array
1206
 */
1207
function utf8Converter(array $array): array
1208
{
1209
    array_walk_recursive(
1210
        $array,
1211
        static function (&$item): void {
1212
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1213
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1214
            }
1215
        }
1216
    );
1217
    return $array;
1218
}
1219
1220
/**
1221
 * Permits to prepare data to be exchanged.
1222
 *
1223
 * @param array|string $data Text
1224
 * @param string       $type Parameter
1225
 * @param string       $key  Optional key
1226
 *
1227
 * @return string|array
1228
 */
1229
function prepareExchangedData($data, string $type, ?string $key = null)
1230
{
1231
    $session = SessionManager::getSession();
1232
    
1233
    // Perform
1234
    if ($type === 'encode' && is_array($data) === true) {
1235
        // json encoding
1236
        $data = json_encode(
1237
            $data,
1238
            JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1239
        );
1240
1241
        // Now encrypt
1242
        if ($session->get('encryptClientServer') === 1) {
1243
            $data = Encryption::encrypt(
1244
                $data,
1245
                $session->get('key')
1246
            );
1247
        }
1248
1249
        return $data;
1250
    }
1251
1252
    if ($type === 'decode' && is_array($data) === false) {
1253
        // Decrypt if needed
1254
        if ($session->get('encryptClientServer') === 1) {
1255
            $data = (string) Encryption::decrypt(
1256
                (string) $data,
1257
                $session->get('key')
1258
            );
1259
        } else {
1260
            // Double html encoding received
1261
            $data = html_entity_decode(html_entity_decode($data));
0 ignored issues
show
Bug introduced by
$data of type array is incompatible with the type string expected by parameter $string of html_entity_decode(). ( Ignorable by Annotation )

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

1261
            $data = html_entity_decode(html_entity_decode(/** @scrutinizer ignore-type */ $data));
Loading history...
1262
        }
1263
1264
        // Check if $data is a valid string before json_decode
1265
        if (is_string($data) && !empty($data)) {
1266
            // Return data array
1267
            return json_decode($data, true);
1268
        }
1269
    }
1270
1271
    return '';
1272
}
1273
1274
1275
/**
1276
 * Create a thumbnail.
1277
 *
1278
 * @param string  $src           Source
1279
 * @param string  $dest          Destination
1280
 * @param int $desired_width Size of width
1281
 * 
1282
 * @return void|string|bool
1283
 */
1284
function makeThumbnail(string $src, string $dest, int $desired_width)
1285
{
1286
    /* read the source image */
1287
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1288
        $source_image = imagecreatefrompng($src);
1289
        if ($source_image === false) {
1290
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1291
        }
1292
    } else {
1293
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1294
    }
1295
1296
    // Get height and width
1297
    $width = imagesx($source_image);
1298
    $height = imagesy($source_image);
1299
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1300
    $desired_height = (int) floor($height * $desired_width / $width);
1301
    /* create a new, "virtual" image */
1302
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1303
    if ($virtual_image === false) {
1304
        return false;
1305
    }
1306
    /* copy source image at a resized size */
1307
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1308
    /* create the physical thumbnail image to its destination */
1309
    imagejpeg($virtual_image, $dest);
1310
}
1311
1312
/**
1313
 * Check table prefix in SQL query.
1314
 *
1315
 * @param string $table Table name
1316
 * 
1317
 * @return string
1318
 */
1319
function prefixTable(string $table): string
1320
{
1321
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1322
    if (empty($safeTable) === false) {
1323
        // sanitize string
1324
        return $safeTable;
1325
    }
1326
    // stop error no table
1327
    return 'table_not_exists';
1328
}
1329
1330
/**
1331
 * GenerateCryptKey
1332
 *
1333
 * @param int     $size      Length
1334
 * @param bool $secure Secure
1335
 * @param bool $numerals Numerics
1336
 * @param bool $uppercase Uppercase letters
1337
 * @param bool $symbols Symbols
1338
 * @param bool $lowercase Lowercase
1339
 * 
1340
 * @return string
1341
 */
1342
function GenerateCryptKey(
1343
    int $size = 20,
1344
    bool $secure = false,
1345
    bool $numerals = false,
1346
    bool $uppercase = false,
1347
    bool $symbols = false,
1348
    bool $lowercase = false
1349
): string {
1350
    $generator = new ComputerPasswordGenerator();
1351
    $generator->setRandomGenerator(new Php7RandomGenerator());
1352
    
1353
    // Manage size
1354
    $generator->setLength((int) $size);
1355
    if ($secure === true) {
1356
        $generator->setSymbols(true);
1357
        $generator->setLowercase(true);
1358
        $generator->setUppercase(true);
1359
        $generator->setNumbers(true);
1360
    } else {
1361
        $generator->setLowercase($lowercase);
1362
        $generator->setUppercase($uppercase);
1363
        $generator->setNumbers($numerals);
1364
        $generator->setSymbols($symbols);
1365
    }
1366
1367
    return $generator->generatePasswords()[0];
1368
}
1369
1370
/**
1371
 * GenerateGenericPassword
1372
 *
1373
 * @param int     $size      Length
1374
 * @param bool $secure Secure
1375
 * @param bool $numerals Numerics
1376
 * @param bool $uppercase Uppercase letters
1377
 * @param bool $symbols Symbols
1378
 * @param bool $lowercase Lowercase
1379
 * @param array   $SETTINGS  SETTINGS
1380
 * 
1381
 * @return string
1382
 */
1383
function generateGenericPassword(
1384
    int $size,
1385
    bool $secure,
1386
    bool $lowercase,
1387
    bool $capitalize,
1388
    bool $numerals,
1389
    bool $symbols,
1390
    array $SETTINGS
1391
): string
1392
{
1393
    if ((int) $size > (int) $SETTINGS['pwd_maximum_length']) {
1394
        return prepareExchangedData(
1395
            array(
1396
                'error_msg' => 'Password length is too long! ',
1397
                'error' => 'true',
1398
            ),
1399
            'encode'
1400
        );
1401
    }
1402
    // Load libraries
1403
    $generator = new ComputerPasswordGenerator();
1404
    $generator->setRandomGenerator(new Php7RandomGenerator());
1405
1406
    // Manage size
1407
    $generator->setLength(($size <= 0) ? 10 : $size);
1408
1409
    if ($secure === true) {
1410
        $generator->setSymbols(true);
1411
        $generator->setLowercase(true);
1412
        $generator->setUppercase(true);
1413
        $generator->setNumbers(true);
1414
    } else {
1415
        $generator->setLowercase($lowercase);
1416
        $generator->setUppercase($capitalize);
1417
        $generator->setNumbers($numerals);
1418
        $generator->setSymbols($symbols);
1419
    }
1420
1421
    return prepareExchangedData(
1422
        array(
1423
            'key' => $generator->generatePasswords(),
1424
            'error' => '',
1425
        ),
1426
        'encode'
1427
    );
1428
}
1429
1430
/**
1431
 * Send sysLOG message
1432
 *
1433
 * @param string    $message
1434
 * @param string    $host
1435
 * @param int       $port
1436
 * @param string    $component
1437
 * 
1438
 * @return void
1439
*/
1440
function send_syslog($message, $host, $port, $component = 'teampass'): void
1441
{
1442
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1443
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1444
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1445
    socket_close($sock);
1446
}
1447
1448
/**
1449
 * Permits to log events into DB
1450
 *
1451
 * @param array  $SETTINGS Teampass settings
1452
 * @param string $type     Type
1453
 * @param string $label    Label
1454
 * @param string $who      Who
1455
 * @param string $login    Login
1456
 * @param string|int $field_1  Field
1457
 * 
1458
 * @return void
1459
 */
1460
function logEvents(
1461
    array $SETTINGS, 
1462
    string $type, 
1463
    string $label, 
1464
    string $who, 
1465
    ?string $login = null, 
1466
    $field_1 = null
1467
): void
1468
{
1469
    if (empty($who)) {
1470
        $who = getClientIpServer();
1471
    }
1472
1473
    // Load class DB
1474
    loadClasses('DB');
1475
1476
    DB::insert(
1477
        prefixTable('log_system'),
1478
        [
1479
            'type' => $type,
1480
            'date' => time(),
1481
            'label' => $label,
1482
            'qui' => $who,
1483
            'field_1' => $field_1 === null ? '' : $field_1,
1484
        ]
1485
    );
1486
    // If SYSLOG
1487
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1488
        if ($type === 'user_mngt') {
1489
            send_syslog(
1490
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1491
                $SETTINGS['syslog_host'],
1492
                $SETTINGS['syslog_port'],
1493
                'teampass'
1494
            );
1495
        } else {
1496
            send_syslog(
1497
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1498
                $SETTINGS['syslog_host'],
1499
                $SETTINGS['syslog_port'],
1500
                'teampass'
1501
            );
1502
        }
1503
    }
1504
}
1505
1506
/**
1507
 * Log events.
1508
 *
1509
 * @param array  $SETTINGS        Teampass settings
1510
 * @param int    $item_id         Item id
1511
 * @param string $item_label      Item label
1512
 * @param int    $id_user         User id
1513
 * @param string $action          Code for reason
1514
 * @param string $login           User login
1515
 * @param string $raison          Code for reason
1516
 * @param string $encryption_type Encryption on
1517
 * @param string $time Encryption Time
1518
 * @param string $old_value       Old value
1519
 * 
1520
 * @return void
1521
 */
1522
function logItems(
1523
    array $SETTINGS,
1524
    int $item_id,
1525
    string $item_label,
1526
    int $id_user,
1527
    string $action,
1528
    ?string $login = null,
1529
    ?string $raison = null,
1530
    ?string $encryption_type = null,
1531
    ?string $time = null,
1532
    ?string $old_value = null
1533
): void {
1534
    // Load class DB
1535
    loadClasses('DB');
1536
1537
    // Insert log in DB
1538
    DB::insert(
1539
        prefixTable('log_items'),
1540
        [
1541
            'id_item' => $item_id,
1542
            'date' => is_null($time) === true ? time() : $time,
1543
            'id_user' => $id_user,
1544
            'action' => $action,
1545
            'raison' => $raison,
1546
            'old_value' => $old_value,
1547
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1548
        ]
1549
    );
1550
    // Timestamp the last change
1551
    if (in_array($action, ['at_creation', 'at_modifiation', 'at_delete', 'at_import'], true)) {
1552
        DB::update(
1553
            prefixTable('misc'),
1554
            [
1555
                'valeur' => time(),
1556
                'updated_at' => time(),
1557
            ],
1558
            'type = %s AND intitule = %s',
1559
            'timestamp',
1560
            'last_item_change'
1561
        );
1562
    }
1563
1564
    // SYSLOG
1565
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1566
        // Extract reason
1567
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1568
        // Get item info if not known
1569
        if (empty($item_label) === true) {
1570
            $dataItem = DB::queryfirstrow(
1571
                'SELECT id, id_tree, label
1572
                FROM ' . prefixTable('items') . '
1573
                WHERE id = %i',
1574
                $item_id
1575
            );
1576
            $item_label = $dataItem['label'];
1577
        }
1578
1579
        send_syslog(
1580
            'action=' . str_replace('at_', '', $action) .
1581
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1582
                ' itemno=' . $item_id .
1583
                ' user=' . (is_null($login) === true ? '' : addslashes((string) $login)) .
1584
                ' itemname="' . addslashes($item_label) . '"',
1585
            $SETTINGS['syslog_host'],
1586
            $SETTINGS['syslog_port'],
1587
            'teampass'
1588
        );
1589
    }
1590
1591
    // send notification if enabled
1592
    //notifyOnChange($item_id, $action, $SETTINGS);
1593
}
1594
1595
/**
1596
 * Prepare notification email to subscribers.
1597
 *
1598
 * @param int    $item_id  Item id
1599
 * @param string $label    Item label
1600
 * @param array  $changes  List of changes
1601
 * @param array  $SETTINGS Teampass settings
1602
 * 
1603
 * @return void
1604
 */
1605
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1606
{
1607
    $session = SessionManager::getSession();
1608
    $lang = new Language($session->get('user-language') ?? 'english');
1609
    $globalsUserId = $session->get('user-id');
1610
    $globalsLastname = $session->get('user-lastname');
1611
    $globalsName = $session->get('user-name');
1612
    // send email to user that what to be notified
1613
    $notification = DB::queryOneColumn(
1614
        'email',
1615
        'SELECT *
1616
        FROM ' . prefixTable('notification') . ' AS n
1617
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1618
        WHERE n.item_id = %i AND n.user_id != %i',
1619
        $item_id,
1620
        $globalsUserId
1621
    );
1622
    if (DB::count() > 0) {
1623
        // Prepare path
1624
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1625
        // Get list of changes
1626
        $htmlChanges = '<ul>';
1627
        foreach ($changes as $change) {
1628
            $htmlChanges .= '<li>' . $change . '</li>';
1629
        }
1630
        $htmlChanges .= '</ul>';
1631
        // send email
1632
        DB::insert(
1633
            prefixTable('emails'),
1634
            [
1635
                'timestamp' => time(),
1636
                'subject' => $lang->get('email_subject_item_updated'),
1637
                'body' => str_replace(
1638
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1639
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1640
                    $lang->get('email_body_item_updated')
1641
                ),
1642
                'receivers' => implode(',', $notification),
1643
                'status' => '',
1644
            ]
1645
        );
1646
    }
1647
}
1648
1649
/**
1650
 * Returns the Item + path.
1651
 *
1652
 * @param int    $id_tree  Node id
1653
 * @param string $label    Label
1654
 * @param array  $SETTINGS TP settings
1655
 * 
1656
 * @return string
1657
 */
1658
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1659
{
1660
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1661
    $arbo = $tree->getPath($id_tree, true);
1662
    $path = '';
1663
    foreach ($arbo as $elem) {
1664
        if (empty($path) === true) {
1665
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1666
        } else {
1667
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1668
        }
1669
    }
1670
1671
    // Build text to show user
1672
    if (empty($label) === false) {
1673
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1674
    }
1675
    return empty($path) === true ? '' : $path;
1676
}
1677
1678
/**
1679
 * Get the client ip address.
1680
 *
1681
 * @return string IP address
1682
 */
1683
function getClientIpServer(): string
1684
{
1685
    if (getenv('HTTP_CLIENT_IP')) {
1686
        $ipaddress = getenv('HTTP_CLIENT_IP');
1687
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1688
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1689
    } elseif (getenv('HTTP_X_FORWARDED')) {
1690
        $ipaddress = getenv('HTTP_X_FORWARDED');
1691
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1692
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1693
    } elseif (getenv('HTTP_FORWARDED')) {
1694
        $ipaddress = getenv('HTTP_FORWARDED');
1695
    } elseif (getenv('REMOTE_ADDR')) {
1696
        $ipaddress = getenv('REMOTE_ADDR');
1697
    } else {
1698
        $ipaddress = 'UNKNOWN';
1699
    }
1700
1701
    return $ipaddress;
1702
}
1703
1704
/**
1705
 * Escape all HTML, JavaScript, and CSS.
1706
 *
1707
 * @param string $input    The input string
1708
 * @param string $encoding Which character encoding are we using?
1709
 * 
1710
 * @return string
1711
 */
1712
function noHTML(string $input, string $encoding = 'UTF-8'): string
1713
{
1714
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1715
}
1716
1717
/**
1718
 * Rebuilds the Teampass config file.
1719
 *
1720
 * @param string $configFilePath Path to the config file.
1721
 * @param array  $settings       Teampass settings.
1722
 *
1723
 * @return string|bool
1724
 */
1725
function rebuildConfigFile(string $configFilePath, array $settings)
1726
{
1727
    // Perform a copy if the file exists
1728
    if (file_exists($configFilePath)) {
1729
        $backupFilePath = $configFilePath . '.' . date('Y_m_d_His', time());
1730
        if (!copy($configFilePath, $backupFilePath)) {
1731
            return "ERROR: Could not copy file '$configFilePath'";
1732
        }
1733
    }
1734
1735
    // Regenerate the config file
1736
    $data = ["<?php\n", "global \$SETTINGS;\n", "\$SETTINGS = array (\n"];
1737
    $rows = DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s', 'admin');
1738
    foreach ($rows as $record) {
1739
        $value = getEncryptedValue($record['valeur'], $record['is_encrypted']);
1740
        $data[] = "    '{$record['intitule']}' => '". htmlspecialchars_decode($value, ENT_COMPAT) . "',\n";
1741
    }
1742
    $data[] = ");\n";
1743
    $data = array_unique($data);
1744
1745
    // Update the file
1746
    file_put_contents($configFilePath, implode('', $data));
1747
1748
    return true;
1749
}
1750
1751
/**
1752
 * Returns the encrypted value if needed.
1753
 *
1754
 * @param string $value       Value to encrypt.
1755
 * @param int   $isEncrypted Is the value encrypted?
1756
 *
1757
 * @return string
1758
 */
1759
function getEncryptedValue(string $value, int $isEncrypted): string
1760
{
1761
    return $isEncrypted ? cryption($value, '', 'encrypt')['string'] : $value;
1762
}
1763
1764
/**
1765
 * Permits to replace &#92; to permit correct display
1766
 *
1767
 * @param string $input Some text
1768
 * 
1769
 * @return string
1770
 */
1771
function handleBackslash(string $input): string
1772
{
1773
    return str_replace('&amp;#92;', '&#92;', $input);
1774
}
1775
1776
/**
1777
 * Permits to load settings
1778
 * 
1779
 * @return void
1780
*/
1781
function loadSettings(): void
1782
{
1783
    global $SETTINGS;
1784
    /* LOAD CPASSMAN SETTINGS */
1785
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1786
        $SETTINGS = [];
1787
        $SETTINGS['duplicate_folder'] = 0;
1788
        //by default, this is set to 0;
1789
        $SETTINGS['duplicate_item'] = 0;
1790
        //by default, this is set to 0;
1791
        $SETTINGS['number_of_used_pw'] = 5;
1792
        //by default, this value is set to 5;
1793
        $settings = [];
1794
        $rows = DB::query(
1795
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1796
            [
1797
                'type' => 'admin',
1798
                'type2' => 'settings',
1799
            ]
1800
        );
1801
        foreach ($rows as $record) {
1802
            if ($record['type'] === 'admin') {
1803
                $SETTINGS[$record['intitule']] = $record['valeur'];
1804
            } else {
1805
                $settings[$record['intitule']] = $record['valeur'];
1806
            }
1807
        }
1808
        $SETTINGS['loaded'] = 1;
1809
        $SETTINGS['default_session_expiration_time'] = 5;
1810
    }
1811
}
1812
1813
/**
1814
 * check if folder has custom fields.
1815
 * Ensure that target one also has same custom fields
1816
 * 
1817
 * @param int $source_id
1818
 * @param int $target_id 
1819
 * 
1820
 * @return bool
1821
*/
1822
function checkCFconsistency(int $source_id, int $target_id): bool
1823
{
1824
    $source_cf = [];
1825
    $rows = DB::QUERY(
1826
        'SELECT id_category
1827
            FROM ' . prefixTable('categories_folders') . '
1828
            WHERE id_folder = %i',
1829
        $source_id
1830
    );
1831
    foreach ($rows as $record) {
1832
        array_push($source_cf, $record['id_category']);
1833
    }
1834
1835
    $target_cf = [];
1836
    $rows = DB::QUERY(
1837
        'SELECT id_category
1838
            FROM ' . prefixTable('categories_folders') . '
1839
            WHERE id_folder = %i',
1840
        $target_id
1841
    );
1842
    foreach ($rows as $record) {
1843
        array_push($target_cf, $record['id_category']);
1844
    }
1845
1846
    $cf_diff = array_diff($source_cf, $target_cf);
1847
    if (count($cf_diff) > 0) {
1848
        return false;
1849
    }
1850
1851
    return true;
1852
}
1853
1854
/**
1855
 * Will encrypte/decrypt a fil eusing Defuse.
1856
 *
1857
 * @param string $type        can be either encrypt or decrypt
1858
 * @param string $source_file path to source file
1859
 * @param string $target_file path to target file
1860
 * @param array  $SETTINGS    Settings
1861
 * @param string $password    A password
1862
 *
1863
 * @return string|bool
1864
 */
1865
function prepareFileWithDefuse(
1866
    string $type,
1867
    string $source_file,
1868
    string $target_file,
1869
    array $SETTINGS,
1870
    string $password = null
1871
) {
1872
    // Load AntiXSS
1873
    $antiXss = new AntiXSS();
1874
    // Protect against bad inputs
1875
    if (is_array($source_file) === true || is_array($target_file) === true) {
1876
        return 'error_cannot_be_array';
1877
    }
1878
1879
    // Sanitize
1880
    $source_file = $antiXss->xss_clean($source_file);
1881
    $target_file = $antiXss->xss_clean($target_file);
1882
    if (empty($password) === true || is_null($password) === true) {
1883
        // get KEY to define password
1884
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
1885
        $password = Key::loadFromAsciiSafeString($ascii_key);
1886
    }
1887
1888
    $err = '';
1889
    if ($type === 'decrypt') {
1890
        // Decrypt file
1891
        $err = defuseFileDecrypt(
1892
            $source_file,
1893
            $target_file,
1894
            $SETTINGS, /** @scrutinizer ignore-type */
1895
            $password
1896
        );
1897
    } elseif ($type === 'encrypt') {
1898
        // Encrypt file
1899
        $err = defuseFileEncrypt(
1900
            $source_file,
1901
            $target_file,
1902
            $SETTINGS, /** @scrutinizer ignore-type */
1903
            $password
1904
        );
1905
    }
1906
1907
    // return error
1908
    return $err === true ? $err : '';
1909
}
1910
1911
/**
1912
 * Encrypt a file with Defuse.
1913
 *
1914
 * @param string $source_file path to source file
1915
 * @param string $target_file path to target file
1916
 * @param array  $SETTINGS    Settings
1917
 * @param string $password    A password
1918
 *
1919
 * @return string|bool
1920
 */
1921
function defuseFileEncrypt(
1922
    string $source_file,
1923
    string $target_file,
1924
    array $SETTINGS,
1925
    string $password = null
1926
) {
1927
    try {
1928
        CryptoFile::encryptFileWithPassword(
1929
            $source_file,
1930
            $target_file,
1931
            $password
1932
        );
1933
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1934
        $err = 'wrong_key';
1935
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1936
        $err = $ex;
1937
    } catch (CryptoException\IOException $ex) {
1938
        $err = $ex;
1939
    }
1940
1941
    // return error
1942
    return empty($err) === false ? $err : true;
1943
}
1944
1945
/**
1946
 * Decrypt a file with Defuse.
1947
 *
1948
 * @param string $source_file path to source file
1949
 * @param string $target_file path to target file
1950
 * @param array  $SETTINGS    Settings
1951
 * @param string $password    A password
1952
 *
1953
 * @return string|bool
1954
 */
1955
function defuseFileDecrypt(
1956
    string $source_file,
1957
    string $target_file,
1958
    array $SETTINGS,
1959
    string $password = null
1960
) {
1961
    try {
1962
        CryptoFile::decryptFileWithPassword(
1963
            $source_file,
1964
            $target_file,
1965
            $password
1966
        );
1967
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1968
        $err = 'wrong_key';
1969
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1970
        $err = $ex;
1971
    } catch (CryptoException\IOException $ex) {
1972
        $err = $ex;
1973
    }
1974
1975
    // return error
1976
    return empty($err) === false ? $err : true;
1977
}
1978
1979
/*
1980
* NOT TO BE USED
1981
*/
1982
/**
1983
 * Undocumented function.
1984
 *
1985
 * @param string $text Text to debug
1986
 */
1987
function debugTeampass(string $text): void
1988
{
1989
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
1990
    if ($debugFile !== false) {
1991
        fputs($debugFile, $text);
1992
        fclose($debugFile);
1993
    }
1994
}
1995
1996
/**
1997
 * DELETE the file with expected command depending on server type.
1998
 *
1999
 * @param string $file     Path to file
2000
 * @param array  $SETTINGS Teampass settings
2001
 *
2002
 * @return void
2003
 */
2004
function fileDelete(string $file, array $SETTINGS): void
2005
{
2006
    // Load AntiXSS
2007
    $antiXss = new AntiXSS();
2008
    $file = $antiXss->xss_clean($file);
2009
    if (is_file($file)) {
2010
        unlink($file);
2011
    }
2012
}
2013
2014
/**
2015
 * Permits to extract the file extension.
2016
 *
2017
 * @param string $file File name
2018
 *
2019
 * @return string
2020
 */
2021
function getFileExtension(string $file): string
2022
{
2023
    if (strpos($file, '.') === false) {
2024
        return $file;
2025
    }
2026
2027
    return substr($file, strrpos($file, '.') + 1);
2028
}
2029
2030
/**
2031
 * Chmods files and folders with different permissions.
2032
 *
2033
 * This is an all-PHP alternative to using: \n
2034
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2035
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2036
 *
2037
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2038
  *
2039
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2040
 * @param int    $filePerm The permissions any found files should get.
2041
 * @param int    $dirPerm  The permissions any found folder should get.
2042
 *
2043
 * @return bool Returns TRUE if the path if found and FALSE if not.
2044
 *
2045
 * @warning The permission levels has to be entered in octal format, which
2046
 * normally means adding a zero ("0") in front of the permission level. \n
2047
 * More info at: http://php.net/chmod.
2048
*/
2049
2050
function recursiveChmod(
2051
    string $path,
2052
    int $filePerm = 0644,
2053
    int  $dirPerm = 0755
2054
) {
2055
    // Check if the path exists
2056
    $path = basename($path);
2057
    if (! file_exists($path)) {
2058
        return false;
2059
    }
2060
2061
    // See whether this is a file
2062
    if (is_file($path)) {
2063
        // Chmod the file with our given filepermissions
2064
        try {
2065
            chmod($path, $filePerm);
2066
        } catch (Exception $e) {
2067
            return false;
2068
        }
2069
    // If this is a directory...
2070
    } elseif (is_dir($path)) {
2071
        // Then get an array of the contents
2072
        $foldersAndFiles = scandir($path);
2073
        // Remove "." and ".." from the list
2074
        $entries = array_slice($foldersAndFiles, 2);
2075
        // Parse every result...
2076
        foreach ($entries as $entry) {
2077
            // And call this function again recursively, with the same permissions
2078
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2079
        }
2080
2081
        // When we are done with the contents of the directory, we chmod the directory itself
2082
        try {
2083
            chmod($path, $filePerm);
2084
        } catch (Exception $e) {
2085
            return false;
2086
        }
2087
    }
2088
2089
    // Everything seemed to work out well, return true
2090
    return true;
2091
}
2092
2093
/**
2094
 * Check if user can access to this item.
2095
 *
2096
 * @param int   $item_id ID of item
2097
 * @param array $SETTINGS
2098
 *
2099
 * @return bool|string
2100
 */
2101
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2102
{
2103
    
2104
    $session = SessionManager::getSession();
2105
    $session_groupes_visibles = $session->get('user-accessible_folders');
2106
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2107
    // Load item data
2108
    $data = DB::queryFirstRow(
2109
        'SELECT id_tree
2110
        FROM ' . prefixTable('items') . '
2111
        WHERE id = %i',
2112
        $item_id
2113
    );
2114
    // Check if user can access this folder
2115
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2116
        // Now check if this folder is restricted to user
2117
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2118
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2119
        ) {
2120
            return 'ERR_FOLDER_NOT_ALLOWED';
2121
        }
2122
    }
2123
2124
    return true;
2125
}
2126
2127
/**
2128
 * Creates a unique key.
2129
 *
2130
 * @param int $lenght Key lenght
2131
 *
2132
 * @return string
2133
 */
2134
function uniqidReal(int $lenght = 13): string
2135
{
2136
    if (function_exists('random_bytes')) {
2137
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2138
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2139
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2140
    } else {
2141
        throw new Exception('no cryptographically secure random function available');
2142
    }
2143
2144
    return substr(bin2hex($bytes), 0, $lenght);
2145
}
2146
2147
/**
2148
 * Obfuscate an email.
2149
 *
2150
 * @param string $email Email address
2151
 *
2152
 * @return string
2153
 */
2154
function obfuscateEmail(string $email): string
2155
{
2156
    $email = explode("@", $email);
2157
    $name = $email[0];
2158
    if (strlen($name) > 3) {
2159
        $name = substr($name, 0, 2);
2160
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2161
            $name .= "*";
2162
        }
2163
        $name .= substr($email[0], -1, 1);
2164
    }
2165
    $host = explode(".", $email[1])[0];
2166
    if (strlen($host) > 3) {
2167
        $host = substr($host, 0, 1);
2168
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2169
            $host .= "*";
2170
        }
2171
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2172
    }
2173
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2174
    return $email;
2175
}
2176
2177
/**
2178
 * Perform a Query.
2179
 *
2180
 * @param array  $SETTINGS Teamapss settings
2181
 * @param string $fields   Fields to use
2182
 * @param string $table    Table to use
2183
 *
2184
 * @return array
2185
 */
2186
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2187
{
2188
    // include librairies & connect to DB
2189
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2190
2191
    // Load class DB
2192
    loadClasses('DB');
2193
    
2194
    // Insert log in DB
2195
    return DB::query(
2196
        'SELECT ' . $fields . '
2197
        FROM ' . prefixTable($table)
2198
    );
2199
}
2200
2201
/**
2202
 * Undocumented function.
2203
 *
2204
 * @param int $bytes Size of file
2205
 *
2206
 * @return string
2207
 */
2208
function formatSizeUnits(int $bytes): string
2209
{
2210
    if ($bytes >= 1073741824) {
2211
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2212
    } elseif ($bytes >= 1048576) {
2213
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2214
    } elseif ($bytes >= 1024) {
2215
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2216
    } elseif ($bytes > 1) {
2217
        $bytes .= ' bytes';
2218
    } elseif ($bytes === 1) {
2219
        $bytes .= ' byte';
2220
    } else {
2221
        $bytes = '0 bytes';
2222
    }
2223
2224
    return $bytes;
2225
}
2226
2227
/**
2228
 * Generate user pair of keys.
2229
 *
2230
 * @param string $userPwd User password
2231
 *
2232
 * @return array
2233
 */
2234
function generateUserKeys(string $userPwd): array
2235
{
2236
    // Sanitize
2237
    $antiXss = new AntiXSS();
2238
    $userPwd = $antiXss->xss_clean($userPwd);
2239
    // Load classes
2240
    $rsa = new Crypt_RSA();
2241
    $cipher = new Crypt_AES();
2242
    // Create the private and public key
2243
    $res = $rsa->createKey(4096);
2244
    // Encrypt the privatekey
2245
    $cipher->setPassword($userPwd);
2246
    $privatekey = $cipher->encrypt($res['privatekey']);
2247
    return [
2248
        'private_key' => base64_encode($privatekey),
2249
        'public_key' => base64_encode($res['publickey']),
2250
        'private_key_clear' => base64_encode($res['privatekey']),
2251
    ];
2252
}
2253
2254
/**
2255
 * Permits to decrypt the user's privatekey.
2256
 *
2257
 * @param string $userPwd        User password
2258
 * @param string $userPrivateKey User private key
2259
 *
2260
 * @return string|object
2261
 */
2262
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2263
{
2264
    // Sanitize
2265
    $antiXss = new AntiXSS();
2266
    $userPwd = $antiXss->xss_clean($userPwd);
2267
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2268
2269
    if (empty($userPwd) === false) {
2270
        // Load classes
2271
        $cipher = new Crypt_AES();
2272
        // Encrypt the privatekey
2273
        $cipher->setPassword($userPwd);
2274
        try {
2275
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2276
        } catch (Exception $e) {
2277
            return $e;
2278
        }
2279
    }
2280
    return '';
2281
}
2282
2283
/**
2284
 * Permits to encrypt the user's privatekey.
2285
 *
2286
 * @param string $userPwd        User password
2287
 * @param string $userPrivateKey User private key
2288
 *
2289
 * @return string
2290
 */
2291
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2292
{
2293
    // Sanitize
2294
    $antiXss = new AntiXSS();
2295
    $userPwd = $antiXss->xss_clean($userPwd);
2296
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2297
2298
    if (empty($userPwd) === false) {
2299
        // Load classes
2300
        $cipher = new Crypt_AES();
2301
        // Encrypt the privatekey
2302
        $cipher->setPassword($userPwd);        
2303
        try {
2304
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2305
        } catch (Exception $e) {
2306
            return $e;
2307
        }
2308
    }
2309
    return '';
2310
}
2311
2312
/**
2313
 * Encrypts a string using AES.
2314
 *
2315
 * @param string $data String to encrypt
2316
 * @param string $key
2317
 *
2318
 * @return array
2319
 */
2320
function doDataEncryption(string $data, string $key = NULL): array
2321
{
2322
    // Sanitize
2323
    $antiXss = new AntiXSS();
2324
    $data = $antiXss->xss_clean($data);
2325
    
2326
    // Load classes
2327
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2328
    // Generate an object key
2329
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2330
    // Set it as password
2331
    $cipher->setPassword($objectKey);
2332
    return [
2333
        'encrypted' => base64_encode($cipher->encrypt($data)),
2334
        'objectKey' => base64_encode($objectKey),
2335
    ];
2336
}
2337
2338
/**
2339
 * Decrypts a string using AES.
2340
 *
2341
 * @param string $data Encrypted data
2342
 * @param string $key  Key to uncrypt
2343
 *
2344
 * @return string
2345
 */
2346
function doDataDecryption(string $data, string $key): string
2347
{
2348
    // Sanitize
2349
    $antiXss = new AntiXSS();
2350
    $data = $antiXss->xss_clean($data);
2351
    $key = $antiXss->xss_clean($key);
2352
2353
    // Load classes
2354
    $cipher = new Crypt_AES();
2355
    // Set the object key
2356
    $cipher->setPassword(base64_decode($key));
2357
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2358
}
2359
2360
/**
2361
 * Encrypts using RSA a string using a public key.
2362
 *
2363
 * @param string $key       Key to be encrypted
2364
 * @param string $publicKey User public key
2365
 *
2366
 * @return string
2367
 */
2368
function encryptUserObjectKey(string $key, string $publicKey): string
2369
{
2370
    // Sanitize
2371
    $antiXss = new AntiXSS();
2372
    $publicKey = $antiXss->xss_clean($publicKey);
2373
    // Load classes
2374
    $rsa = new Crypt_RSA();
2375
    // Load the public key
2376
    $decodedPublicKey = base64_decode($publicKey, true);
2377
    if ($decodedPublicKey === false) {
2378
        throw new InvalidArgumentException("Error while decoding key.");
2379
    }
2380
    $rsa->loadKey($decodedPublicKey);
2381
    // Encrypt
2382
    $encrypted = $rsa->encrypt(base64_decode($key));
2383
    if (empty($encrypted)) {  // Check if key is empty or null
2384
        throw new RuntimeException("Error while encrypting key.");
2385
    }
2386
    // Return
2387
    return base64_encode($encrypted);
2388
}
2389
2390
/**
2391
 * Decrypts using RSA an encrypted string using a private key.
2392
 *
2393
 * @param string $key        Encrypted key
2394
 * @param string $privateKey User private key
2395
 *
2396
 * @return string
2397
 */
2398
function decryptUserObjectKey(string $key, string $privateKey): string
2399
{
2400
    // Sanitize
2401
    $antiXss = new AntiXSS();
2402
    $privateKey = $antiXss->xss_clean($privateKey);
2403
2404
    // Load classes
2405
    $rsa = new Crypt_RSA();
2406
    // Load the private key
2407
    $decodedPrivateKey = base64_decode($privateKey, true);
2408
    if ($decodedPrivateKey === false) {
2409
        throw new InvalidArgumentException("Error while decoding private key.");
2410
    }
2411
2412
    $rsa->loadKey($decodedPrivateKey);
2413
2414
    // Decrypt
2415
    try {
2416
        $decodedKey = base64_decode($key, true);
2417
        if ($decodedKey === false) {
2418
            throw new InvalidArgumentException("Error while decoding key.");
2419
        }
2420
2421
        $tmpValue = $rsa->decrypt($decodedKey);
2422
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2423
            return base64_encode($tmpValue);
2424
        } else {
2425
            return '';
2426
        }
2427
    } catch (Exception $e) {
2428
        if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2429
            error_log('TEAMPASS Error - ldap - '.$e->getMessage());
2430
        }
2431
        return 'Exception: could not decrypt object';
2432
    }
2433
}
2434
2435
/**
2436
 * Encrypts a file.
2437
 *
2438
 * @param string $fileInName File name
2439
 * @param string $fileInPath Path to file
2440
 *
2441
 * @return array
2442
 */
2443
function encryptFile(string $fileInName, string $fileInPath): array
2444
{
2445
    if (defined('FILE_BUFFER_SIZE') === false) {
2446
        define('FILE_BUFFER_SIZE', 128 * 1024);
2447
    }
2448
2449
    // Load classes
2450
    $cipher = new Crypt_AES();
2451
2452
    // Generate an object key
2453
    $objectKey = uniqidReal(32);
2454
    // Set it as password
2455
    $cipher->setPassword($objectKey);
2456
    // Prevent against out of memory
2457
    $cipher->enableContinuousBuffer();
2458
2459
    // Encrypt the file content
2460
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2461
    $fileContent = file_get_contents($filePath);
2462
    $plaintext = $fileContent;
2463
    $ciphertext = $cipher->encrypt($plaintext);
2464
2465
    // Save new file
2466
    // deepcode ignore InsecureHash: is simply used to get a unique name
2467
    $hash = md5($plaintext);
2468
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2469
    file_put_contents($fileOut, $ciphertext);
2470
    unlink($fileInPath . '/' . $fileInName);
2471
    return [
2472
        'fileHash' => base64_encode($hash),
2473
        'objectKey' => base64_encode($objectKey),
2474
    ];
2475
}
2476
2477
/**
2478
 * Decrypt a file.
2479
 *
2480
 * @param string $fileName File name
2481
 * @param string $filePath Path to file
2482
 * @param string $key      Key to use
2483
 *
2484
 * @return string|array
2485
 */
2486
function decryptFile(string $fileName, string $filePath, string $key): string|array
2487
{
2488
    if (! defined('FILE_BUFFER_SIZE')) {
2489
        define('FILE_BUFFER_SIZE', 128 * 1024);
2490
    }
2491
    
2492
    // Load classes
2493
    $cipher = new Crypt_AES();
2494
    $antiXSS = new AntiXSS();
2495
    
2496
    // Get file name
2497
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2498
2499
    // Set the object key
2500
    $cipher->setPassword(base64_decode($key));
2501
    // Prevent against out of memory
2502
    $cipher->enableContinuousBuffer();
2503
    $cipher->disablePadding();
2504
    // Get file content
2505
    $safeFilePath = realpath($filePath . '/' . TP_FILE_PREFIX . $safeFileName);
2506
    if ($safeFilePath !== false && file_exists($safeFilePath)) {
2507
        $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2508
    } else {
2509
        // Handle the error: file doesn't exist or path is invalid
2510
        return [
2511
            'error' => true,
2512
            'message' => 'This file has not been found.',
2513
        ];
2514
    }
2515
2516
    if (WIP) error_log('DEBUG: File image url -> '.filter_var($safeFilePath, FILTER_SANITIZE_URL));
2517
2518
    // Decrypt file content and return
2519
    return base64_encode($cipher->decrypt($ciphertext));
2520
}
2521
2522
/**
2523
 * Generate a simple password
2524
 *
2525
 * @param int $length Length of string
2526
 * @param bool $symbolsincluded Allow symbols
2527
 *
2528
 * @return string
2529
 */
2530
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2531
{
2532
    // Generate new user password
2533
    $small_letters = range('a', 'z');
2534
    $big_letters = range('A', 'Z');
2535
    $digits = range(0, 9);
2536
    $symbols = $symbolsincluded === true ?
2537
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2538
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2539
    $count = count($res);
2540
    // first variant
2541
2542
    $random_string = '';
2543
    for ($i = 0; $i < $length; ++$i) {
2544
        $random_string .= $res[random_int(0, $count - 1)];
2545
    }
2546
2547
    return $random_string;
2548
}
2549
2550
/**
2551
 * Permit to store the sharekey of an object for users.
2552
 *
2553
 * @param string $object_name             Type for table selection
2554
 * @param int    $post_folder_is_personal Personal
2555
 * @param int    $post_folder_id          Folder
2556
 * @param int    $post_object_id          Object
2557
 * @param string $objectKey               Object key
2558
 * @param array  $SETTINGS                Teampass settings
2559
 * @param int    $user_id                 User ID if needed
2560
 * @param bool   $onlyForUser                 User ID if needed
2561
 * @param bool   $deleteAll                 User ID if needed
2562
 * @param array  $objectKeyArray                 User ID if needed
2563
 * @param int    $all_users_except_id                 User ID if needed
2564
 * @param int   $apiUserId                 User ID if needed
2565
 *
2566
 * @return void
2567
 */
2568
function storeUsersShareKey(
2569
    string $object_name,
2570
    int $post_folder_is_personal,
2571
    int $post_folder_id,
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
    if (
2594
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2595
    ) {
2596
        // Only create the sharekey for a user
2597
        $user = DB::queryFirstRow(
2598
            'SELECT public_key
2599
            FROM ' . prefixTable('users') . '
2600
            WHERE id = ' . ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId) . '
2601
            AND public_key != ""'
2602
        );
2603
2604
        if (empty($objectKey) === false) {
2605
            DB::insert(
2606
                $object_name,
2607
                [
2608
                    'object_id' => (int) $post_object_id,
2609
                    'user_id' => (int) ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId),
2610
                    'share_key' => encryptUserObjectKey(
2611
                        $objectKey,
2612
                        $user['public_key']
2613
                    ),
2614
                ]
2615
            );
2616
        } else if (count($objectKeyArray) > 0) {
2617
            foreach ($objectKeyArray as $object) {
2618
                DB::insert(
2619
                    $object_name,
2620
                    [
2621
                        'object_id' => (int) $object['objectId'],
2622
                        'user_id' => (int) ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId),
2623
                        'share_key' => encryptUserObjectKey(
2624
                            $object['objectKey'],
2625
                            $user['public_key']
2626
                        ),
2627
                    ]
2628
                );
2629
            }
2630
        }
2631
    } else {
2632
        // Create sharekey for each user
2633
        //DB::debugmode(true);
2634
        $users = DB::query(
2635
            'SELECT id, public_key
2636
            FROM ' . prefixTable('users') . '
2637
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2638
                'id IN ("' . TP_USER_ID . '","' . ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId) . '") ' : 
2639
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '"'.($all_users_except_id === -1 ? '' : ', "'.$all_users_except_id.'"').') ') . '
2640
            AND public_key != ""'
2641
        );
2642
        //DB::debugmode(false);
2643
        foreach ($users as $user) {
2644
            // Insert in DB the new object key for this item by user
2645
            if (count($objectKeyArray) === 0) {
2646
                if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case1 - ' . $object_name . ' - ' . $post_object_id . ' - ' . $user['id'] . ' - ' . $objectKey);
2647
                DB::insert(
2648
                    $object_name,
2649
                    [
2650
                        'object_id' => $post_object_id,
2651
                        'user_id' => (int) $user['id'],
2652
                        'share_key' => encryptUserObjectKey(
2653
                            $objectKey,
2654
                            $user['public_key']
2655
                        ),
2656
                    ]
2657
                );
2658
            } else {
2659
                foreach ($objectKeyArray as $object) {
2660
                    if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case2 - ' . $object_name . ' - ' . $object['objectId'] . ' - ' . $user['id'] . ' - ' . $object['objectKey']);
2661
                    DB::insert(
2662
                        $object_name,
2663
                        [
2664
                            'object_id' => (int) $object['objectId'],
2665
                            'user_id' => (int) $user['id'],
2666
                            'share_key' => encryptUserObjectKey(
2667
                                $object['objectKey'],
2668
                                $user['public_key']
2669
                            ),
2670
                        ]
2671
                    );
2672
                }
2673
            }
2674
        }
2675
    }
2676
}
2677
2678
/**
2679
 * Is this string base64 encoded?
2680
 *
2681
 * @param string $str Encoded string?
2682
 *
2683
 * @return bool
2684
 */
2685
function isBase64(string $str): bool
2686
{
2687
    $str = (string) trim($str);
2688
    if (! isset($str[0])) {
2689
        return false;
2690
    }
2691
2692
    $base64String = (string) base64_decode($str, true);
2693
    if ($base64String && base64_encode($base64String) === $str) {
2694
        return true;
2695
    }
2696
2697
    return false;
2698
}
2699
2700
/**
2701
 * Undocumented function
2702
 *
2703
 * @param string $field Parameter
2704
 *
2705
 * @return array|bool|resource|string
2706
 */
2707
function filterString(string $field)
2708
{
2709
    // Sanitize string
2710
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2711
    if (empty($field) === false) {
2712
        // Load AntiXSS
2713
        $antiXss = new AntiXSS();
2714
        // Return
2715
        return $antiXss->xss_clean($field);
2716
    }
2717
2718
    return false;
2719
}
2720
2721
/**
2722
 * CHeck if provided credentials are allowed on server
2723
 *
2724
 * @param string $login    User Login
2725
 * @param string $password User Pwd
2726
 * @param array  $SETTINGS Teampass settings
2727
 *
2728
 * @return bool
2729
 */
2730
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2731
{
2732
    // Build ldap configuration array
2733
    $config = [
2734
        // Mandatory Configuration Options
2735
        'hosts' => [$SETTINGS['ldap_hosts']],
2736
        'base_dn' => $SETTINGS['ldap_bdn'],
2737
        'username' => $SETTINGS['ldap_username'],
2738
        'password' => $SETTINGS['ldap_password'],
2739
2740
        // Optional Configuration Options
2741
        'port' => $SETTINGS['ldap_port'],
2742
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2743
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2744
        'version' => 3,
2745
        'timeout' => 5,
2746
        'follow_referrals' => false,
2747
2748
        // Custom LDAP Options
2749
        'options' => [
2750
            // See: http://php.net/ldap_set_option
2751
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2752
        ],
2753
    ];
2754
    
2755
    $connection = new Connection($config);
2756
    // Connect to LDAP
2757
    try {
2758
        $connection->connect();
2759
    } catch (\LdapRecord\Auth\BindException $e) {
2760
        $error = $e->getDetailedError();
2761
        if ($error && defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2762
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2763
        }
2764
        // deepcode ignore ServerLeak: No important data is sent
2765
        echo 'An error occurred.';
2766
        return false;
2767
    }
2768
2769
    // Authenticate user
2770
    try {
2771
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2772
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2773
        } else {
2774
            $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);
2775
        }
2776
    } catch (\LdapRecord\Auth\BindException $e) {
2777
        $error = $e->getDetailedError();
2778
        if ($error && defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2779
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2780
        }
2781
        // deepcode ignore ServerLeak: No important data is sent
2782
        echo 'An error occurred.';
2783
        return false;
2784
    }
2785
2786
    return true;
2787
}
2788
2789
/**
2790
 * Removes from DB all sharekeys of this user
2791
 *
2792
 * @param int $userId User's id
2793
 * @param array   $SETTINGS Teampass settings
2794
 *
2795
 * @return bool
2796
 */
2797
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2798
{
2799
    // Load class DB
2800
    loadClasses('DB');
2801
2802
    // Remove all item sharekeys items
2803
    // expect if personal item
2804
    DB::delete(
2805
        prefixTable('sharekeys_items'),
2806
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2807
        $userId
2808
    );
2809
    // Remove all item sharekeys files
2810
    DB::delete(
2811
        prefixTable('sharekeys_files'),
2812
        'user_id = %i AND object_id NOT IN (
2813
            SELECT f.id 
2814
            FROM ' . prefixTable('items') . ' AS i 
2815
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2816
            WHERE i.perso = 1
2817
        )',
2818
        $userId
2819
    );
2820
    // Remove all item sharekeys fields
2821
    DB::delete(
2822
        prefixTable('sharekeys_fields'),
2823
        'user_id = %i AND object_id NOT IN (
2824
            SELECT c.id 
2825
            FROM ' . prefixTable('items') . ' AS i 
2826
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2827
            WHERE i.perso = 1
2828
        )',
2829
        $userId
2830
    );
2831
    // Remove all item sharekeys logs
2832
    DB::delete(
2833
        prefixTable('sharekeys_logs'),
2834
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2835
        $userId
2836
    );
2837
    // Remove all item sharekeys suggestions
2838
    DB::delete(
2839
        prefixTable('sharekeys_suggestions'),
2840
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2841
        $userId
2842
    );
2843
    return false;
2844
}
2845
2846
/**
2847
 * Manage list of timezones   $SETTINGS Teampass settings
2848
 *
2849
 * @return array
2850
 */
2851
function timezone_list()
2852
{
2853
    static $timezones = null;
2854
    if ($timezones === null) {
2855
        $timezones = [];
2856
        $offsets = [];
2857
        $now = new DateTime('now', new DateTimeZone('UTC'));
2858
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2859
            $now->setTimezone(new DateTimeZone($timezone));
2860
            $offsets[] = $offset = $now->getOffset();
2861
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2862
        }
2863
2864
        array_multisort($offsets, $timezones);
2865
    }
2866
2867
    return $timezones;
2868
}
2869
2870
/**
2871
 * Provide timezone offset
2872
 *
2873
 * @param int $offset Timezone offset
2874
 *
2875
 * @return string
2876
 */
2877
function format_GMT_offset($offset): string
2878
{
2879
    $hours = intval($offset / 3600);
2880
    $minutes = abs(intval($offset % 3600 / 60));
2881
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2882
}
2883
2884
/**
2885
 * Provides timezone name
2886
 *
2887
 * @param string $name Timezone name
2888
 *
2889
 * @return string
2890
 */
2891
function format_timezone_name($name): string
2892
{
2893
    $name = str_replace('/', ', ', $name);
2894
    $name = str_replace('_', ' ', $name);
2895
2896
    return str_replace('St ', 'St. ', $name);
2897
}
2898
2899
/**
2900
 * Provides info if user should use MFA based on roles
2901
 *
2902
 * @param string $userRolesIds  User roles ids
2903
 * @param string $mfaRoles      Roles for which MFA is requested
2904
 *
2905
 * @return bool
2906
 */
2907
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
2908
{
2909
    if (empty($mfaRoles) === true) {
2910
        return true;
2911
    }
2912
2913
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2914
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2915
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2916
        return true;
2917
    }
2918
2919
    return false;
2920
}
2921
2922
/**
2923
 * Permits to clean a string for export purpose
2924
 *
2925
 * @param string $text
2926
 * @param bool $emptyCheckOnly
2927
 * 
2928
 * @return string
2929
 */
2930
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
2931
{
2932
    if (is_null($text) === true || empty($text) === true) {
2933
        return '';
2934
    }
2935
    // only expected to check if $text was empty
2936
    elseif ($emptyCheckOnly === true) {
2937
        return $text;
2938
    }
2939
2940
    return strip_tags(
2941
        cleanString(
2942
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
2943
            true)
2944
        );
2945
}
2946
2947
/**
2948
 * Permits to check if user ID is valid
2949
 *
2950
 * @param integer $post_user_id
2951
 * @return bool
2952
 */
2953
function isUserIdValid($userId): bool
2954
{
2955
    if (is_null($userId) === false
2956
        && isset($userId) === true
2957
        && empty($userId) === false
2958
    ) {
2959
        return true;
2960
    }
2961
    return false;
2962
}
2963
2964
/**
2965
 * Check if a key exists and if its value equal the one expected
2966
 *
2967
 * @param string $key
2968
 * @param integer|string $value
2969
 * @param array $array
2970
 * 
2971
 * @return boolean
2972
 */
2973
function isKeyExistingAndEqual(
2974
    string $key,
2975
    /*PHP8 - integer|string*/$value,
2976
    array $array
2977
): bool
2978
{
2979
    if (isset($array[$key]) === true
2980
        && (is_int($value) === true ?
2981
            (int) $array[$key] === $value :
2982
            (string) $array[$key] === $value)
2983
    ) {
2984
        return true;
2985
    }
2986
    return false;
2987
}
2988
2989
/**
2990
 * Check if a variable is not set or equal to a value
2991
 *
2992
 * @param string|null $var
2993
 * @param integer|string $value
2994
 * 
2995
 * @return boolean
2996
 */
2997
function isKeyNotSetOrEqual(
2998
    /*PHP8 - string|null*/$var,
2999
    /*PHP8 - integer|string*/$value
3000
): bool
3001
{
3002
    if (isset($var) === false
3003
        || (is_int($value) === true ?
3004
            (int) $var === $value :
3005
            (string) $var === $value)
3006
    ) {
3007
        return true;
3008
    }
3009
    return false;
3010
}
3011
3012
/**
3013
 * Check if a key exists and if its value < to the one expected
3014
 *
3015
 * @param string $key
3016
 * @param integer $value
3017
 * @param array $array
3018
 * 
3019
 * @return boolean
3020
 */
3021
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3022
{
3023
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3024
        return true;
3025
    }
3026
    return false;
3027
}
3028
3029
/**
3030
 * Check if a key exists and if its value > to the one expected
3031
 *
3032
 * @param string $key
3033
 * @param integer $value
3034
 * @param array $array
3035
 * 
3036
 * @return boolean
3037
 */
3038
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3039
{
3040
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3041
        return true;
3042
    }
3043
    return false;
3044
}
3045
3046
/**
3047
 * Check if values in array are set
3048
 * Return true if all set
3049
 * Return false if one of them is not set
3050
 *
3051
 * @param array $arrayOfValues
3052
 * @return boolean
3053
 */
3054
function isSetArrayOfValues(array $arrayOfValues): bool
3055
{
3056
    foreach($arrayOfValues as $value) {
3057
        if (isset($value) === false) {
3058
            return false;
3059
        }
3060
    }
3061
    return true;
3062
}
3063
3064
/**
3065
 * Check if values in array are set
3066
 * Return true if all set
3067
 * Return false if one of them is not set
3068
 *
3069
 * @param array $arrayOfValues
3070
 * @param integer|string $value
3071
 * @return boolean
3072
 */
3073
function isArrayOfVarsEqualToValue(
3074
    array $arrayOfVars,
3075
    /*PHP8 - integer|string*/$value
3076
) : bool
3077
{
3078
    foreach($arrayOfVars as $variable) {
3079
        if ($variable !== $value) {
3080
            return false;
3081
        }
3082
    }
3083
    return true;
3084
}
3085
3086
/**
3087
 * Checks if at least one variable in array is equal to value
3088
 *
3089
 * @param array $arrayOfValues
3090
 * @param integer|string $value
3091
 * @return boolean
3092
 */
3093
function isOneVarOfArrayEqualToValue(
3094
    array $arrayOfVars,
3095
    /*PHP8 - integer|string*/$value
3096
) : bool
3097
{
3098
    foreach($arrayOfVars as $variable) {
3099
        if ($variable === $value) {
3100
            return true;
3101
        }
3102
    }
3103
    return false;
3104
}
3105
3106
/**
3107
 * Checks is value is null, not set OR empty
3108
 *
3109
 * @param string|int|null $value
3110
 * @return boolean
3111
 */
3112
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3113
{
3114
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3115
        return true;
3116
    }
3117
    return false;
3118
}
3119
3120
/**
3121
 * Checks if value is set and if empty is equal to passed boolean
3122
 *
3123
 * @param string|int $value
3124
 * @param boolean $boolean
3125
 * @return boolean
3126
 */
3127
function isValueSetEmpty($value, $boolean = true) : bool
3128
{
3129
    if (isset($value) === true && empty($value) === $boolean) {
3130
        return true;
3131
    }
3132
    return false;
3133
}
3134
3135
/**
3136
 * Ensure Complexity is translated
3137
 *
3138
 * @return void
3139
 */
3140
function defineComplexity() : void
3141
{
3142
    // Load user's language
3143
    $session = SessionManager::getSession();
3144
    $lang = new Language($session->get('user-language') ?? 'english');
3145
    
3146
    if (defined('TP_PW_COMPLEXITY') === false) {
3147
        define(
3148
            'TP_PW_COMPLEXITY',
3149
            [
3150
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3151
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3152
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3153
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3154
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3155
            ]
3156
        );
3157
    }
3158
}
3159
3160
/**
3161
 * Uses Sanitizer to perform data sanitization
3162
 *
3163
 * @param array     $data
3164
 * @param array     $filters
3165
 * @return array|string
3166
 */
3167
function dataSanitizer(array $data, array $filters): array|string
3168
{
3169
    // Load Sanitizer library
3170
    $sanitizer = new Sanitizer($data, $filters);
3171
3172
    // Load AntiXSS
3173
    $antiXss = new AntiXSS();
3174
3175
    // Sanitize post and get variables
3176
    return $antiXss->xss_clean($sanitizer->sanitize());
3177
}
3178
3179
/**
3180
 * Permits to manage the cache tree for a user
3181
 *
3182
 * @param integer $user_id
3183
 * @param string $data
3184
 * @param array $SETTINGS
3185
 * @param string $field_update
3186
 * @return void
3187
 */
3188
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3189
{
3190
    // Load class DB
3191
    loadClasses('DB');
3192
3193
    // Exists ?
3194
    $userCacheId = DB::queryfirstrow(
3195
        'SELECT increment_id
3196
        FROM ' . prefixTable('cache_tree') . '
3197
        WHERE user_id = %i',
3198
        $user_id
3199
    );
3200
    
3201
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3202
        // insert in table
3203
        DB::insert(
3204
            prefixTable('cache_tree'),
3205
            array(
3206
                'data' => $data,
3207
                'timestamp' => time(),
3208
                'user_id' => $user_id,
3209
                'visible_folders' => '',
3210
            )
3211
        );
3212
    } else {
3213
        if (empty($field_update) === true) {
3214
            DB::update(
3215
                prefixTable('cache_tree'),
3216
                [
3217
                    'timestamp' => time(),
3218
                    'data' => $data,
3219
                ],
3220
                'increment_id = %i',
3221
                $userCacheId['increment_id']
3222
            );
3223
        /* USELESS
3224
        } else {
3225
            DB::update(
3226
                prefixTable('cache_tree'),
3227
                [
3228
                    $field_update => $data,
3229
                ],
3230
                'increment_id = %i',
3231
                $userCacheId['increment_id']
3232
            );*/
3233
        }
3234
    }
3235
}
3236
3237
/**
3238
 * Permits to calculate a %
3239
 *
3240
 * @param float $nombre
3241
 * @param float $total
3242
 * @param float $pourcentage
3243
 * @return float
3244
 */
3245
function pourcentage(float $nombre, float $total, float $pourcentage): float
3246
{ 
3247
    $resultat = ($nombre/$total) * $pourcentage;
3248
    return round($resultat);
3249
}
3250
3251
/**
3252
 * Load the folders list from the cache
3253
 *
3254
 * @param string $fieldName
3255
 * @param string $sessionName
3256
 * @param boolean $forceRefresh
3257
 * @return array
3258
 */
3259
function loadFoldersListByCache(
3260
    string $fieldName,
3261
    string $sessionName,
3262
    bool $forceRefresh = false
3263
): array
3264
{
3265
    // Case when refresh is EXPECTED / MANDATORY
3266
    if ($forceRefresh === true) {
3267
        return [
3268
            'state' => false,
3269
            'data' => [],
3270
        ];
3271
    }
3272
    
3273
    $session = SessionManager::getSession();
3274
3275
    // Get last folder update
3276
    $lastFolderChange = DB::queryfirstrow(
3277
        'SELECT valeur FROM ' . prefixTable('misc') . '
3278
        WHERE type = %s AND intitule = %s',
3279
        'timestamp',
3280
        'last_folder_change'
3281
    );
3282
    if (DB::count() === 0) {
3283
        $lastFolderChange['valeur'] = 0;
3284
    }
3285
3286
    // Case when an update in the tree has been done
3287
    // Refresh is then mandatory
3288
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3289
        return [
3290
            'state' => false,
3291
            'data' => [],
3292
        ];
3293
    }
3294
3295
    // Does this user has the tree structure in session?
3296
    // If yes then use it
3297
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3298
        return [
3299
            'state' => true,
3300
            'data' => json_encode($session->get('user-folders_list')[0]),
3301
            'extra' => 'to_be_parsed',
3302
        ];
3303
    }
3304
    
3305
    // Does this user has a tree cache
3306
    $userCacheTree = DB::queryfirstrow(
3307
        'SELECT '.$fieldName.'
3308
        FROM ' . prefixTable('cache_tree') . '
3309
        WHERE user_id = %i',
3310
        $session->get('user-id')
3311
    );
3312
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3313
        SessionManager::addRemoveFromSessionAssociativeArray(
3314
            'user-folders_list',
3315
            [$userCacheTree[$fieldName]],
3316
            'add'
3317
        );
3318
        return [
3319
            'state' => true,
3320
            'data' => $userCacheTree[$fieldName],
3321
            'extra' => '',
3322
        ];
3323
    }
3324
3325
    return [
3326
        'state' => false,
3327
        'data' => [],
3328
    ];
3329
}
3330
3331
3332
/**
3333
 * Permits to refresh the categories of folders
3334
 *
3335
 * @param array $folderIds
3336
 * @return void
3337
 */
3338
function handleFoldersCategories(
3339
    array $folderIds
3340
)
3341
{
3342
    // Load class DB
3343
    loadClasses('DB');
3344
3345
    $arr_data = array();
3346
3347
    // force full list of folders
3348
    if (count($folderIds) === 0) {
3349
        $folderIds = DB::queryFirstColumn(
3350
            'SELECT id
3351
            FROM ' . prefixTable('nested_tree') . '
3352
            WHERE personal_folder=%i',
3353
            0
3354
        );
3355
    }
3356
3357
    // Get complexity
3358
    defineComplexity();
3359
3360
    // update
3361
    foreach ($folderIds as $folder) {
3362
        // Do we have Categories
3363
        // get list of associated Categories
3364
        $arrCatList = array();
3365
        $rows_tmp = DB::query(
3366
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3367
            f.id_category AS category_id
3368
            FROM ' . prefixTable('categories_folders') . ' AS f
3369
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3370
            WHERE id_folder=%i',
3371
            $folder
3372
        );
3373
        if (DB::count() > 0) {
3374
            foreach ($rows_tmp as $row) {
3375
                $arrCatList[$row['id']] = array(
3376
                    'id' => $row['id'],
3377
                    'title' => $row['title'],
3378
                    'level' => $row['level'],
3379
                    'type' => $row['type'],
3380
                    'masked' => $row['masked'],
3381
                    'order' => $row['order'],
3382
                    'encrypted_data' => $row['encrypted_data'],
3383
                    'role_visibility' => $row['role_visibility'],
3384
                    'is_mandatory' => $row['is_mandatory'],
3385
                    'category_id' => $row['category_id'],
3386
                );
3387
            }
3388
        }
3389
        $arr_data['categories'] = $arrCatList;
3390
3391
        // Now get complexity
3392
        $valTemp = '';
3393
        $data = DB::queryFirstRow(
3394
            'SELECT valeur
3395
            FROM ' . prefixTable('misc') . '
3396
            WHERE type = %s AND intitule=%i',
3397
            'complex',
3398
            $folder
3399
        );
3400
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3401
            $valTemp = array(
3402
                'value' => $data['valeur'],
3403
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3404
            );
3405
        }
3406
        $arr_data['complexity'] = $valTemp;
3407
3408
        // Now get Roles
3409
        $valTemp = '';
3410
        $rows_tmp = DB::query(
3411
            'SELECT t.title
3412
            FROM ' . prefixTable('roles_values') . ' as v
3413
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3414
            WHERE v.folder_id = %i
3415
            GROUP BY title',
3416
            $folder
3417
        );
3418
        foreach ($rows_tmp as $record) {
3419
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3420
        }
3421
        $arr_data['visibilityRoles'] = $valTemp;
3422
3423
        // now save in DB
3424
        DB::update(
3425
            prefixTable('nested_tree'),
3426
            array(
3427
                'categories' => json_encode($arr_data),
3428
            ),
3429
            'id = %i',
3430
            $folder
3431
        );
3432
    }
3433
}
3434
3435
/**
3436
 * List all users that have specific roles
3437
 *
3438
 * @param array $roles
3439
 * @return array
3440
 */
3441
function getUsersWithRoles(
3442
    array $roles
3443
): array
3444
{
3445
    $session = SessionManager::getSession();
3446
    $arrUsers = array();
3447
3448
    foreach ($roles as $role) {
3449
        // loop on users and check if user has this role
3450
        $rows = DB::query(
3451
            'SELECT id, fonction_id
3452
            FROM ' . prefixTable('users') . '
3453
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3454
            $session->get('user-id')
3455
        );
3456
        foreach ($rows as $user) {
3457
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3458
            if (in_array($role, $userRoles, true) === true) {
3459
                array_push($arrUsers, $user['id']);
3460
            }
3461
        }
3462
    }
3463
3464
    return $arrUsers;
3465
}
3466
3467
3468
/**
3469
 * Get all users informations
3470
 *
3471
 * @param integer $userId
3472
 * @return array
3473
 */
3474
function getFullUserInfos(
3475
    int $userId
3476
): array
3477
{
3478
    if (empty($userId) === true) {
3479
        return array();
3480
    }
3481
3482
    $val = DB::queryfirstrow(
3483
        'SELECT *
3484
        FROM ' . prefixTable('users') . '
3485
        WHERE id = %i',
3486
        $userId
3487
    );
3488
3489
    return $val;
3490
}
3491
3492
/**
3493
 * Is required an upgrade
3494
 *
3495
 * @return boolean
3496
 */
3497
function upgradeRequired(): bool
3498
{
3499
    // Get settings.php
3500
    include_once __DIR__. '/../includes/config/settings.php';
3501
3502
    // Get timestamp in DB
3503
    $val = DB::queryfirstrow(
3504
        'SELECT valeur
3505
        FROM ' . prefixTable('misc') . '
3506
        WHERE type = %s AND intitule = %s',
3507
        'admin',
3508
        'upgrade_timestamp'
3509
    );
3510
3511
    // Check if upgrade is required
3512
    return (
3513
        is_null($val) || count($val) === 0 || !defined('UPGRADE_MIN_DATE') || 
3514
        empty($val['valeur']) || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE
3515
    );
3516
}
3517
3518
/**
3519
 * Permits to change the user keys on his demand
3520
 *
3521
 * @param integer $userId
3522
 * @param string $passwordClear
3523
 * @param integer $nbItemsToTreat
3524
 * @param string $encryptionKey
3525
 * @param boolean $deleteExistingKeys
3526
 * @param boolean $sendEmailToUser
3527
 * @param boolean $encryptWithUserPassword
3528
 * @param boolean $generate_user_new_password
3529
 * @param string $emailBody
3530
 * @param boolean $user_self_change
3531
 * @param string $recovery_public_key
3532
 * @param string $recovery_private_key
3533
 * @return string
3534
 */
3535
function handleUserKeys(
3536
    int $userId,
3537
    string $passwordClear,
3538
    int $nbItemsToTreat,
3539
    string $encryptionKey = '',
3540
    bool $deleteExistingKeys = false,
3541
    bool $sendEmailToUser = true,
3542
    bool $encryptWithUserPassword = false,
3543
    bool $generate_user_new_password = false,
3544
    string $emailBody = '',
3545
    bool $user_self_change = false,
3546
    string $recovery_public_key = '',
3547
    string $recovery_private_key = ''
3548
): string
3549
{
3550
    $session = SessionManager::getSession();
3551
    $lang = new Language($session->get('user-language') ?? 'english');
3552
3553
    // prepapre background tasks for item keys generation        
3554
    $userTP = DB::queryFirstRow(
3555
        'SELECT pw, public_key, private_key
3556
        FROM ' . prefixTable('users') . '
3557
        WHERE id = %i',
3558
        TP_USER_ID
3559
    );
3560
    if (DB::count() === 0) {
3561
        return prepareExchangedData(
3562
            array(
3563
                'error' => true,
3564
                'message' => 'User not exists',
3565
            ),
3566
            'encode'
3567
        );
3568
    }
3569
3570
    // Do we need to generate new user password
3571
    if ($generate_user_new_password === true) {
3572
        // Generate a new password
3573
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3574
    }
3575
3576
    // Create password hash
3577
    $passwordManager = new PasswordManager();
3578
    $hashedPassword = $passwordManager->hashPassword($passwordClear);
3579
    if ($passwordManager->verifyPassword($hashedPassword, $passwordClear) === false) {
3580
        return prepareExchangedData(
3581
            array(
3582
                'error' => true,
3583
                'message' => $lang->get('pw_hash_not_correct'),
3584
            ),
3585
            'encode'
3586
        );
3587
    }
3588
3589
    // Check if valid public/private keys
3590
    if ($recovery_public_key !== '' && $recovery_private_key !== '') {
3591
        try {
3592
            // Generate random string
3593
            $random_str = generateQuickPassword(12, false);
3594
            // Encrypt random string with user publick key
3595
            $encrypted = encryptUserObjectKey($random_str, $recovery_public_key);
3596
            // Decrypt $encrypted with private key
3597
            $decrypted = decryptUserObjectKey($encrypted, $recovery_private_key);
3598
            // Check if decryptUserObjectKey returns our random string
3599
            if ($decrypted !== $random_str) {
3600
                throw new Exception('Public/Private keypair invalid.');
3601
            }
3602
        } catch (Exception $e) {
3603
            // Show error message to user and log event
3604
            if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
3605
                error_log('ERROR: User '.$userId.' - '.$e->getMessage());
3606
            }
3607
            return prepareExchangedData([
3608
                    'error' => true,
3609
                    'message' => $lang->get('pw_encryption_error'),
3610
                ],
3611
                'encode'
3612
            );
3613
        }
3614
    }
3615
3616
    // Generate new keys
3617
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3618
        $userKeys = [
3619
            'public_key' => $recovery_public_key,
3620
            'private_key_clear' => $recovery_private_key,
3621
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3622
        ];
3623
    } else {
3624
        $userKeys = generateUserKeys($passwordClear);
3625
    }
3626
3627
    // Save in DB
3628
    DB::update(
3629
        prefixTable('users'),
3630
        array(
3631
            'pw' => $hashedPassword,
3632
            'public_key' => $userKeys['public_key'],
3633
            'private_key' => $userKeys['private_key'],
3634
        ),
3635
        'id=%i',
3636
        $userId
3637
    );
3638
3639
    // update session too
3640
    if ($userId === $session->get('user-id')) {
3641
        $session->set('user-private_key', $userKeys['private_key_clear']);
3642
        $session->set('user-public_key', $userKeys['public_key']);
3643
    }
3644
3645
    // Manage empty encryption key
3646
    // Let's take the user's password if asked and if no encryption key provided
3647
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3648
3649
    // Create process
3650
    DB::insert(
3651
        prefixTable('background_tasks'),
3652
        array(
3653
            'created_at' => time(),
3654
            'process_type' => 'create_user_keys',
3655
            'arguments' => json_encode([
3656
                'new_user_id' => (int) $userId,
3657
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3658
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3659
                'owner_id' => (int) TP_USER_ID,
3660
                'creator_pwd' => $userTP['pw'],
3661
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3662
                'otp_provided_new_value' => 1,
3663
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3664
                'user_self_change' => $user_self_change === true ? 1 : 0,
3665
            ]),
3666
        )
3667
    );
3668
    $processId = DB::insertId();
3669
3670
    // Delete existing keys
3671
    if ($deleteExistingKeys === true) {
3672
        deleteUserObjetsKeys(
3673
            (int) $userId,
3674
        );
3675
    }
3676
3677
    // Create tasks
3678
    createUserTasks($processId, $nbItemsToTreat);
3679
3680
    // update user's new status
3681
    DB::update(
3682
        prefixTable('users'),
3683
        [
3684
            'is_ready_for_usage' => 0,
3685
            'otp_provided' => 1,
3686
            'ongoing_process_id' => $processId,
3687
            'special' => 'generate-keys',
3688
        ],
3689
        'id=%i',
3690
        $userId
3691
    );
3692
3693
    return prepareExchangedData(
3694
        array(
3695
            'error' => false,
3696
            'message' => '',
3697
            'user_password' => $generate_user_new_password === true ? $passwordClear : '',
3698
        ),
3699
        'encode'
3700
    );
3701
}
3702
3703
/**
3704
 * Permits to generate a new password for a user
3705
 *
3706
 * @param integer $processId
3707
 * @param integer $nbItemsToTreat
3708
 * @return void
3709
 
3710
 */
3711
function createUserTasks($processId, $nbItemsToTreat): void
3712
{
3713
    DB::insert(
3714
        prefixTable('background_subtasks'),
3715
        array(
3716
            'task_id' => $processId,
3717
            'created_at' => time(),
3718
            'task' => json_encode([
3719
                'step' => 'step0',
3720
                'index' => 0,
3721
                'nb' => $nbItemsToTreat,
3722
            ]),
3723
        )
3724
    );
3725
3726
    DB::insert(
3727
        prefixTable('background_subtasks'),
3728
        array(
3729
            'task_id' => $processId,
3730
            'created_at' => time(),
3731
            'task' => json_encode([
3732
                'step' => 'step10',
3733
                'index' => 0,
3734
                'nb' => $nbItemsToTreat,
3735
            ]),
3736
        )
3737
    );
3738
3739
    DB::insert(
3740
        prefixTable('background_subtasks'),
3741
        array(
3742
            'task_id' => $processId,
3743
            'created_at' => time(),
3744
            'task' => json_encode([
3745
                'step' => 'step20',
3746
                'index' => 0,
3747
                'nb' => $nbItemsToTreat,
3748
            ]),
3749
        )
3750
    );
3751
3752
    DB::insert(
3753
        prefixTable('background_subtasks'),
3754
        array(
3755
            'task_id' => $processId,
3756
            'created_at' => time(),
3757
            'task' => json_encode([
3758
                'step' => 'step30',
3759
                'index' => 0,
3760
                'nb' => $nbItemsToTreat,
3761
            ]),
3762
        )
3763
    );
3764
3765
    DB::insert(
3766
        prefixTable('background_subtasks'),
3767
        array(
3768
            'task_id' => $processId,
3769
            'created_at' => time(),
3770
            'task' => json_encode([
3771
                'step' => 'step40',
3772
                'index' => 0,
3773
                'nb' => $nbItemsToTreat,
3774
            ]),
3775
        )
3776
    );
3777
3778
    DB::insert(
3779
        prefixTable('background_subtasks'),
3780
        array(
3781
            'task_id' => $processId,
3782
            'created_at' => time(),
3783
            'task' => json_encode([
3784
                'step' => 'step50',
3785
                'index' => 0,
3786
                'nb' => $nbItemsToTreat,
3787
            ]),
3788
        )
3789
    );
3790
3791
    DB::insert(
3792
        prefixTable('background_subtasks'),
3793
        array(
3794
            'task_id' => $processId,
3795
            'created_at' => time(),
3796
            'task' => json_encode([
3797
                'step' => 'step60',
3798
                'index' => 0,
3799
                'nb' => $nbItemsToTreat,
3800
            ]),
3801
        )
3802
    );
3803
}
3804
3805
/**
3806
 * Permeits to check the consistency of date versus columns definition
3807
 *
3808
 * @param string $table
3809
 * @param array $dataFields
3810
 * @return array
3811
 */
3812
function validateDataFields(
3813
    string $table,
3814
    array $dataFields
3815
): array
3816
{
3817
    // Get table structure
3818
    $result = DB::query(
3819
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3820
        DB_NAME,
3821
        $table
3822
    );
3823
3824
    foreach ($result as $row) {
3825
        $field = $row['COLUMN_NAME'];
3826
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3827
3828
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3829
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3830
                return [
3831
                    'state' => false,
3832
                    'field' => $field,
3833
                    'maxLength' => $maxLength,
3834
                    'currentLength' => strlen((string) $dataFields[$field]),
3835
                ];
3836
            }
3837
        }
3838
    }
3839
    
3840
    return [
3841
        'state' => true,
3842
        'message' => '',
3843
    ];
3844
}
3845
3846
/**
3847
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3848
 *
3849
 * @param string $string
3850
 * @return string
3851
 */
3852
function filterVarBack(string $string): string
3853
{
3854
    $arr = [
3855
        '&#060;' => '<',
3856
        '&#062;' => '>',
3857
        '&#034;' => '"',
3858
        '&#039;' => "'",
3859
        '&#038;' => '&',
3860
    ];
3861
3862
    foreach ($arr as $key => $value) {
3863
        $string = str_replace($key, $value, $string);
3864
    }
3865
3866
    return $string;
3867
}
3868
3869
/**
3870
 * 
3871
 */
3872
function storeTask(
3873
    string $taskName,
3874
    int $user_id,
3875
    int $is_personal_folder,
3876
    int $folder_destination_id,
3877
    int $item_id,
3878
    string $object_keys,
3879
    array $fields_keys = [],
3880
    array $files_keys = []
3881
)
3882
{
3883
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3884
        // Create process
3885
        DB::insert(
3886
            prefixTable('background_tasks'),
3887
            array(
3888
                'created_at' => time(),
3889
                'process_type' => $taskName,
3890
                'arguments' => json_encode([
3891
                    'item_id' => $item_id,
3892
                    'object_key' => $object_keys,
3893
                ]),
3894
                'item_id' => $item_id,
3895
            )
3896
        );
3897
        $processId = DB::insertId();
3898
3899
        // Create tasks
3900
        // 1- Create password sharekeys for users of this new ITEM
3901
        DB::insert(
3902
            prefixTable('background_subtasks'),
3903
            array(
3904
                'task_id' => $processId,
3905
                'created_at' => time(),
3906
                'task' => json_encode([
3907
                    'step' => 'create_users_pwd_key',
3908
                    'index' => 0,
3909
                ]),
3910
            )
3911
        );
3912
3913
        // 2- Create fields sharekeys for users of this new ITEM
3914
        DB::insert(
3915
            prefixTable('background_subtasks'),
3916
            array(
3917
                'task_id' => $processId,
3918
                'created_at' => time(),
3919
                'task' => json_encode([
3920
                    'step' => 'create_users_fields_key',
3921
                    'index' => 0,
3922
                    'fields_keys' => $fields_keys,
3923
                ]),
3924
            )
3925
        );
3926
3927
        // 3- Create files sharekeys for users of this new ITEM
3928
        DB::insert(
3929
            prefixTable('background_subtasks'),
3930
            array(
3931
                'task_id' => $processId,
3932
                'created_at' => time(),
3933
                'task' => json_encode([
3934
                    'step' => 'create_users_files_key',
3935
                    'index' => 0,
3936
                    'files_keys' => $files_keys,
3937
                ]),
3938
            )
3939
        );
3940
    }
3941
}
3942
3943
/**
3944
 * 
3945
 */
3946
function createTaskForItem(
3947
    string $processType,
3948
    string|array $taskName,
3949
    int $itemId,
3950
    int $userId,
3951
    string $objectKey,
3952
    int $parentId = -1,
3953
    array $fields_keys = [],
3954
    array $files_keys = []
3955
)
3956
{
3957
    // 1- Create main process
3958
    // ---
3959
    
3960
    // Create process
3961
    DB::insert(
3962
        prefixTable('background_tasks'),
3963
        array(
3964
            'created_at' => time(),
3965
            'process_type' => $processType,
3966
            'arguments' => json_encode([
3967
                'all_users_except_id' => (int) $userId,
3968
                'item_id' => (int) $itemId,
3969
                'object_key' => $objectKey,
3970
                'author' => (int) $userId,
3971
            ]),
3972
            'item_id' => (int) $parentId !== -1 ?  $parentId : null,
3973
        )
3974
    );
3975
    $processId = DB::insertId();
3976
3977
    // 2- Create expected tasks
3978
    // ---
3979
    if (is_array($taskName) === false) {
0 ignored issues
show
introduced by
The condition is_array($taskName) === false is always false.
Loading history...
3980
        $taskName = [$taskName];
3981
    }
3982
    foreach($taskName as $task) {
3983
        if (WIP === true) error_log('createTaskForItem - task: '.$task);
3984
        switch ($task) {
3985
            case 'item_password':
3986
                
3987
                DB::insert(
3988
                    prefixTable('background_subtasks'),
3989
                    array(
3990
                        'task_id' => $processId,
3991
                        'created_at' => time(),
3992
                        'task' => json_encode([
3993
                            'step' => 'create_users_pwd_key',
3994
                            'index' => 0,
3995
                        ]),
3996
                    )
3997
                );
3998
3999
                break;
4000
            case 'item_field':
4001
                
4002
                DB::insert(
4003
                    prefixTable('background_subtasks'),
4004
                    array(
4005
                        'task_id' => $processId,
4006
                        'created_at' => time(),
4007
                        'task' => json_encode([
4008
                            'step' => 'create_users_fields_key',
4009
                            'index' => 0,
4010
                            'fields_keys' => $fields_keys,
4011
                        ]),
4012
                    )
4013
                );
4014
4015
                break;
4016
            case 'item_file':
4017
4018
                DB::insert(
4019
                    prefixTable('background_subtasks'),
4020
                    array(
4021
                        'task_id' => $processId,
4022
                        'created_at' => time(),
4023
                        'task' => json_encode([
4024
                            'step' => 'create_users_files_key',
4025
                            'index' => 0,
4026
                            'fields_keys' => $files_keys,
4027
                        ]),
4028
                    )
4029
                );
4030
                break;
4031
            default:
4032
                # code...
4033
                break;
4034
        }
4035
    }
4036
}
4037
4038
4039
function deleteProcessAndRelatedTasks(int $processId)
4040
{
4041
    // Delete process
4042
    DB::delete(
4043
        prefixTable('background_tasks'),
4044
        'id=%i',
4045
        $processId
4046
    );
4047
4048
    // Delete tasks
4049
    DB::delete(
4050
        prefixTable('background_subtasks'),
4051
        'task_id=%i',
4052
        $processId
4053
    );
4054
4055
}
4056
4057
/**
4058
 * Return PHP binary path
4059
 *
4060
 * @return string
4061
 */
4062
function getPHPBinary(): string
4063
{
4064
    // Get PHP binary path
4065
    $phpBinaryFinder = new PhpExecutableFinder();
4066
    $phpBinaryPath = $phpBinaryFinder->find();
4067
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4068
}
4069
4070
4071
4072
/**
4073
 * Delete unnecessary keys for personal items
4074
 *
4075
 * @param boolean $allUsers
4076
 * @param integer $user_id
4077
 * @return void
4078
 */
4079
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4080
{
4081
    if ($allUsers === true) {
4082
        // Load class DB
4083
        if (class_exists('DB') === false) {
4084
            loadClasses('DB');
4085
        }
4086
4087
        $users = DB::query(
4088
            'SELECT id
4089
            FROM ' . prefixTable('users') . '
4090
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4091
            ORDER BY login ASC'
4092
        );
4093
        foreach ($users as $user) {
4094
            purgeUnnecessaryKeysForUser((int) $user['id']);
4095
        }
4096
    } else {
4097
        purgeUnnecessaryKeysForUser((int) $user_id);
4098
    }
4099
}
4100
4101
/**
4102
 * Delete unnecessary keys for personal items
4103
 *
4104
 * @param integer $user_id
4105
 * @return void
4106
 */
4107
function purgeUnnecessaryKeysForUser(int $user_id=0)
4108
{
4109
    if ($user_id === 0) {
4110
        return;
4111
    }
4112
4113
    // Load class DB
4114
    loadClasses('DB');
4115
4116
    $personalItems = DB::queryFirstColumn(
4117
        'SELECT id
4118
        FROM ' . prefixTable('items') . ' AS i
4119
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4120
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4121
        $user_id
4122
    );
4123
    if (count($personalItems) > 0) {
4124
        // Item keys
4125
        DB::delete(
4126
            prefixTable('sharekeys_items'),
4127
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4128
            $personalItems,
4129
            $user_id
4130
        );
4131
        // Files keys
4132
        DB::delete(
4133
            prefixTable('sharekeys_files'),
4134
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4135
            $personalItems,
4136
            $user_id
4137
        );
4138
        // Fields keys
4139
        DB::delete(
4140
            prefixTable('sharekeys_fields'),
4141
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4142
            $personalItems,
4143
            $user_id
4144
        );
4145
        // Logs keys
4146
        DB::delete(
4147
            prefixTable('sharekeys_logs'),
4148
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4149
            $personalItems,
4150
            $user_id
4151
        );
4152
    }
4153
}
4154
4155
/**
4156
 * Generate recovery keys file
4157
 *
4158
 * @param integer $userId
4159
 * @param array $SETTINGS
4160
 * @return string
4161
 */
4162
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4163
{
4164
    $session = SessionManager::getSession();
4165
    // Check if user exists
4166
    $userInfo = DB::queryFirstRow(
4167
        'SELECT pw, public_key, private_key, login, name
4168
        FROM ' . prefixTable('users') . '
4169
        WHERE id = %i',
4170
        $userId
4171
    );
4172
4173
    if (DB::count() > 0) {
4174
        $now = (int) time();
4175
        // Prepare file content
4176
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4177
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4178
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4179
            "Public Key:\n".$userInfo['public_key']."\n\n".
4180
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4181
4182
        // Update user's keys_recovery_time
4183
        DB::update(
4184
            prefixTable('users'),
4185
            [
4186
                'keys_recovery_time' => $now,
4187
            ],
4188
            'id=%i',
4189
            $userId
4190
        );
4191
        $session->set('user-keys_recovery_time', $now);
4192
4193
        //Log into DB the user's disconnection
4194
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4195
        
4196
        // Return data
4197
        return prepareExchangedData(
4198
            array(
4199
                'error' => false,
4200
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4201
                'timestamp' => $now,
4202
                'content' => base64_encode($export_value),
4203
                'login' => $userInfo['login'],
4204
            ),
4205
            'encode'
4206
        );
4207
    }
4208
4209
    return prepareExchangedData(
4210
        array(
4211
            'error' => true,
4212
            'datetime' => '',
4213
        ),
4214
        'encode'
4215
    );
4216
}
4217
4218
/**
4219
 * Permits to load expected classes
4220
 *
4221
 * @param string $className
4222
 * @return void
4223
 */
4224
function loadClasses(string $className = ''): void
4225
{
4226
    require_once __DIR__. '/../includes/config/include.php';
4227
    require_once __DIR__. '/../includes/config/settings.php';
4228
    require_once __DIR__.'/../vendor/autoload.php';
4229
4230
    if (defined('DB_PASSWD_CLEAR') === false) {
4231
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4232
    }
4233
4234
    if (empty($className) === false) {
4235
        // Load class DB
4236
        if ((string) $className === 'DB') {
4237
            //Connect to DB
4238
            DB::$host = DB_HOST;
4239
            DB::$user = DB_USER;
4240
            DB::$password = DB_PASSWD_CLEAR;
4241
            DB::$dbName = DB_NAME;
4242
            DB::$port = DB_PORT;
4243
            DB::$encoding = DB_ENCODING;
4244
            DB::$ssl = DB_SSL;
4245
            DB::$connect_options = DB_CONNECT_OPTIONS;
4246
        }
4247
    }
4248
}
4249
4250
/**
4251
 * Returns the page the user is visiting.
4252
 *
4253
 * @return string The page name
4254
 */
4255
function getCurrectPage($SETTINGS)
4256
{
4257
    
4258
    $request = SymfonyRequest::createFromGlobals();
4259
4260
    // Parse the url
4261
    parse_str(
4262
        substr(
4263
            (string) $request->getRequestUri(),
4264
            strpos((string) $request->getRequestUri(), '?') + 1
4265
        ),
4266
        $result
4267
    );
4268
4269
    return $result['page'];
4270
}
4271
4272
/**
4273
 * Permits to return value if set
4274
 *
4275
 * @param string|int $value
4276
 * @param string|int|null $retFalse
4277
 * @param string|int $retTrue
4278
 * @return mixed
4279
 */
4280
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4281
{
4282
4283
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4284
}
4285
4286
4287
/**
4288
 * SEnd email to user
4289
 *
4290
 * @param string $post_receipt
4291
 * @param string $post_body
4292
 * @param string $post_subject
4293
 * @param array $post_replace
4294
 * @param boolean $immediate_email
4295
 * @param string $encryptedUserPassword
4296
 * @return string
4297
 */
4298
function sendMailToUser(
4299
    string $post_receipt,
4300
    string $post_body,
4301
    string $post_subject,
4302
    array $post_replace,
4303
    bool $immediate_email = false,
4304
    $encryptedUserPassword = ''
4305
): ?string {
4306
    global $SETTINGS;
4307
    $emailSettings = new EmailSettings($SETTINGS);
4308
    $emailService = new EmailService();
4309
4310
    // Sanitize inputs
4311
    $post_receipt = filter_var($post_receipt, FILTER_SANITIZE_EMAIL);
4312
    $post_subject = htmlspecialchars($post_subject, ENT_QUOTES, 'UTF-8');
4313
    $post_body = htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8');
4314
4315
    if (count($post_replace) > 0) {
4316
        $post_body = str_replace(
4317
            array_keys($post_replace),
4318
            array_values($post_replace),
4319
            $post_body
4320
        );
4321
    }
4322
4323
    // Remove newlines to prevent header injection
4324
    $post_body = str_replace(array("\r", "\n"), '', $post_body);    
4325
4326
    if ($immediate_email === true) {
4327
        // Send email
4328
        $ret = $emailService->sendMail(
4329
            $post_subject,
4330
            $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 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  2. 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  3. 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  4. 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  6. 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4357
  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 3075
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  10. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3135
  11. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1622
  12. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  13. PHPMailer::sendmailSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  14. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1726
  15. fwrite() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1803

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 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  2. 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  3. 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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324
  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 4300
  10. Data is passed through htmlspecialchars(), and htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8') is assigned to $post_body
    in sources/main.functions.php on line 4313
  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 4324

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4357
  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 3075
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  10. file_put_contents() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3092

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...
4331
            $post_receipt,
4332
            $emailSettings,
4333
            '',
4334
            false
4335
        );
4336
    
4337
        $ret = json_decode($ret, true);
4338
    
4339
        return prepareExchangedData(
4340
            array(
4341
                'error' => empty($ret['error']) === true ? false : true,
4342
                'message' => $ret['message'],
4343
            ),
4344
            'encode'
4345
        );
4346
    } else {
4347
        // Send through task handler
4348
        prepareSendingEmail(
4349
            $post_subject,
4350
            $post_body,
4351
            $post_receipt,
4352
            "",
4353
            $encryptedUserPassword,
4354
        );
4355
    }
4356
4357
    return null;
4358
}
4359
4360
/**
4361
 * Converts a password strengh value to zxcvbn level
4362
 * 
4363
 * @param integer $passwordStrength
4364
 * 
4365
 * @return integer
4366
 */
4367
function convertPasswordStrength($passwordStrength): int
4368
{
4369
    if ($passwordStrength === 0) {
4370
        return TP_PW_STRENGTH_1;
4371
    } else if ($passwordStrength === 1) {
4372
        return TP_PW_STRENGTH_2;
4373
    } else if ($passwordStrength === 2) {
4374
        return TP_PW_STRENGTH_3;
4375
    } else if ($passwordStrength === 3) {
4376
        return TP_PW_STRENGTH_4;
4377
    } else {
4378
        return TP_PW_STRENGTH_5;
4379
    }
4380
}
4381
4382
/**
4383
 * Vérifie si les IDs d'un tableau existent bien dans la table.
4384
 *
4385
 * @param array $ids - Tableau d'IDs à vérifier
4386
 * @param string $tableName - Nom de la table dans laquelle vérifier les IDs
4387
 * @param string $fieldName - Nom du champ dans lequel vérifier les IDs
4388
 * @return array - IDs qui n'existent pas dans la table
4389
 */
4390
function checkIdsExist(array $ids, string $tableName, string $fieldName) : array
4391
{
4392
    // Assure-toi que le tableau d'IDs n'est pas vide
4393
    if (empty($ids)) {
4394
        return [];
4395
    }
4396
4397
    // Nettoyage des IDs pour éviter les injections SQL
4398
    $ids = array_map('intval', $ids);  // Assure que chaque ID est un entier
4399
4400
    // Construction de la requête SQL pour vérifier les IDs dans la table
4401
    $result = DB::query('SELECT id FROM ' . prefixTable($tableName) . ' WHERE ' . $fieldName . ' IN %li', $ids);
4402
4403
    // Extraire les IDs existants de la table
4404
    $existingIds = array_column($result, 'id');
4405
4406
    // Trouver les IDs manquants en comparant les deux tableaux
4407
    $missingIds = array_diff($ids, $existingIds);
4408
4409
    return $missingIds; // Renvoie les IDs qui n'existent pas dans la table
4410
}
4411