Passed
Push — master ( 662ef8...67da6b )
by Nils
05:57 queued 13s
created

rebuildConfigFile()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
c 0
b 0
f 0
nc 5
nop 2
dl 0
loc 24
rs 9.8333
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');
355
    $globalsVisibleFolders = $session->get('user-accessible_folders');
356
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
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
    // Exclude all PF
365
    $where = new WhereClause('and');
366
    // create a WHERE statement of pieces joined by ANDs
367
    $where->add('personal_folder=%i', 1);
368
    if (
369
        isset($SETTINGS['enable_pf_feature']) === true
370
        && (int) $SETTINGS['enable_pf_feature'] === 1
371
    ) {
372
        $where->add('title=%s', $globalsUserId);
373
        $where->negateLast();
374
    }
375
    // Get ID of personal folder
376
    $persfld = DB::queryfirstrow(
377
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
378
        $globalsUserId
379
    );
380
    if (empty($persfld['id']) === false) {
381
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
382
            array_push($globalsVisibleFolders, $persfld['id']);
383
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
384
            // get all descendants
385
            $tree->rebuild();
386
            $tst = $tree->getDescendants($persfld['id']);
387
            foreach ($tst as $t) {
388
                array_push($globalsVisibleFolders, $t->id);
389
                array_push($globalsPersonalVisibleFolders, $t->id);
390
            }
391
        }
392
    }
393
394
    // get complete list of ROLES
395
    $tmp = explode(';', $idFonctions);
396
    $rows = DB::query(
397
        'SELECT * FROM ' . prefixTable('roles_title') . '
398
        ORDER BY title ASC'
399
    );
400
    foreach ($rows as $record) {
401
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
402
            array_push($tmp, $record['id']);
403
        }
404
    }
405
    $session->set('user-roles', implode(';', $tmp));
406
    $session->set('user-admin', 1);
407
    // Check if admin has created Folders and Roles
408
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
409
    $session->set('user-nb_folders', DB::count());
410
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
411
    $session->set('user-nb_roles', DB::count());
412
413
    return true;
414
}
415
416
/**
417
 * Permits to convert an element to array.
418
 *
419
 * @param string|array $element Any value to be returned as array
420
 *
421
 * @return array
422
 */
423
function convertToArray($element): ?array
424
{
425
    if (is_string($element) === true) {
426
        if (empty($element) === true) {
427
            return [];
428
        }
429
        return explode(
430
            ';',
431
            trimElement($element, ';')
432
        );
433
    }
434
    return $element;
435
}
436
437
/**
438
 * Defines the rights the user has.
439
 *
440
 * @param string|array $allowedFolders  Allowed folders
441
 * @param string|array $noAccessFolders Not allowed folders
442
 * @param string|array $userRoles       Roles of user
443
 * @param array        $SETTINGS        Teampass settings
444
 * @param object       $tree            Tree of folders
445
 * 
446
 * @return bool
447
 */
448
function identUser(
449
    $allowedFolders,
450
    $noAccessFolders,
451
    $userRoles,
452
    array $SETTINGS,
453
    object $tree
454
) {
455
    
456
    $session = SessionManager::getSession();
457
    // Init
458
    $session->set('user-accessible_folders', []);
459
    $session->set('user-personal_folders', []);
460
    $session->set('user-no_access_folders', []);
461
    $session->set('user-personal_visible_folders', []);
462
    $session->set('user-read_only_folders', []);
463
    $session->set('user-user-roles', $userRoles);
464
    $session->set('user-admin', 0);
465
    // init
466
    $personalFolders = [];
467
    $readOnlyFolders = [];
468
    $noAccessPersonalFolders = [];
469
    $restrictedFoldersForItems = [];
470
    $foldersLimited = [];
471
    $foldersLimitedFull = [];
472
    $allowedFoldersByRoles = [];
473
    $globalsUserId = $session->get('user-id');
474
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
475
    // Ensure consistency in array format
476
    $noAccessFolders = convertToArray($noAccessFolders);
477
    $userRoles = convertToArray($userRoles);
478
    $allowedFolders = convertToArray($allowedFolders);
479
    
480
    // Get list of folders depending on Roles
481
    $arrays = identUserGetFoldersFromRoles(
482
        $userRoles,
483
        $allowedFoldersByRoles,
484
        $readOnlyFolders,
485
        $allowedFolders
486
    );
487
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
488
    $readOnlyFolders = $arrays['readOnlyFolders'];
489
490
    // Does this user is allowed to see other items
491
    $inc = 0;
492
    $rows = DB::query(
493
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
494
            WHERE restricted_to LIKE %ss AND inactif = %s'.
495
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
496
        $globalsUserId,
497
        '0'
498
    );
499
    foreach ($rows as $record) {
500
        // Exclude restriction on item if folder is fully accessible
501
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
502
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
503
            ++$inc;
504
        //}
505
    }
506
507
    // Check for the users roles if some specific rights exist on items
508
    $rows = DB::query(
509
        'SELECT i.id_tree, r.item_id
510
        FROM ' . prefixTable('items') . ' as i
511
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
512
        WHERE i.id_tree <> "" '.
513
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
514
        'ORDER BY i.id_tree ASC',
515
        $userRoles
516
    );
517
    $inc = 0;
518
    foreach ($rows as $record) {
519
        //if (isset($record['id_tree'])) {
520
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
521
            array_push($foldersLimitedFull, $record['id_tree']);
522
            ++$inc;
523
        //}
524
    }
525
526
    // Get list of Personal Folders
527
    $arrays = identUserGetPFList(
528
        $globalsPersonalFolders,
529
        $allowedFolders,
530
        $globalsUserId,
531
        $personalFolders,
532
        $noAccessPersonalFolders,
533
        $foldersLimitedFull,
534
        $allowedFoldersByRoles,
535
        array_keys($restrictedFoldersForItems),
536
        $readOnlyFolders,
537
        $noAccessFolders,
538
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
539
        $tree
540
    );
541
    $allowedFolders = $arrays['allowedFolders'];
542
    $personalFolders = $arrays['personalFolders'];
543
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
544
545
    // Return data
546
    $session->set('user-all_non_personal_folders', $allowedFolders);
547
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
548
    $session->set('user-read_only_folders', $readOnlyFolders);
549
    $session->set('user-no_access_folders', $noAccessFolders);
550
    $session->set('user-personal_folders', $personalFolders);
551
    $session->set('user-list_folders_limited', $foldersLimited);
552
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
553
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
554
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
555
    $session->set(
556
        'all_folders_including_no_access',
557
        array_unique(array_merge(
558
            $allowedFolders,
559
            $personalFolders,
560
            $noAccessFolders,
561
            $readOnlyFolders
562
        ), SORT_NUMERIC)
563
    );
564
    // Folders and Roles numbers
565
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
566
    $session->set('user-nb_folders', DB::count());
567
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
568
    $session->set('user-nb_roles', DB::count());
569
    // check if change proposals on User's items
570
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
571
        $countNewItems = DB::query(
572
            'SELECT COUNT(*)
573
            FROM ' . prefixTable('items_change') . ' AS c
574
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
575
            WHERE i.action = %s AND i.id_user = %i',
576
            'at_creation',
577
            $globalsUserId
578
        );
579
        $session->set('user-nb_item_change_proposals', $countNewItems);
580
    } else {
581
        $session->set('user-nb_item_change_proposals', 0);
582
    }
583
584
    return true;
585
}
586
587
/**
588
 * Get list of folders depending on Roles
589
 * 
590
 * @param array $userRoles
591
 * @param array $allowedFoldersByRoles
592
 * @param array $readOnlyFolders
593
 * @param array $allowedFolders
594
 * 
595
 * @return array
596
 */
597
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
598
{
599
    $rows = DB::query(
600
        'SELECT *
601
        FROM ' . prefixTable('roles_values') . '
602
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
603
        ['W', 'ND', 'NE', 'NDNE', 'R'],
604
        $userRoles,
605
    );
606
    foreach ($rows as $record) {
607
        if ($record['type'] === 'R') {
608
            array_push($readOnlyFolders, $record['folder_id']);
609
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
610
            array_push($allowedFoldersByRoles, $record['folder_id']);
611
        }
612
    }
613
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
614
    $readOnlyFolders = array_unique($readOnlyFolders);
615
    
616
    // Clean arrays
617
    foreach ($allowedFoldersByRoles as $value) {
618
        $key = array_search($value, $readOnlyFolders);
619
        if ($key !== false) {
620
            unset($readOnlyFolders[$key]);
621
        }
622
    }
623
    return [
624
        'readOnlyFolders' => $readOnlyFolders,
625
        'allowedFoldersByRoles' => $allowedFoldersByRoles
626
    ];
627
}
628
629
/**
630
 * Get list of Personal Folders
631
 * 
632
 * @param int $globalsPersonalFolders
633
 * @param array $allowedFolders
634
 * @param int $globalsUserId
635
 * @param array $personalFolders
636
 * @param array $noAccessPersonalFolders
637
 * @param array $foldersLimitedFull
638
 * @param array $allowedFoldersByRoles
639
 * @param array $restrictedFoldersForItems
640
 * @param array $readOnlyFolders
641
 * @param array $noAccessFolders
642
 * @param int $enablePfFeature
643
 * @param object $tree
644
 * 
645
 * @return array
646
 */
647
function identUserGetPFList(
648
    $globalsPersonalFolders,
649
    $allowedFolders,
650
    $globalsUserId,
651
    $personalFolders,
652
    $noAccessPersonalFolders,
653
    $foldersLimitedFull,
654
    $allowedFoldersByRoles,
655
    $restrictedFoldersForItems,
656
    $readOnlyFolders,
657
    $noAccessFolders,
658
    $enablePfFeature,
659
    $tree
660
)
661
{
662
    if (
663
        (int) $enablePfFeature === 1
664
        && (int) $globalsPersonalFolders === 1
665
    ) {
666
        $persoFld = DB::queryfirstrow(
667
            'SELECT id
668
            FROM ' . prefixTable('nested_tree') . '
669
            WHERE title = %s AND personal_folder = %i'.
670
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
671
            $globalsUserId,
672
            1
673
        );
674
        if (empty($persoFld['id']) === false) {
675
            array_push($personalFolders, $persoFld['id']);
676
            array_push($allowedFolders, $persoFld['id']);
677
            // get all descendants
678
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
679
            foreach ($ids as $id) {
680
                //array_push($allowedFolders, $id);
681
                array_push($personalFolders, $id);
682
            }
683
        }
684
    }
685
    
686
    // Exclude all other PF
687
    $where = new WhereClause('and');
688
    $where->add('personal_folder=%i', 1);
689
    if (count($personalFolders) > 0) {
690
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
691
    }
692
    if (
693
        (int) $enablePfFeature === 1
694
        && (int) $globalsPersonalFolders === 1
695
    ) {
696
        $where->add('title=%s', $globalsUserId);
697
        $where->negateLast();
698
    }
699
    $persoFlds = DB::query(
700
        'SELECT id
701
        FROM ' . prefixTable('nested_tree') . '
702
        WHERE %l',
703
        $where
704
    );
705
    foreach ($persoFlds as $persoFldId) {
706
        array_push($noAccessPersonalFolders, $persoFldId['id']);
707
    }
708
709
    // All folders visibles
710
    $allowedFolders = array_unique(array_merge(
711
        $allowedFolders,
712
        $foldersLimitedFull,
713
        $allowedFoldersByRoles,
714
        $restrictedFoldersForItems,
715
        $readOnlyFolders
716
    ), SORT_NUMERIC);
717
    // Exclude from allowed folders all the specific user forbidden folders
718
    if (count($noAccessFolders) > 0) {
719
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
720
    }
721
722
    return [
723
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
724
        'personalFolders' => $personalFolders,
725
        'noAccessPersonalFolders' => $noAccessPersonalFolders
726
    ];
727
}
728
729
730
/**
731
 * Update the CACHE table.
732
 *
733
 * @param string $action   What to do
734
 * @param array  $SETTINGS Teampass settings
735
 * @param int    $ident    Ident format
736
 * 
737
 * @return void
738
 */
739
function updateCacheTable(string $action, ?int $ident = null): void
740
{
741
    if ($action === 'reload') {
742
        // Rebuild full cache table
743
        cacheTableRefresh();
744
    } elseif ($action === 'update_value' && is_null($ident) === false) {
745
        // UPDATE an item
746
        cacheTableUpdate($ident);
747
    } elseif ($action === 'add_value' && is_null($ident) === false) {
748
        // ADD an item
749
        cacheTableAdd($ident);
750
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
751
        // DELETE an item
752
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
753
    }
754
}
755
756
/**
757
 * Cache table - refresh.
758
 *
759
 * @return void
760
 */
761
function cacheTableRefresh(): void
762
{
763
    // Load class DB
764
    loadClasses('DB');
765
766
    //Load Tree
767
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
768
    // truncate table
769
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
770
    // reload date
771
    $rows = DB::query(
772
        'SELECT *
773
        FROM ' . prefixTable('items') . ' as i
774
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
775
        AND l.action = %s
776
        AND i.inactif = %i',
777
        'at_creation',
778
        0
779
    );
780
    foreach ($rows as $record) {
781
        if (empty($record['id_tree']) === false) {
782
            // Get all TAGS
783
            $tags = '';
784
            $itemTags = DB::query(
785
                'SELECT tag
786
                FROM ' . prefixTable('tags') . '
787
                WHERE item_id = %i AND tag != ""',
788
                $record['id']
789
            );
790
            foreach ($itemTags as $itemTag) {
791
                $tags .= $itemTag['tag'] . ' ';
792
            }
793
794
            // Get renewal period
795
            $resNT = DB::queryfirstrow(
796
                'SELECT renewal_period
797
                FROM ' . prefixTable('nested_tree') . '
798
                WHERE id = %i',
799
                $record['id_tree']
800
            );
801
            // form id_tree to full foldername
802
            $folder = [];
803
            $arbo = $tree->getPath($record['id_tree'], true);
804
            foreach ($arbo as $elem) {
805
                // Check if title is the ID of a user
806
                if (is_numeric($elem->title) === true) {
807
                    // Is this a User id?
808
                    $user = DB::queryfirstrow(
809
                        'SELECT id, login
810
                        FROM ' . prefixTable('users') . '
811
                        WHERE id = %i',
812
                        $elem->title
813
                    );
814
                    if (count($user) > 0) {
815
                        $elem->title = $user['login'];
816
                    }
817
                }
818
                // Build path
819
                array_push($folder, stripslashes($elem->title));
820
            }
821
            // store data
822
            DB::insert(
823
                prefixTable('cache'),
824
                [
825
                    'id' => $record['id'],
826
                    'label' => $record['label'],
827
                    'description' => $record['description'] ?? '',
828
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
829
                    'tags' => $tags,
830
                    'id_tree' => $record['id_tree'],
831
                    'perso' => $record['perso'],
832
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
833
                    'login' => $record['login'] ?? '',
834
                    'folder' => implode(' > ', $folder),
835
                    'author' => $record['id_user'],
836
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
837
                    'timestamp' => $record['date'],
838
                ]
839
            );
840
        }
841
    }
842
}
843
844
/**
845
 * Cache table - update existing value.
846
 *
847
 * @param int    $ident    Ident format
848
 * 
849
 * @return void
850
 */
851
function cacheTableUpdate(?int $ident = null): void
852
{
853
    $session = SessionManager::getSession();
854
    loadClasses('DB');
855
856
    //Load Tree
857
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
858
    // get new value from db
859
    $data = DB::queryfirstrow(
860
        'SELECT label, description, id_tree, perso, restricted_to, login, url
861
        FROM ' . prefixTable('items') . '
862
        WHERE id=%i',
863
        $ident
864
    );
865
    // Get all TAGS
866
    $tags = '';
867
    $itemTags = DB::query(
868
        'SELECT tag
869
            FROM ' . prefixTable('tags') . '
870
            WHERE item_id = %i AND tag != ""',
871
        $ident
872
    );
873
    foreach ($itemTags as $itemTag) {
874
        $tags .= $itemTag['tag'] . ' ';
875
    }
876
    // form id_tree to full foldername
877
    $folder = [];
878
    $arbo = $tree->getPath($data['id_tree'], true);
879
    foreach ($arbo as $elem) {
880
        // Check if title is the ID of a user
881
        if (is_numeric($elem->title) === true) {
882
            // Is this a User id?
883
            $user = DB::queryfirstrow(
884
                'SELECT id, login
885
                FROM ' . prefixTable('users') . '
886
                WHERE id = %i',
887
                $elem->title
888
            );
889
            if (count($user) > 0) {
890
                $elem->title = $user['login'];
891
            }
892
        }
893
        // Build path
894
        array_push($folder, stripslashes($elem->title));
895
    }
896
    // finaly update
897
    DB::update(
898
        prefixTable('cache'),
899
        [
900
            'label' => $data['label'],
901
            'description' => $data['description'],
902
            'tags' => $tags,
903
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
904
            'id_tree' => $data['id_tree'],
905
            'perso' => $data['perso'],
906
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
907
            'login' => $data['login'] ?? '',
908
            'folder' => implode(' » ', $folder),
909
            'author' => $session->get('user-id'),
910
        ],
911
        'id = %i',
912
        $ident
913
    );
914
}
915
916
/**
917
 * Cache table - add new value.
918
 *
919
 * @param int    $ident    Ident format
920
 * 
921
 * @return void
922
 */
923
function cacheTableAdd(?int $ident = null): void
924
{
925
    $session = SessionManager::getSession();
926
    $globalsUserId = $session->get('user-id');
927
928
    // Load class DB
929
    loadClasses('DB');
930
931
    //Load Tree
932
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
933
    // get new value from db
934
    $data = DB::queryFirstRow(
935
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
936
        FROM ' . prefixTable('items') . ' as i
937
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
938
        WHERE i.id = %i
939
        AND l.action = %s',
940
        $ident,
941
        'at_creation'
942
    );
943
    // Get all TAGS
944
    $tags = '';
945
    $itemTags = DB::query(
946
        'SELECT tag
947
            FROM ' . prefixTable('tags') . '
948
            WHERE item_id = %i AND tag != ""',
949
        $ident
950
    );
951
    foreach ($itemTags as $itemTag) {
952
        $tags .= $itemTag['tag'] . ' ';
953
    }
954
    // form id_tree to full foldername
955
    $folder = [];
956
    $arbo = $tree->getPath($data['id_tree'], true);
957
    foreach ($arbo as $elem) {
958
        // Check if title is the ID of a user
959
        if (is_numeric($elem->title) === true) {
960
            // Is this a User id?
961
            $user = DB::queryfirstrow(
962
                'SELECT id, login
963
                FROM ' . prefixTable('users') . '
964
                WHERE id = %i',
965
                $elem->title
966
            );
967
            if (count($user) > 0) {
968
                $elem->title = $user['login'];
969
            }
970
        }
971
        // Build path
972
        array_push($folder, stripslashes($elem->title));
973
    }
974
    // finaly update
975
    DB::insert(
976
        prefixTable('cache'),
977
        [
978
            'id' => $data['id'],
979
            'label' => $data['label'],
980
            'description' => $data['description'],
981
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
982
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
983
            'id_tree' => $data['id_tree'],
984
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
985
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
986
            'login' => $data['login'] ?? '',
987
            'folder' => implode(' » ', $folder),
988
            'author' => $globalsUserId,
989
            'timestamp' => $data['date'],
990
        ]
991
    );
992
}
993
994
/**
995
 * Do statistics.
996
 *
997
 * @param array $SETTINGS Teampass settings
998
 *
999
 * @return array
1000
 */
1001
function getStatisticsData(array $SETTINGS): array
1002
{
1003
    DB::query(
1004
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1005
        0
1006
    );
1007
    $counter_folders = DB::count();
1008
    DB::query(
1009
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1010
        1
1011
    );
1012
    $counter_folders_perso = DB::count();
1013
    DB::query(
1014
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1015
        0
1016
    );
1017
    $counter_items = DB::count();
1018
        DB::query(
1019
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1020
        1
1021
    );
1022
    $counter_items_perso = DB::count();
1023
        DB::query(
1024
        'SELECT id FROM ' . prefixTable('users') . ' WHERE login NOT IN (%s, %s, %s)',
1025
        'OTV', 'TP', 'API'
1026
    );
1027
    $counter_users = DB::count();
1028
        DB::query(
1029
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1030
        1
1031
    );
1032
    $admins = DB::count();
1033
    DB::query(
1034
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1035
        1
1036
    );
1037
    $managers = DB::count();
1038
    DB::query(
1039
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1040
        1
1041
    );
1042
    $readOnly = DB::count();
1043
    // list the languages
1044
    $usedLang = [];
1045
    $tp_languages = DB::query(
1046
        'SELECT name FROM ' . prefixTable('languages')
1047
    );
1048
    foreach ($tp_languages as $tp_language) {
1049
        DB::query(
1050
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1051
            $tp_language['name']
1052
        );
1053
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1054
    }
1055
1056
    // get list of ips
1057
    $usedIp = [];
1058
    $tp_ips = DB::query(
1059
        'SELECT user_ip FROM ' . prefixTable('users')
1060
    );
1061
    foreach ($tp_ips as $ip) {
1062
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1063
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1064
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1065
            $usedIp[$ip['user_ip']] = 1;
1066
        }
1067
    }
1068
1069
    return [
1070
        'error' => '',
1071
        'stat_phpversion' => phpversion(),
1072
        'stat_folders' => $counter_folders,
1073
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1074
        'stat_items' => $counter_items,
1075
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1076
        'stat_users' => $counter_users,
1077
        'stat_admins' => $admins,
1078
        'stat_managers' => $managers,
1079
        'stat_ro' => $readOnly,
1080
        'stat_kb' => $SETTINGS['enable_kb'],
1081
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1082
        'stat_fav' => $SETTINGS['enable_favourites'],
1083
        'stat_teampassversion' => TP_VERSION,
1084
        'stat_ldap' => $SETTINGS['ldap_mode'],
1085
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1086
        'stat_duo' => $SETTINGS['duo'],
1087
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1088
        'stat_api' => $SETTINGS['api'],
1089
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1090
        'stat_syslog' => $SETTINGS['syslog_enable'],
1091
        'stat_2fa' => $SETTINGS['google_authentication'],
1092
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1093
        'stat_mysqlversion' => DB::serverVersion(),
1094
        'stat_languages' => $usedLang,
1095
        'stat_country' => $usedIp,
1096
    ];
1097
}
1098
1099
/**
1100
 * Permits to prepare the way to send the email
1101
 * 
1102
 * @param string $subject       email subject
1103
 * @param string $body          email message
1104
 * @param string $email         email
1105
 * @param string $receiverName  Receiver name
1106
 * @param string $encryptedUserPassword      encryptedUserPassword
1107
 *
1108
 * @return void
1109
 */
1110
function prepareSendingEmail(
1111
    $subject,
1112
    $body,
1113
    $email,
1114
    $receiverName = '',
1115
    $encryptedUserPassword = ''
1116
): void 
1117
{
1118
    DB::insert(
1119
        prefixTable('background_tasks'),
1120
        array(
1121
            'created_at' => time(),
1122
            'process_type' => 'send_email',
1123
            'arguments' => json_encode([
1124
                'subject' => $subject,
1125
                'receivers' => $email,
1126
                'body' => $body,
1127
                'receiver_name' => $receiverName,
1128
                'encryptedUserPassword' => $encryptedUserPassword,
1129
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1130
        )
1131
    );
1132
}
1133
1134
/**
1135
 * Returns the email body.
1136
 *
1137
 * @param string $textMail Text for the email
1138
 */
1139
function emailBody(string $textMail): string
1140
{
1141
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1142
    w3.org/TR/html4/loose.dtd"><html>
1143
    <head><title>Email Template</title>
1144
    <style type="text/css">
1145
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1146
    </style></head>
1147
    <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">
1148
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1149
    <tr><td style="border-collapse: collapse;"><br>
1150
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1151
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1152
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1153
        </td></tr></table></td>
1154
    </tr>
1155
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1156
        <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;">
1157
        <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;">
1158
        <br><div style="float:right;">' .
1159
        $textMail .
1160
        '<br><br></td></tr></table>
1161
    </td></tr></table>
1162
    <br></body></html>';
1163
}
1164
1165
/**
1166
 * Convert date to timestamp.
1167
 *
1168
 * @param string $date        The date
1169
 * @param string $date_format Date format
1170
 *
1171
 * @return int
1172
 */
1173
function dateToStamp(string $date, string $date_format): int
1174
{
1175
    $date = date_parse_from_format($date_format, $date);
1176
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1177
        return mktime(
1178
            empty($date['hour']) === false ? $date['hour'] : 23,
1179
            empty($date['minute']) === false ? $date['minute'] : 59,
1180
            empty($date['second']) === false ? $date['second'] : 59,
1181
            $date['month'],
1182
            $date['day'],
1183
            $date['year']
1184
        );
1185
    }
1186
    return 0;
1187
}
1188
1189
/**
1190
 * Is this a date.
1191
 *
1192
 * @param string $date Date
1193
 *
1194
 * @return bool
1195
 */
1196
function isDate(string $date): bool
1197
{
1198
    return strtotime($date) !== false;
1199
}
1200
1201
/**
1202
 * Check if isUTF8().
1203
 *
1204
 * @param string|array $string Is the string
1205
 *
1206
 * @return int is the string in UTF8 format
1207
 */
1208
function isUTF8($string): int
1209
{
1210
    if (is_array($string) === true) {
1211
        $string = $string['string'];
1212
    }
1213
1214
    return preg_match(
1215
        '%^(?:
1216
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1217
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1218
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1219
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1220
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1221
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1222
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1223
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1224
        )*$%xs',
1225
        $string
1226
    );
1227
}
1228
1229
/**
1230
 * Prepare an array to UTF8 format before JSON_encode.
1231
 *
1232
 * @param array $array Array of values
1233
 *
1234
 * @return array
1235
 */
1236
function utf8Converter(array $array): array
1237
{
1238
    array_walk_recursive(
1239
        $array,
1240
        static function (&$item): void {
1241
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1242
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1243
            }
1244
        }
1245
    );
1246
    return $array;
1247
}
1248
1249
/**
1250
 * Permits to prepare data to be exchanged.
1251
 *
1252
 * @param array|string $data Text
1253
 * @param string       $type Parameter
1254
 * @param string       $key  Optional key
1255
 *
1256
 * @return string|array
1257
 */
1258
function prepareExchangedData($data, string $type, ?string $key = null)
1259
{
1260
    $session = SessionManager::getSession();
1261
    
1262
    // Perform
1263
    if ($type === 'encode' && is_array($data) === true) {
1264
        // json encoding
1265
        $data = json_encode(
1266
            $data,
1267
            JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1268
        );
1269
1270
        // Now encrypt
1271
        if ($session->get('encryptClientServer') === 1) {
1272
            $data = Encryption::encrypt(
1273
                $data,
1274
                $session->get('key')
1275
            );
1276
        }
1277
1278
        return $data;
1279
    }
1280
    if ($type === 'decode' && is_array($data) === false) {
1281
        // Decrypt if needed
1282
        if ($session->get('encryptClientServer') === 1) {
1283
            $data = (string) Encryption::decrypt(
1284
                (string) $data,
1285
                $session->get('key')
1286
            );
1287
        } else {
1288
            // Double html encoding received
1289
            $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

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

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

4334
            array_keys(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4335
            array_values($post_replace),
0 ignored issues
show
Bug introduced by
$post_replace of type null is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

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

4335
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4336
            $post_body
4337
        );
4338
    }
4339
4340
    if ($immediate_email === true) {
4341
        
4342
        $ret = $emailService->sendMail(
4343
            $post_subject,
4344
            $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 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4554
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 94
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  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 1689
  13. PHPMailer::smtpSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1689
  14. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 2058
  15. SMTP::data() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 2096
  16. Enters via parameter $msg_data
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 728
  17. Data is passed through str_replace()
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 744
  18. Data is passed through explode()
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 744
  19. ``explode(' ', str_replace(array(' ', ' '), ' ', $msg_data))`` is assigned to $lines
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 744
  20. $lines is assigned to $line
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 757
  21. $line is assigned to $lines_out
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 785
  22. $lines_out is assigned to $line_out
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 788
  23. SMTP::client_send() is called
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 794
  24. Enters via parameter $data
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 1153
  25. fwrite() is called
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 1166

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 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4554
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 94
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  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...
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 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  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 64
  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 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4322

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4554
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 94
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  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 1779

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