Passed
Pull Request — master (#4412)
by Nils
06:38
created

ldapCheckUserPassword()   C

Complexity

Conditions 15
Paths 80

Size

Total Lines 57
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 34
c 0
b 0
f 0
nc 80
nop 3
dl 0
loc 57
rs 5.9166

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.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
1281
    if ($type === 'decode' && is_array($data) === false) {
1282
        // Decrypt if needed
1283
        if ($session->get('encryptClientServer') === 1) {
1284
            $data = (string) Encryption::decrypt(
1285
                (string) $data,
1286
                $session->get('key')
1287
            );
1288
        } else {
1289
            // Double html encoding received
1290
            $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

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

Used in path-write context

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

General Strategies to prevent injection

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

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

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

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

6 paths for user data to reach this point

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

Used in path-write context

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

General Strategies to prevent injection

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

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

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

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