Passed
Pull Request — master (#4469)
by Nils
05:39
created

storeUsersShareKey()   C

Complexity

Conditions 14
Paths 32

Size

Total Lines 101
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 14
eloc 56
c 3
b 0
f 0
nc 32
nop 9
dl 0
loc 101
rs 6.2666

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.functions.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use LdapRecord\Connection;
33
use ForceUTF8\Encoding;
34
use Elegant\Sanitizer\Sanitizer;
35
use voku\helper\AntiXSS;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use TeampassClasses\SessionManager\SessionManager;
39
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
40
use TeampassClasses\Language\Language;
41
use TeampassClasses\NestedTree\NestedTree;
42
use Defuse\Crypto\Key;
43
use Defuse\Crypto\Crypto;
44
use Defuse\Crypto\KeyProtectedByPassword;
45
use Defuse\Crypto\File as CryptoFile;
46
use Defuse\Crypto\Exception as CryptoException;
47
use Elegant\Sanitizer\Filters\Uppercase;
48
use PHPMailer\PHPMailer\PHPMailer;
49
use TeampassClasses\PasswordManager\PasswordManager;
50
use Symfony\Component\Process\Exception\ProcessFailedException;
51
use Symfony\Component\Process\Process;
52
use Symfony\Component\Process\PhpExecutableFinder;
53
use TeampassClasses\Encryption\Encryption;
54
use TeampassClasses\ConfigManager\ConfigManager;
55
use TeampassClasses\EmailService\EmailService;
56
use TeampassClasses\EmailService\EmailSettings;
57
58
header('Content-type: text/html; charset=utf-8');
59
header('Cache-Control: no-cache, must-revalidate');
60
61
loadClasses('DB');
62
$session = SessionManager::getSession();
63
64
// Load config if $SETTINGS not defined
65
$configManager = new ConfigManager($session);
66
$SETTINGS = $configManager->getAllSettings();
67
68
/**
69
 * genHash().
70
 *
71
 * Generate a hash for user login
72
 *
73
 * @param string $password What password
74
 * @param string $cost     What cost
75
 *
76
 * @return string|void
77
 */
78
/* TODO - Remove this function
79
function bCrypt(
80
    string $password,
81
    string $cost
82
): ?string
83
{
84
    $salt = sprintf('$2y$%02d$', $cost);
85
    if (function_exists('openssl_random_pseudo_bytes')) {
86
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
87
    } else {
88
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
89
        for ($i = 0; $i < 22; ++$i) {
90
            $salt .= $chars[mt_rand(0, 63)];
91
        }
92
    }
93
94
    return crypt($password, $salt);
95
}
96
*/
97
98
/**
99
 * Checks if a string is hex encoded
100
 *
101
 * @param string $str
102
 * @return boolean
103
 */
104
function isHex(string $str): bool
105
{
106
    if (str_starts_with(strtolower($str), '0x')) {
107
        $str = substr($str, 2);
108
    }
109
110
    return ctype_xdigit($str);
111
}
112
113
/**
114
 * Defuse cryption function.
115
 *
116
 * @param string $message   what to de/crypt
117
 * @param string $ascii_key key to use
118
 * @param string $type      operation to perform
119
 * @param array  $SETTINGS  Teampass settings
120
 *
121
 * @return array
122
 */
123
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
124
{
125
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
126
    $err = false;
127
    
128
    // convert KEY
129
    $key = Key::loadFromAsciiSafeString($ascii_key);
130
    try {
131
        if ($type === 'encrypt') {
132
            $text = Crypto::encrypt($message, $key);
133
        } elseif ($type === 'decrypt') {
134
            $text = Crypto::decrypt($message, $key);
135
        }
136
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
137
        $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
    
355
    // Get list of Folders
356
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
357
    foreach ($rows as $record) {
358
        array_push($groupesVisibles, $record['id']);
359
    }
360
    $session->set('user-accessible_folders', $groupesVisibles);
361
    $session->set('user-all_non_personal_folders', $groupesVisibles);
362
363
    // get complete list of ROLES
364
    $tmp = explode(';', $idFonctions);
365
    $rows = DB::query(
366
        'SELECT * FROM ' . prefixTable('roles_title') . '
367
        ORDER BY title ASC'
368
    );
369
    foreach ($rows as $record) {
370
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
371
            array_push($tmp, $record['id']);
372
        }
373
    }
374
    $session->set('user-roles', implode(';', $tmp));
375
    $session->set('user-admin', 1);
376
    // Check if admin has created Folders and Roles
377
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
378
    $session->set('user-nb_folders', DB::count());
379
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
380
    $session->set('user-nb_roles', DB::count());
381
382
    return true;
383
}
384
385
/**
386
 * Permits to convert an element to array.
387
 *
388
 * @param string|array $element Any value to be returned as array
389
 *
390
 * @return array
391
 */
392
function convertToArray($element): ?array
393
{
394
    if (is_string($element) === true) {
395
        if (empty($element) === true) {
396
            return [];
397
        }
398
        return explode(
399
            ';',
400
            trimElement($element, ';')
401
        );
402
    }
403
    return $element;
404
}
405
406
/**
407
 * Defines the rights the user has.
408
 *
409
 * @param string|array $allowedFolders  Allowed folders
410
 * @param string|array $noAccessFolders Not allowed folders
411
 * @param string|array $userRoles       Roles of user
412
 * @param array        $SETTINGS        Teampass settings
413
 * @param object       $tree            Tree of folders
414
 * 
415
 * @return bool
416
 */
417
function identUser(
418
    $allowedFolders,
419
    $noAccessFolders,
420
    $userRoles,
421
    array $SETTINGS,
422
    object $tree
423
) {
424
    $session = SessionManager::getSession();
425
    // Init
426
    $session->set('user-accessible_folders', []);
427
    $session->set('user-personal_folders', []);
428
    $session->set('user-no_access_folders', []);
429
    $session->set('user-personal_visible_folders', []);
430
    $session->set('user-read_only_folders', []);
431
    $session->set('user-user-roles', $userRoles);
432
    $session->set('user-admin', 0);
433
    // init
434
    $personalFolders = [];
435
    $readOnlyFolders = [];
436
    $noAccessPersonalFolders = [];
437
    $restrictedFoldersForItems = [];
438
    $foldersLimited = [];
439
    $foldersLimitedFull = [];
440
    $allowedFoldersByRoles = [];
441
    $globalsUserId = $session->get('user-id');
442
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
443
    // Ensure consistency in array format
444
    $noAccessFolders = convertToArray($noAccessFolders);
445
    $userRoles = convertToArray($userRoles);
446
    $allowedFolders = convertToArray($allowedFolders);
447
    $session->set('user-allowed_folders_by_definition', $allowedFolders);
448
    
449
    // Get list of folders depending on Roles
450
    $arrays = identUserGetFoldersFromRoles(
451
        $userRoles,
452
        $allowedFoldersByRoles,
453
        $readOnlyFolders,
454
        $allowedFolders
455
    );
456
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
457
    $readOnlyFolders = $arrays['readOnlyFolders'];
458
459
    // Does this user is allowed to see other items
460
    $inc = 0;
461
    $rows = DB::query(
462
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
463
            WHERE restricted_to LIKE %ss AND inactif = %s'.
464
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
465
        $globalsUserId,
466
        '0'
467
    );
468
    foreach ($rows as $record) {
469
        // Exclude restriction on item if folder is fully accessible
470
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
471
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
472
            ++$inc;
473
        //}
474
    }
475
476
    // Check for the users roles if some specific rights exist on items
477
    $rows = DB::query(
478
        'SELECT i.id_tree, r.item_id
479
        FROM ' . prefixTable('items') . ' as i
480
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
481
        WHERE i.id_tree <> "" '.
482
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
483
        'ORDER BY i.id_tree ASC',
484
        $userRoles
485
    );
486
    $inc = 0;
487
    foreach ($rows as $record) {
488
        //if (isset($record['id_tree'])) {
489
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
490
            array_push($foldersLimitedFull, $record['id_tree']);
491
            ++$inc;
492
        //}
493
    }
494
495
    // Get list of Personal Folders
496
    $arrays = identUserGetPFList(
497
        $globalsPersonalFolders,
498
        $allowedFolders,
499
        $globalsUserId,
500
        $personalFolders,
501
        $noAccessPersonalFolders,
502
        $foldersLimitedFull,
503
        $allowedFoldersByRoles,
504
        array_keys($restrictedFoldersForItems),
505
        $readOnlyFolders,
506
        $noAccessFolders,
507
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
508
        $tree
509
    );
510
    $allowedFolders = $arrays['allowedFolders'];
511
    $personalFolders = $arrays['personalFolders'];
512
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
513
514
    // Return data
515
    $session->set('user-all_non_personal_folders', $allowedFolders);
516
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
517
    $session->set('user-read_only_folders', $readOnlyFolders);
518
    $session->set('user-no_access_folders', $noAccessFolders);
519
    $session->set('user-personal_folders', $personalFolders);
520
    $session->set('user-list_folders_limited', $foldersLimited);
521
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
522
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
523
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
524
    $session->set(
525
        'all_folders_including_no_access',
526
        array_unique(array_merge(
527
            $allowedFolders,
528
            $personalFolders,
529
            $noAccessFolders,
530
            $readOnlyFolders
531
        ), SORT_NUMERIC)
532
    );
533
    // Folders and Roles numbers
534
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
535
    $session->set('user-nb_folders', DB::count());
536
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
537
    $session->set('user-nb_roles', DB::count());
538
    // check if change proposals on User's items
539
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
540
        $countNewItems = DB::query(
541
            'SELECT COUNT(*)
542
            FROM ' . prefixTable('items_change') . ' AS c
543
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
544
            WHERE i.action = %s AND i.id_user = %i',
545
            'at_creation',
546
            $globalsUserId
547
        );
548
        $session->set('user-nb_item_change_proposals', $countNewItems);
549
    } else {
550
        $session->set('user-nb_item_change_proposals', 0);
551
    }
552
553
    return true;
554
}
555
556
/**
557
 * Get list of folders depending on Roles
558
 * 
559
 * @param array $userRoles
560
 * @param array $allowedFoldersByRoles
561
 * @param array $readOnlyFolders
562
 * @param array $allowedFolders
563
 * 
564
 * @return array
565
 */
566
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
567
{
568
    $rows = DB::query(
569
        'SELECT *
570
        FROM ' . prefixTable('roles_values') . '
571
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
572
        ['W', 'ND', 'NE', 'NDNE', 'R'],
573
        $userRoles,
574
    );
575
    foreach ($rows as $record) {
576
        if ($record['type'] === 'R') {
577
            array_push($readOnlyFolders, $record['folder_id']);
578
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
579
            array_push($allowedFoldersByRoles, $record['folder_id']);
580
        }
581
    }
582
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
583
    $readOnlyFolders = array_unique($readOnlyFolders);
584
    
585
    // Clean arrays
586
    foreach ($allowedFoldersByRoles as $value) {
587
        $key = array_search($value, $readOnlyFolders);
588
        if ($key !== false) {
589
            unset($readOnlyFolders[$key]);
590
        }
591
    }
592
    return [
593
        'readOnlyFolders' => $readOnlyFolders,
594
        'allowedFoldersByRoles' => $allowedFoldersByRoles
595
    ];
596
}
597
598
/**
599
 * Get list of Personal Folders
600
 * 
601
 * @param int $globalsPersonalFolders
602
 * @param array $allowedFolders
603
 * @param int $globalsUserId
604
 * @param array $personalFolders
605
 * @param array $noAccessPersonalFolders
606
 * @param array $foldersLimitedFull
607
 * @param array $allowedFoldersByRoles
608
 * @param array $restrictedFoldersForItems
609
 * @param array $readOnlyFolders
610
 * @param array $noAccessFolders
611
 * @param int $enablePfFeature
612
 * @param object $tree
613
 * 
614
 * @return array
615
 */
616
function identUserGetPFList(
617
    $globalsPersonalFolders,
618
    $allowedFolders,
619
    $globalsUserId,
620
    $personalFolders,
621
    $noAccessPersonalFolders,
622
    $foldersLimitedFull,
623
    $allowedFoldersByRoles,
624
    $restrictedFoldersForItems,
625
    $readOnlyFolders,
626
    $noAccessFolders,
627
    $enablePfFeature,
628
    $tree
629
)
630
{
631
    if (
632
        (int) $enablePfFeature === 1
633
        && (int) $globalsPersonalFolders === 1
634
    ) {
635
        $persoFld = DB::queryfirstrow(
636
            'SELECT id
637
            FROM ' . prefixTable('nested_tree') . '
638
            WHERE title = %s AND personal_folder = %i'.
639
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
640
            $globalsUserId,
641
            1
642
        );
643
        if (empty($persoFld['id']) === false) {
644
            array_push($personalFolders, $persoFld['id']);
645
            array_push($allowedFolders, $persoFld['id']);
646
            // get all descendants
647
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
648
            foreach ($ids as $id) {
649
                //array_push($allowedFolders, $id);
650
                array_push($personalFolders, $id);
651
            }
652
        }
653
    }
654
    
655
    // Exclude all other PF
656
    $where = new WhereClause('and');
657
    $where->add('personal_folder=%i', 1);
658
    if (count($personalFolders) > 0) {
659
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
660
    }
661
    if (
662
        (int) $enablePfFeature === 1
663
        && (int) $globalsPersonalFolders === 1
664
    ) {
665
        $where->add('title=%s', $globalsUserId);
666
        $where->negateLast();
667
    }
668
    $persoFlds = DB::query(
669
        'SELECT id
670
        FROM ' . prefixTable('nested_tree') . '
671
        WHERE %l',
672
        $where
673
    );
674
    foreach ($persoFlds as $persoFldId) {
675
        array_push($noAccessPersonalFolders, $persoFldId['id']);
676
    }
677
678
    // All folders visibles
679
    $allowedFolders = array_unique(array_merge(
680
        $allowedFolders,
681
        $foldersLimitedFull,
682
        $allowedFoldersByRoles,
683
        $restrictedFoldersForItems,
684
        $readOnlyFolders
685
    ), SORT_NUMERIC);
686
    // Exclude from allowed folders all the specific user forbidden folders
687
    if (count($noAccessFolders) > 0) {
688
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
689
    }
690
691
    return [
692
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
693
        'personalFolders' => $personalFolders,
694
        'noAccessPersonalFolders' => $noAccessPersonalFolders
695
    ];
696
}
697
698
699
/**
700
 * Update the CACHE table.
701
 *
702
 * @param string $action   What to do
703
 * @param array  $SETTINGS Teampass settings
704
 * @param int    $ident    Ident format
705
 * 
706
 * @return void
707
 */
708
function updateCacheTable(string $action, ?int $ident = null): void
709
{
710
    if ($action === 'reload') {
711
        // Rebuild full cache table
712
        cacheTableRefresh();
713
    } elseif ($action === 'update_value' && is_null($ident) === false) {
714
        // UPDATE an item
715
        cacheTableUpdate($ident);
716
    } elseif ($action === 'add_value' && is_null($ident) === false) {
717
        // ADD an item
718
        cacheTableAdd($ident);
719
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
720
        // DELETE an item
721
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
722
    }
723
}
724
725
/**
726
 * Cache table - refresh.
727
 *
728
 * @return void
729
 */
730
function cacheTableRefresh(): void
731
{
732
    // Load class DB
733
    loadClasses('DB');
734
735
    //Load Tree
736
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
737
    // truncate table
738
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
739
    // reload date
740
    $rows = DB::query(
741
        'SELECT *
742
        FROM ' . prefixTable('items') . ' as i
743
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
744
        AND l.action = %s
745
        AND i.inactif = %i',
746
        'at_creation',
747
        0
748
    );
749
    foreach ($rows as $record) {
750
        if (empty($record['id_tree']) === false) {
751
            // Get all TAGS
752
            $tags = '';
753
            $itemTags = DB::query(
754
                'SELECT tag
755
                FROM ' . prefixTable('tags') . '
756
                WHERE item_id = %i AND tag != ""',
757
                $record['id']
758
            );
759
            foreach ($itemTags as $itemTag) {
760
                $tags .= $itemTag['tag'] . ' ';
761
            }
762
763
            // Get renewal period
764
            $resNT = DB::queryfirstrow(
765
                'SELECT renewal_period
766
                FROM ' . prefixTable('nested_tree') . '
767
                WHERE id = %i',
768
                $record['id_tree']
769
            );
770
            // form id_tree to full foldername
771
            $folder = [];
772
            $arbo = $tree->getPath($record['id_tree'], true);
773
            foreach ($arbo as $elem) {
774
                // Check if title is the ID of a user
775
                if (is_numeric($elem->title) === true) {
776
                    // Is this a User id?
777
                    $user = DB::queryfirstrow(
778
                        'SELECT id, login
779
                        FROM ' . prefixTable('users') . '
780
                        WHERE id = %i',
781
                        $elem->title
782
                    );
783
                    if (count($user) > 0) {
784
                        $elem->title = $user['login'];
785
                    }
786
                }
787
                // Build path
788
                array_push($folder, stripslashes($elem->title));
789
            }
790
            // store data
791
            DB::insert(
792
                prefixTable('cache'),
793
                [
794
                    'id' => $record['id'],
795
                    'label' => $record['label'],
796
                    'description' => $record['description'] ?? '',
797
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
798
                    'tags' => $tags,
799
                    'id_tree' => $record['id_tree'],
800
                    'perso' => $record['perso'],
801
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
802
                    'login' => $record['login'] ?? '',
803
                    'folder' => implode(' > ', $folder),
804
                    'author' => $record['id_user'],
805
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
806
                    'timestamp' => $record['date'],
807
                ]
808
            );
809
        }
810
    }
811
}
812
813
/**
814
 * Cache table - update existing value.
815
 *
816
 * @param int    $ident    Ident format
817
 * 
818
 * @return void
819
 */
820
function cacheTableUpdate(?int $ident = null): void
821
{
822
    $session = SessionManager::getSession();
823
    loadClasses('DB');
824
825
    //Load Tree
826
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
827
    // get new value from db
828
    $data = DB::queryfirstrow(
829
        'SELECT label, description, id_tree, perso, restricted_to, login, url
830
        FROM ' . prefixTable('items') . '
831
        WHERE id=%i',
832
        $ident
833
    );
834
    // Get all TAGS
835
    $tags = '';
836
    $itemTags = DB::query(
837
        'SELECT tag
838
            FROM ' . prefixTable('tags') . '
839
            WHERE item_id = %i AND tag != ""',
840
        $ident
841
    );
842
    foreach ($itemTags as $itemTag) {
843
        $tags .= $itemTag['tag'] . ' ';
844
    }
845
    // form id_tree to full foldername
846
    $folder = [];
847
    $arbo = $tree->getPath($data['id_tree'], true);
848
    foreach ($arbo as $elem) {
849
        // Check if title is the ID of a user
850
        if (is_numeric($elem->title) === true) {
851
            // Is this a User id?
852
            $user = DB::queryfirstrow(
853
                'SELECT id, login
854
                FROM ' . prefixTable('users') . '
855
                WHERE id = %i',
856
                $elem->title
857
            );
858
            if (count($user) > 0) {
859
                $elem->title = $user['login'];
860
            }
861
        }
862
        // Build path
863
        array_push($folder, stripslashes($elem->title));
864
    }
865
    // finaly update
866
    DB::update(
867
        prefixTable('cache'),
868
        [
869
            'label' => $data['label'],
870
            'description' => $data['description'],
871
            'tags' => $tags,
872
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
873
            'id_tree' => $data['id_tree'],
874
            'perso' => $data['perso'],
875
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
876
            'login' => $data['login'] ?? '',
877
            'folder' => implode(' » ', $folder),
878
            'author' => $session->get('user-id'),
879
        ],
880
        'id = %i',
881
        $ident
882
    );
883
}
884
885
/**
886
 * Cache table - add new value.
887
 *
888
 * @param int    $ident    Ident format
889
 * 
890
 * @return void
891
 */
892
function cacheTableAdd(?int $ident = null): void
893
{
894
    $session = SessionManager::getSession();
895
    $globalsUserId = $session->get('user-id');
896
897
    // Load class DB
898
    loadClasses('DB');
899
900
    //Load Tree
901
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
902
    // get new value from db
903
    $data = DB::queryFirstRow(
904
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
905
        FROM ' . prefixTable('items') . ' as i
906
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
907
        WHERE i.id = %i
908
        AND l.action = %s',
909
        $ident,
910
        'at_creation'
911
    );
912
    // Get all TAGS
913
    $tags = '';
914
    $itemTags = DB::query(
915
        'SELECT tag
916
            FROM ' . prefixTable('tags') . '
917
            WHERE item_id = %i AND tag != ""',
918
        $ident
919
    );
920
    foreach ($itemTags as $itemTag) {
921
        $tags .= $itemTag['tag'] . ' ';
922
    }
923
    // form id_tree to full foldername
924
    $folder = [];
925
    $arbo = $tree->getPath($data['id_tree'], true);
926
    foreach ($arbo as $elem) {
927
        // Check if title is the ID of a user
928
        if (is_numeric($elem->title) === true) {
929
            // Is this a User id?
930
            $user = DB::queryfirstrow(
931
                'SELECT id, login
932
                FROM ' . prefixTable('users') . '
933
                WHERE id = %i',
934
                $elem->title
935
            );
936
            if (count($user) > 0) {
937
                $elem->title = $user['login'];
938
            }
939
        }
940
        // Build path
941
        array_push($folder, stripslashes($elem->title));
942
    }
943
    // finaly update
944
    DB::insert(
945
        prefixTable('cache'),
946
        [
947
            'id' => $data['id'],
948
            'label' => $data['label'],
949
            'description' => $data['description'],
950
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
951
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
952
            'id_tree' => $data['id_tree'],
953
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
954
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
955
            'login' => $data['login'] ?? '',
956
            'folder' => implode(' » ', $folder),
957
            'author' => $globalsUserId,
958
            'timestamp' => $data['date'],
959
        ]
960
    );
961
}
962
963
/**
964
 * Do statistics.
965
 *
966
 * @param array $SETTINGS Teampass settings
967
 *
968
 * @return array
969
 */
970
function getStatisticsData(array $SETTINGS): array
971
{
972
    DB::query(
973
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
974
        0
975
    );
976
    $counter_folders = DB::count();
977
    DB::query(
978
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
979
        1
980
    );
981
    $counter_folders_perso = DB::count();
982
    DB::query(
983
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
984
        0
985
    );
986
    $counter_items = DB::count();
987
        DB::query(
988
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
989
        1
990
    );
991
    $counter_items_perso = DB::count();
992
        DB::query(
993
        'SELECT id FROM ' . prefixTable('users') . ' WHERE login NOT IN (%s, %s, %s)',
994
        'OTV', 'TP', 'API'
995
    );
996
    $counter_users = DB::count();
997
        DB::query(
998
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
999
        1
1000
    );
1001
    $admins = DB::count();
1002
    DB::query(
1003
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1004
        1
1005
    );
1006
    $managers = DB::count();
1007
    DB::query(
1008
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1009
        1
1010
    );
1011
    $readOnly = DB::count();
1012
    // list the languages
1013
    $usedLang = [];
1014
    $tp_languages = DB::query(
1015
        'SELECT name FROM ' . prefixTable('languages')
1016
    );
1017
    foreach ($tp_languages as $tp_language) {
1018
        DB::query(
1019
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1020
            $tp_language['name']
1021
        );
1022
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1023
    }
1024
1025
    // get list of ips
1026
    $usedIp = [];
1027
    $tp_ips = DB::query(
1028
        'SELECT user_ip FROM ' . prefixTable('users')
1029
    );
1030
    foreach ($tp_ips as $ip) {
1031
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1032
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1033
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1034
            $usedIp[$ip['user_ip']] = 1;
1035
        }
1036
    }
1037
1038
    return [
1039
        'error' => '',
1040
        'stat_phpversion' => phpversion(),
1041
        'stat_folders' => $counter_folders,
1042
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1043
        'stat_items' => $counter_items,
1044
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1045
        'stat_users' => $counter_users,
1046
        'stat_admins' => $admins,
1047
        'stat_managers' => $managers,
1048
        'stat_ro' => $readOnly,
1049
        'stat_kb' => $SETTINGS['enable_kb'],
1050
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1051
        'stat_fav' => $SETTINGS['enable_favourites'],
1052
        'stat_teampassversion' => TP_VERSION,
1053
        'stat_ldap' => $SETTINGS['ldap_mode'],
1054
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1055
        'stat_duo' => $SETTINGS['duo'],
1056
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1057
        'stat_api' => $SETTINGS['api'],
1058
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1059
        'stat_syslog' => $SETTINGS['syslog_enable'],
1060
        'stat_2fa' => $SETTINGS['google_authentication'],
1061
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1062
        'stat_mysqlversion' => DB::serverVersion(),
1063
        'stat_languages' => $usedLang,
1064
        'stat_country' => $usedIp,
1065
    ];
1066
}
1067
1068
/**
1069
 * Permits to prepare the way to send the email
1070
 * 
1071
 * @param string $subject       email subject
1072
 * @param string $body          email message
1073
 * @param string $email         email
1074
 * @param string $receiverName  Receiver name
1075
 * @param string $encryptedUserPassword      encryptedUserPassword
1076
 *
1077
 * @return void
1078
 */
1079
function prepareSendingEmail(
1080
    $subject,
1081
    $body,
1082
    $email,
1083
    $receiverName = '',
1084
    $encryptedUserPassword = ''
1085
): void 
1086
{
1087
    DB::insert(
1088
        prefixTable('background_tasks'),
1089
        array(
1090
            'created_at' => time(),
1091
            'process_type' => 'send_email',
1092
            'arguments' => json_encode([
1093
                'subject' => $subject,
1094
                'receivers' => $email,
1095
                'body' => $body,
1096
                'receiver_name' => $receiverName,
1097
                'encryptedUserPassword' => $encryptedUserPassword,
1098
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1099
        )
1100
    );
1101
}
1102
1103
/**
1104
 * Returns the email body.
1105
 *
1106
 * @param string $textMail Text for the email
1107
 */
1108
function emailBody(string $textMail): string
1109
{
1110
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1111
    w3.org/TR/html4/loose.dtd"><html>
1112
    <head><title>Email Template</title>
1113
    <style type="text/css">
1114
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1115
    </style></head>
1116
    <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">
1117
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1118
    <tr><td style="border-collapse: collapse;"><br>
1119
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1120
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1121
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1122
        </td></tr></table></td>
1123
    </tr>
1124
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1125
        <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;">
1126
        <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;">
1127
        <br><div style="float:right;">' .
1128
        $textMail .
1129
        '<br><br></td></tr></table>
1130
    </td></tr></table>
1131
    <br></body></html>';
1132
}
1133
1134
/**
1135
 * Convert date to timestamp.
1136
 *
1137
 * @param string $date        The date
1138
 * @param string $date_format Date format
1139
 *
1140
 * @return int
1141
 */
1142
function dateToStamp(string $date, string $date_format): int
1143
{
1144
    $date = date_parse_from_format($date_format, $date);
1145
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1146
        return mktime(
1147
            empty($date['hour']) === false ? $date['hour'] : 23,
1148
            empty($date['minute']) === false ? $date['minute'] : 59,
1149
            empty($date['second']) === false ? $date['second'] : 59,
1150
            $date['month'],
1151
            $date['day'],
1152
            $date['year']
1153
        );
1154
    }
1155
    return 0;
1156
}
1157
1158
/**
1159
 * Is this a date.
1160
 *
1161
 * @param string $date Date
1162
 *
1163
 * @return bool
1164
 */
1165
function isDate(string $date): bool
1166
{
1167
    return strtotime($date) !== false;
1168
}
1169
1170
/**
1171
 * Check if isUTF8().
1172
 *
1173
 * @param string|array $string Is the string
1174
 *
1175
 * @return int is the string in UTF8 format
1176
 */
1177
function isUTF8($string): int
1178
{
1179
    if (is_array($string) === true) {
1180
        $string = $string['string'];
1181
    }
1182
1183
    return preg_match(
1184
        '%^(?:
1185
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1186
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1187
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1188
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1189
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1190
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1191
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1192
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1193
        )*$%xs',
1194
        $string
1195
    );
1196
}
1197
1198
/**
1199
 * Prepare an array to UTF8 format before JSON_encode.
1200
 *
1201
 * @param array $array Array of values
1202
 *
1203
 * @return array
1204
 */
1205
function utf8Converter(array $array): array
1206
{
1207
    array_walk_recursive(
1208
        $array,
1209
        static function (&$item): void {
1210
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1211
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1212
            }
1213
        }
1214
    );
1215
    return $array;
1216
}
1217
1218
/**
1219
 * Permits to prepare data to be exchanged.
1220
 *
1221
 * @param array|string $data Text
1222
 * @param string       $type Parameter
1223
 * @param string       $key  Optional key
1224
 *
1225
 * @return string|array
1226
 */
1227
function prepareExchangedData($data, string $type, ?string $key = null)
1228
{
1229
    $session = SessionManager::getSession();
1230
    $key = empty($key) ? $session->get('key') : $key;
1231
    
1232
    // Perform
1233
    if ($type === 'encode' && is_array($data) === true) {
1234
        // json encoding
1235
        $data = json_encode(
1236
            $data,
1237
            JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1238
        );
1239
1240
        // Now encrypt
1241
        if ($session->get('encryptClientServer') === 1) {
1242
            $data = Encryption::encrypt(
1243
                $data,
1244
                $key
1245
            );
1246
        }
1247
1248
        return $data;
1249
    }
1250
1251
    if ($type === 'decode' && is_array($data) === false) {
1252
        // Decrypt if needed
1253
        if ($session->get('encryptClientServer') === 1) {
1254
            $data = (string) Encryption::decrypt(
1255
                (string) $data,
1256
                $key
1257
            );
1258
        } else {
1259
            // Double html encoding received
1260
            $data = html_entity_decode(html_entity_decode(/** @scrutinizer ignore-type */$data)); // @codeCoverageIgnore Is always a string (not an array)
1261
        }
1262
1263
        // Check if $data is a valid string before json_decode
1264
        if (is_string($data) && !empty($data)) {
1265
            // Return data array
1266
            return json_decode($data, true);
1267
        }
1268
    }
1269
1270
    return '';
1271
}
1272
1273
1274
/**
1275
 * Create a thumbnail.
1276
 *
1277
 * @param string  $src           Source
1278
 * @param string  $dest          Destination
1279
 * @param int $desired_width Size of width
1280
 * 
1281
 * @return void|string|bool
1282
 */
1283
function makeThumbnail(string $src, string $dest, int $desired_width)
1284
{
1285
    /* read the source image */
1286
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1287
        $source_image = imagecreatefrompng($src);
1288
        if ($source_image === false) {
1289
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1290
        }
1291
    } else {
1292
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1293
    }
1294
1295
    // Get height and width
1296
    $width = imagesx($source_image);
1297
    $height = imagesy($source_image);
1298
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1299
    $desired_height = (int) floor($height * $desired_width / $width);
1300
    /* create a new, "virtual" image */
1301
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1302
    if ($virtual_image === false) {
1303
        return false;
1304
    }
1305
    /* copy source image at a resized size */
1306
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1307
    /* create the physical thumbnail image to its destination */
1308
    imagejpeg($virtual_image, $dest);
1309
}
1310
1311
/**
1312
 * Check table prefix in SQL query.
1313
 *
1314
 * @param string $table Table name
1315
 * 
1316
 * @return string
1317
 */
1318
function prefixTable(string $table): string
1319
{
1320
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1321
    if (empty($safeTable) === false) {
1322
        // sanitize string
1323
        return $safeTable;
1324
    }
1325
    // stop error no table
1326
    return 'table_not_exists';
1327
}
1328
1329
/**
1330
 * GenerateCryptKey
1331
 *
1332
 * @param int     $size      Length
1333
 * @param bool $secure Secure
1334
 * @param bool $numerals Numerics
1335
 * @param bool $uppercase Uppercase letters
1336
 * @param bool $symbols Symbols
1337
 * @param bool $lowercase Lowercase
1338
 * 
1339
 * @return string
1340
 */
1341
function GenerateCryptKey(
1342
    int $size = 20,
1343
    bool $secure = false,
1344
    bool $numerals = false,
1345
    bool $uppercase = false,
1346
    bool $symbols = false,
1347
    bool $lowercase = false
1348
): string {
1349
    $generator = new ComputerPasswordGenerator();
1350
    $generator->setRandomGenerator(new Php7RandomGenerator());
1351
    
1352
    // Manage size
1353
    $generator->setLength((int) $size);
1354
    if ($secure === true) {
1355
        $generator->setSymbols(true);
1356
        $generator->setLowercase(true);
1357
        $generator->setUppercase(true);
1358
        $generator->setNumbers(true);
1359
    } else {
1360
        $generator->setLowercase($lowercase);
1361
        $generator->setUppercase($uppercase);
1362
        $generator->setNumbers($numerals);
1363
        $generator->setSymbols($symbols);
1364
    }
1365
1366
    return $generator->generatePasswords()[0];
1367
}
1368
1369
/**
1370
 * GenerateGenericPassword
1371
 *
1372
 * @param int     $size      Length
1373
 * @param bool $secure Secure
1374
 * @param bool $numerals Numerics
1375
 * @param bool $uppercase Uppercase letters
1376
 * @param bool $symbols Symbols
1377
 * @param bool $lowercase Lowercase
1378
 * @param array   $SETTINGS  SETTINGS
1379
 * 
1380
 * @return string
1381
 */
1382
function generateGenericPassword(
1383
    int $size,
1384
    bool $secure,
1385
    bool $lowercase,
1386
    bool $capitalize,
1387
    bool $numerals,
1388
    bool $symbols,
1389
    array $SETTINGS
1390
): string
1391
{
1392
    if ((int) $size > (int) $SETTINGS['pwd_maximum_length']) {
1393
        return prepareExchangedData(
1394
            array(
1395
                'error_msg' => 'Password length is too long! ',
1396
                'error' => 'true',
1397
            ),
1398
            'encode'
1399
        );
1400
    }
1401
    // Load libraries
1402
    $generator = new ComputerPasswordGenerator();
1403
    $generator->setRandomGenerator(new Php7RandomGenerator());
1404
1405
    // Manage size
1406
    $generator->setLength(($size <= 0) ? 10 : $size);
1407
1408
    if ($secure === true) {
1409
        $generator->setSymbols(true);
1410
        $generator->setLowercase(true);
1411
        $generator->setUppercase(true);
1412
        $generator->setNumbers(true);
1413
    } else {
1414
        $generator->setLowercase($lowercase);
1415
        $generator->setUppercase($capitalize);
1416
        $generator->setNumbers($numerals);
1417
        $generator->setSymbols($symbols);
1418
    }
1419
1420
    return prepareExchangedData(
1421
        array(
1422
            'key' => $generator->generatePasswords(),
1423
            'error' => '',
1424
        ),
1425
        'encode'
1426
    );
1427
}
1428
1429
/**
1430
 * Send sysLOG message
1431
 *
1432
 * @param string    $message
1433
 * @param string    $host
1434
 * @param int       $port
1435
 * @param string    $component
1436
 * 
1437
 * @return void
1438
*/
1439
function send_syslog($message, $host, $port, $component = 'teampass'): void
1440
{
1441
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1442
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1443
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1444
    socket_close($sock);
1445
}
1446
1447
/**
1448
 * Permits to log events into DB
1449
 *
1450
 * @param array  $SETTINGS Teampass settings
1451
 * @param string $type     Type
1452
 * @param string $label    Label
1453
 * @param string $who      Who
1454
 * @param string $login    Login
1455
 * @param string|int $field_1  Field
1456
 * 
1457
 * @return void
1458
 */
1459
function logEvents(
1460
    array $SETTINGS, 
1461
    string $type, 
1462
    string $label, 
1463
    string $who, 
1464
    ?string $login = null, 
1465
    $field_1 = null
1466
): void
1467
{
1468
    if (empty($who)) {
1469
        $who = getClientIpServer();
1470
    }
1471
1472
    // Load class DB
1473
    loadClasses('DB');
1474
1475
    DB::insert(
1476
        prefixTable('log_system'),
1477
        [
1478
            'type' => $type,
1479
            'date' => time(),
1480
            'label' => $label,
1481
            'qui' => $who,
1482
            'field_1' => $field_1 === null ? '' : $field_1,
1483
        ]
1484
    );
1485
    // If SYSLOG
1486
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1487
        if ($type === 'user_mngt') {
1488
            send_syslog(
1489
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1490
                $SETTINGS['syslog_host'],
1491
                $SETTINGS['syslog_port'],
1492
                'teampass'
1493
            );
1494
        } else {
1495
            send_syslog(
1496
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1497
                $SETTINGS['syslog_host'],
1498
                $SETTINGS['syslog_port'],
1499
                'teampass'
1500
            );
1501
        }
1502
    }
1503
}
1504
1505
/**
1506
 * Log events.
1507
 *
1508
 * @param array  $SETTINGS        Teampass settings
1509
 * @param int    $item_id         Item id
1510
 * @param string $item_label      Item label
1511
 * @param int    $id_user         User id
1512
 * @param string $action          Code for reason
1513
 * @param string $login           User login
1514
 * @param string $raison          Code for reason
1515
 * @param string $encryption_type Encryption on
1516
 * @param string $time Encryption Time
1517
 * @param string $old_value       Old value
1518
 * 
1519
 * @return void
1520
 */
1521
function logItems(
1522
    array $SETTINGS,
1523
    int $item_id,
1524
    string $item_label,
1525
    int $id_user,
1526
    string $action,
1527
    ?string $login = null,
1528
    ?string $raison = null,
1529
    ?string $encryption_type = null,
1530
    ?string $time = null,
1531
    ?string $old_value = null
1532
): void {
1533
    // Load class DB
1534
    loadClasses('DB');
1535
1536
    // Insert log in DB
1537
    DB::insert(
1538
        prefixTable('log_items'),
1539
        [
1540
            'id_item' => $item_id,
1541
            'date' => is_null($time) === true ? time() : $time,
1542
            'id_user' => $id_user,
1543
            'action' => $action,
1544
            'raison' => $raison,
1545
            'old_value' => $old_value,
1546
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1547
        ]
1548
    );
1549
    // Timestamp the last change
1550
    if (in_array($action, ['at_creation', 'at_modifiation', 'at_delete', 'at_import'], true)) {
1551
        DB::update(
1552
            prefixTable('misc'),
1553
            [
1554
                'valeur' => time(),
1555
                'updated_at' => time(),
1556
            ],
1557
            'type = %s AND intitule = %s',
1558
            'timestamp',
1559
            'last_item_change'
1560
        );
1561
    }
1562
1563
    // SYSLOG
1564
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1565
        // Extract reason
1566
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1567
        // Get item info if not known
1568
        if (empty($item_label) === true) {
1569
            $dataItem = DB::queryfirstrow(
1570
                'SELECT id, id_tree, label
1571
                FROM ' . prefixTable('items') . '
1572
                WHERE id = %i',
1573
                $item_id
1574
            );
1575
            $item_label = $dataItem['label'];
1576
        }
1577
1578
        send_syslog(
1579
            'action=' . str_replace('at_', '', $action) .
1580
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1581
                ' itemno=' . $item_id .
1582
                ' user=' . (is_null($login) === true ? '' : addslashes((string) $login)) .
1583
                ' itemname="' . addslashes($item_label) . '"',
1584
            $SETTINGS['syslog_host'],
1585
            $SETTINGS['syslog_port'],
1586
            'teampass'
1587
        );
1588
    }
1589
1590
    // send notification if enabled
1591
    //notifyOnChange($item_id, $action, $SETTINGS);
1592
}
1593
1594
/**
1595
 * Prepare notification email to subscribers.
1596
 *
1597
 * @param int    $item_id  Item id
1598
 * @param string $label    Item label
1599
 * @param array  $changes  List of changes
1600
 * @param array  $SETTINGS Teampass settings
1601
 * 
1602
 * @return void
1603
 */
1604
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1605
{
1606
    $session = SessionManager::getSession();
1607
    $lang = new Language($session->get('user-language') ?? 'english');
1608
    $globalsUserId = $session->get('user-id');
1609
    $globalsLastname = $session->get('user-lastname');
1610
    $globalsName = $session->get('user-name');
1611
    // send email to user that what to be notified
1612
    $notification = DB::queryOneColumn(
1613
        'email',
1614
        'SELECT *
1615
        FROM ' . prefixTable('notification') . ' AS n
1616
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1617
        WHERE n.item_id = %i AND n.user_id != %i',
1618
        $item_id,
1619
        $globalsUserId
1620
    );
1621
    if (DB::count() > 0) {
1622
        // Prepare path
1623
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1624
        // Get list of changes
1625
        $htmlChanges = '<ul>';
1626
        foreach ($changes as $change) {
1627
            $htmlChanges .= '<li>' . $change . '</li>';
1628
        }
1629
        $htmlChanges .= '</ul>';
1630
        // send email
1631
        DB::insert(
1632
            prefixTable('emails'),
1633
            [
1634
                'timestamp' => time(),
1635
                'subject' => $lang->get('email_subject_item_updated'),
1636
                'body' => str_replace(
1637
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1638
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1639
                    $lang->get('email_body_item_updated')
1640
                ),
1641
                'receivers' => implode(',', $notification),
1642
                'status' => '',
1643
            ]
1644
        );
1645
    }
1646
}
1647
1648
/**
1649
 * Returns the Item + path.
1650
 *
1651
 * @param int    $id_tree  Node id
1652
 * @param string $label    Label
1653
 * @param array  $SETTINGS TP settings
1654
 * 
1655
 * @return string
1656
 */
1657
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1658
{
1659
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1660
    $arbo = $tree->getPath($id_tree, true);
1661
    $path = '';
1662
    foreach ($arbo as $elem) {
1663
        if (empty($path) === true) {
1664
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1665
        } else {
1666
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1667
        }
1668
    }
1669
1670
    // Build text to show user
1671
    if (empty($label) === false) {
1672
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1673
    }
1674
    return empty($path) === true ? '' : $path;
1675
}
1676
1677
/**
1678
 * Get the client ip address.
1679
 *
1680
 * @return string IP address
1681
 */
1682
function getClientIpServer(): string
1683
{
1684
    if (getenv('HTTP_CLIENT_IP')) {
1685
        $ipaddress = getenv('HTTP_CLIENT_IP');
1686
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1687
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1688
    } elseif (getenv('HTTP_X_FORWARDED')) {
1689
        $ipaddress = getenv('HTTP_X_FORWARDED');
1690
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1691
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1692
    } elseif (getenv('HTTP_FORWARDED')) {
1693
        $ipaddress = getenv('HTTP_FORWARDED');
1694
    } elseif (getenv('REMOTE_ADDR')) {
1695
        $ipaddress = getenv('REMOTE_ADDR');
1696
    } else {
1697
        $ipaddress = 'UNKNOWN';
1698
    }
1699
1700
    return $ipaddress;
1701
}
1702
1703
/**
1704
 * Escape all HTML, JavaScript, and CSS.
1705
 *
1706
 * @param string $input    The input string
1707
 * @param string $encoding Which character encoding are we using?
1708
 * 
1709
 * @return string
1710
 */
1711
function noHTML(string $input, string $encoding = 'UTF-8'): string
1712
{
1713
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1714
}
1715
1716
/**
1717
 * Rebuilds the Teampass config file.
1718
 *
1719
 * @param string $configFilePath Path to the config file.
1720
 * @param array  $settings       Teampass settings.
1721
 *
1722
 * @return string|bool
1723
 */
1724
function rebuildConfigFile(string $configFilePath, array $settings)
1725
{
1726
    // Perform a copy if the file exists
1727
    if (file_exists($configFilePath)) {
1728
        $backupFilePath = $configFilePath . '.' . date('Y_m_d_His', time());
1729
        if (!copy($configFilePath, $backupFilePath)) {
1730
            return "ERROR: Could not copy file '$configFilePath'";
1731
        }
1732
    }
1733
1734
    // Regenerate the config file
1735
    $data = ["<?php\n", "global \$SETTINGS;\n", "\$SETTINGS = array (\n"];
1736
    $rows = DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s', 'admin');
1737
    foreach ($rows as $record) {
1738
        $value = getEncryptedValue($record['valeur'], $record['is_encrypted']);
1739
        $data[] = "    '{$record['intitule']}' => '". htmlspecialchars_decode($value, ENT_COMPAT) . "',\n";
1740
    }
1741
    $data[] = ");\n";
1742
    $data = array_unique($data);
1743
1744
    // Update the file
1745
    file_put_contents($configFilePath, implode('', $data));
1746
1747
    return true;
1748
}
1749
1750
/**
1751
 * Returns the encrypted value if needed.
1752
 *
1753
 * @param string $value       Value to encrypt.
1754
 * @param int   $isEncrypted Is the value encrypted?
1755
 *
1756
 * @return string
1757
 */
1758
function getEncryptedValue(string $value, int $isEncrypted): string
1759
{
1760
    return $isEncrypted ? cryption($value, '', 'encrypt')['string'] : $value;
1761
}
1762
1763
/**
1764
 * Permits to replace &#92; to permit correct display
1765
 *
1766
 * @param string $input Some text
1767
 * 
1768
 * @return string
1769
 */
1770
function handleBackslash(string $input): string
1771
{
1772
    return str_replace('&amp;#92;', '&#92;', $input);
1773
}
1774
1775
/**
1776
 * Permits to load settings
1777
 * 
1778
 * @return void
1779
*/
1780
function loadSettings(): void
1781
{
1782
    global $SETTINGS;
1783
    /* LOAD CPASSMAN SETTINGS */
1784
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1785
        $SETTINGS = [];
1786
        $SETTINGS['duplicate_folder'] = 0;
1787
        //by default, this is set to 0;
1788
        $SETTINGS['duplicate_item'] = 0;
1789
        //by default, this is set to 0;
1790
        $SETTINGS['number_of_used_pw'] = 5;
1791
        //by default, this value is set to 5;
1792
        $settings = [];
1793
        $rows = DB::query(
1794
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1795
            [
1796
                'type' => 'admin',
1797
                'type2' => 'settings',
1798
            ]
1799
        );
1800
        foreach ($rows as $record) {
1801
            if ($record['type'] === 'admin') {
1802
                $SETTINGS[$record['intitule']] = $record['valeur'];
1803
            } else {
1804
                $settings[$record['intitule']] = $record['valeur'];
1805
            }
1806
        }
1807
        $SETTINGS['loaded'] = 1;
1808
        $SETTINGS['default_session_expiration_time'] = 5;
1809
    }
1810
}
1811
1812
/**
1813
 * check if folder has custom fields.
1814
 * Ensure that target one also has same custom fields
1815
 * 
1816
 * @param int $source_id
1817
 * @param int $target_id 
1818
 * 
1819
 * @return bool
1820
*/
1821
function checkCFconsistency(int $source_id, int $target_id): bool
1822
{
1823
    $source_cf = [];
1824
    $rows = DB::QUERY(
1825
        'SELECT id_category
1826
            FROM ' . prefixTable('categories_folders') . '
1827
            WHERE id_folder = %i',
1828
        $source_id
1829
    );
1830
    foreach ($rows as $record) {
1831
        array_push($source_cf, $record['id_category']);
1832
    }
1833
1834
    $target_cf = [];
1835
    $rows = DB::QUERY(
1836
        'SELECT id_category
1837
            FROM ' . prefixTable('categories_folders') . '
1838
            WHERE id_folder = %i',
1839
        $target_id
1840
    );
1841
    foreach ($rows as $record) {
1842
        array_push($target_cf, $record['id_category']);
1843
    }
1844
1845
    $cf_diff = array_diff($source_cf, $target_cf);
1846
    if (count($cf_diff) > 0) {
1847
        return false;
1848
    }
1849
1850
    return true;
1851
}
1852
1853
/**
1854
 * Will encrypte/decrypt a fil eusing Defuse.
1855
 *
1856
 * @param string $type        can be either encrypt or decrypt
1857
 * @param string $source_file path to source file
1858
 * @param string $target_file path to target file
1859
 * @param array  $SETTINGS    Settings
1860
 * @param string $password    A password
1861
 *
1862
 * @return string|bool
1863
 */
1864
function prepareFileWithDefuse(
1865
    string $type,
1866
    string $source_file,
1867
    string $target_file,
1868
    array $SETTINGS,
1869
    string $password = null
1870
) {
1871
    // Load AntiXSS
1872
    $antiXss = new AntiXSS();
1873
    // Protect against bad inputs
1874
    if (is_array($source_file) === true || is_array($target_file) === true) {
1875
        return 'error_cannot_be_array';
1876
    }
1877
1878
    // Sanitize
1879
    $source_file = $antiXss->xss_clean($source_file);
1880
    $target_file = $antiXss->xss_clean($target_file);
1881
    if (empty($password) === true || is_null($password) === true) {
1882
        // get KEY to define password
1883
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
1884
        $password = Key::loadFromAsciiSafeString($ascii_key);
1885
    }
1886
1887
    $err = '';
1888
    if ($type === 'decrypt') {
1889
        // Decrypt file
1890
        $err = defuseFileDecrypt(
1891
            $source_file,
1892
            $target_file,
1893
            $SETTINGS, /** @scrutinizer ignore-type */
1894
            $password
1895
        );
1896
    } elseif ($type === 'encrypt') {
1897
        // Encrypt file
1898
        $err = defuseFileEncrypt(
1899
            $source_file,
1900
            $target_file,
1901
            $SETTINGS, /** @scrutinizer ignore-type */
1902
            $password
1903
        );
1904
    }
1905
1906
    // return error
1907
    return $err === true ? $err : '';
1908
}
1909
1910
/**
1911
 * Encrypt a file with Defuse.
1912
 *
1913
 * @param string $source_file path to source file
1914
 * @param string $target_file path to target file
1915
 * @param array  $SETTINGS    Settings
1916
 * @param string $password    A password
1917
 *
1918
 * @return string|bool
1919
 */
1920
function defuseFileEncrypt(
1921
    string $source_file,
1922
    string $target_file,
1923
    array $SETTINGS,
1924
    string $password = null
1925
) {
1926
    try {
1927
        CryptoFile::encryptFileWithPassword(
1928
            $source_file,
1929
            $target_file,
1930
            $password
1931
        );
1932
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1933
        $err = 'wrong_key';
1934
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1935
        $err = $ex;
1936
    } catch (CryptoException\IOException $ex) {
1937
        $err = $ex;
1938
    }
1939
1940
    // return error
1941
    return empty($err) === false ? $err : true;
1942
}
1943
1944
/**
1945
 * Decrypt a file with Defuse.
1946
 *
1947
 * @param string $source_file path to source file
1948
 * @param string $target_file path to target file
1949
 * @param array  $SETTINGS    Settings
1950
 * @param string $password    A password
1951
 *
1952
 * @return string|bool
1953
 */
1954
function defuseFileDecrypt(
1955
    string $source_file,
1956
    string $target_file,
1957
    array $SETTINGS,
1958
    string $password = null
1959
) {
1960
    try {
1961
        CryptoFile::decryptFileWithPassword(
1962
            $source_file,
1963
            $target_file,
1964
            $password
1965
        );
1966
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1967
        $err = 'wrong_key';
1968
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1969
        $err = $ex;
1970
    } catch (CryptoException\IOException $ex) {
1971
        $err = $ex;
1972
    }
1973
1974
    // return error
1975
    return empty($err) === false ? $err : true;
1976
}
1977
1978
/*
1979
* NOT TO BE USED
1980
*/
1981
/**
1982
 * Undocumented function.
1983
 *
1984
 * @param string $text Text to debug
1985
 */
1986
function debugTeampass(string $text): void
1987
{
1988
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
1989
    if ($debugFile !== false) {
1990
        fputs($debugFile, $text);
1991
        fclose($debugFile);
1992
    }
1993
}
1994
1995
/**
1996
 * DELETE the file with expected command depending on server type.
1997
 *
1998
 * @param string $file     Path to file
1999
 * @param array  $SETTINGS Teampass settings
2000
 *
2001
 * @return void
2002
 */
2003
function fileDelete(string $file, array $SETTINGS): void
2004
{
2005
    // Load AntiXSS
2006
    $antiXss = new AntiXSS();
2007
    $file = $antiXss->xss_clean($file);
2008
    if (is_file($file)) {
2009
        unlink($file);
2010
    }
2011
}
2012
2013
/**
2014
 * Permits to extract the file extension.
2015
 *
2016
 * @param string $file File name
2017
 *
2018
 * @return string
2019
 */
2020
function getFileExtension(string $file): string
2021
{
2022
    if (strpos($file, '.') === false) {
2023
        return $file;
2024
    }
2025
2026
    return substr($file, strrpos($file, '.') + 1);
2027
}
2028
2029
/**
2030
 * Chmods files and folders with different permissions.
2031
 *
2032
 * This is an all-PHP alternative to using: \n
2033
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2034
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2035
 *
2036
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2037
  *
2038
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2039
 * @param int    $filePerm The permissions any found files should get.
2040
 * @param int    $dirPerm  The permissions any found folder should get.
2041
 *
2042
 * @return bool Returns TRUE if the path if found and FALSE if not.
2043
 *
2044
 * @warning The permission levels has to be entered in octal format, which
2045
 * normally means adding a zero ("0") in front of the permission level. \n
2046
 * More info at: http://php.net/chmod.
2047
*/
2048
2049
function recursiveChmod(
2050
    string $path,
2051
    int $filePerm = 0644,
2052
    int  $dirPerm = 0755
2053
) {
2054
    // Check if the path exists
2055
    $path = basename($path);
2056
    if (! file_exists($path)) {
2057
        return false;
2058
    }
2059
2060
    // See whether this is a file
2061
    if (is_file($path)) {
2062
        // Chmod the file with our given filepermissions
2063
        try {
2064
            chmod($path, $filePerm);
2065
        } catch (Exception $e) {
2066
            return false;
2067
        }
2068
    // If this is a directory...
2069
    } elseif (is_dir($path)) {
2070
        // Then get an array of the contents
2071
        $foldersAndFiles = scandir($path);
2072
        // Remove "." and ".." from the list
2073
        $entries = array_slice($foldersAndFiles, 2);
2074
        // Parse every result...
2075
        foreach ($entries as $entry) {
2076
            // And call this function again recursively, with the same permissions
2077
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2078
        }
2079
2080
        // When we are done with the contents of the directory, we chmod the directory itself
2081
        try {
2082
            chmod($path, $filePerm);
2083
        } catch (Exception $e) {
2084
            return false;
2085
        }
2086
    }
2087
2088
    // Everything seemed to work out well, return true
2089
    return true;
2090
}
2091
2092
/**
2093
 * Check if user can access to this item.
2094
 *
2095
 * @param int   $item_id ID of item
2096
 * @param array $SETTINGS
2097
 *
2098
 * @return bool|string
2099
 */
2100
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2101
{
2102
    
2103
    $session = SessionManager::getSession();
2104
    $session_groupes_visibles = $session->get('user-accessible_folders');
2105
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2106
    // Load item data
2107
    $data = DB::queryFirstRow(
2108
        'SELECT id_tree
2109
        FROM ' . prefixTable('items') . '
2110
        WHERE id = %i',
2111
        $item_id
2112
    );
2113
    // Check if user can access this folder
2114
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2115
        // Now check if this folder is restricted to user
2116
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2117
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2118
        ) {
2119
            return 'ERR_FOLDER_NOT_ALLOWED';
2120
        }
2121
    }
2122
2123
    return true;
2124
}
2125
2126
/**
2127
 * Creates a unique key.
2128
 *
2129
 * @param int $lenght Key lenght
2130
 *
2131
 * @return string
2132
 */
2133
function uniqidReal(int $lenght = 13): string
2134
{
2135
    if (function_exists('random_bytes')) {
2136
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2137
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2138
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2139
    } else {
2140
        throw new Exception('no cryptographically secure random function available');
2141
    }
2142
2143
    return substr(bin2hex($bytes), 0, $lenght);
2144
}
2145
2146
/**
2147
 * Obfuscate an email.
2148
 *
2149
 * @param string $email Email address
2150
 *
2151
 * @return string
2152
 */
2153
function obfuscateEmail(string $email): string
2154
{
2155
    $email = explode("@", $email);
2156
    $name = $email[0];
2157
    if (strlen($name) > 3) {
2158
        $name = substr($name, 0, 2);
2159
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2160
            $name .= "*";
2161
        }
2162
        $name .= substr($email[0], -1, 1);
2163
    }
2164
    $host = explode(".", $email[1])[0];
2165
    if (strlen($host) > 3) {
2166
        $host = substr($host, 0, 1);
2167
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2168
            $host .= "*";
2169
        }
2170
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2171
    }
2172
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2173
    return $email;
2174
}
2175
2176
/**
2177
 * Perform a Query.
2178
 *
2179
 * @param array  $SETTINGS Teamapss settings
2180
 * @param string $fields   Fields to use
2181
 * @param string $table    Table to use
2182
 *
2183
 * @return array
2184
 */
2185
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2186
{
2187
    // include librairies & connect to DB
2188
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2189
2190
    // Load class DB
2191
    loadClasses('DB');
2192
    
2193
    // Insert log in DB
2194
    return DB::query(
2195
        'SELECT ' . $fields . '
2196
        FROM ' . prefixTable($table)
2197
    );
2198
}
2199
2200
/**
2201
 * Undocumented function.
2202
 *
2203
 * @param int $bytes Size of file
2204
 *
2205
 * @return string
2206
 */
2207
function formatSizeUnits(int $bytes): string
2208
{
2209
    if ($bytes >= 1073741824) {
2210
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2211
    } elseif ($bytes >= 1048576) {
2212
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2213
    } elseif ($bytes >= 1024) {
2214
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2215
    } elseif ($bytes > 1) {
2216
        $bytes .= ' bytes';
2217
    } elseif ($bytes === 1) {
2218
        $bytes .= ' byte';
2219
    } else {
2220
        $bytes = '0 bytes';
2221
    }
2222
2223
    return $bytes;
2224
}
2225
2226
/**
2227
 * Generate user pair of keys.
2228
 *
2229
 * @param string $userPwd User password
2230
 *
2231
 * @return array
2232
 */
2233
function generateUserKeys(string $userPwd): array
2234
{
2235
    // Sanitize
2236
    $antiXss = new AntiXSS();
2237
    $userPwd = $antiXss->xss_clean($userPwd);
2238
    // Load classes
2239
    $rsa = new Crypt_RSA();
2240
    $cipher = new Crypt_AES();
2241
    // Create the private and public key
2242
    $res = $rsa->createKey(4096);
2243
    // Encrypt the privatekey
2244
    $cipher->setPassword($userPwd);
2245
    $privatekey = $cipher->encrypt($res['privatekey']);
2246
    return [
2247
        'private_key' => base64_encode($privatekey),
2248
        'public_key' => base64_encode($res['publickey']),
2249
        'private_key_clear' => base64_encode($res['privatekey']),
2250
    ];
2251
}
2252
2253
/**
2254
 * Permits to decrypt the user's privatekey.
2255
 *
2256
 * @param string $userPwd        User password
2257
 * @param string $userPrivateKey User private key
2258
 *
2259
 * @return string|object
2260
 */
2261
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2262
{
2263
    // Sanitize
2264
    $antiXss = new AntiXSS();
2265
    $userPwd = $antiXss->xss_clean($userPwd);
2266
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2267
2268
    if (empty($userPwd) === false) {
2269
        // Load classes
2270
        $cipher = new Crypt_AES();
2271
        // Encrypt the privatekey
2272
        $cipher->setPassword($userPwd);
2273
        try {
2274
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2275
        } catch (Exception $e) {
2276
            return $e;
2277
        }
2278
    }
2279
    return '';
2280
}
2281
2282
/**
2283
 * Permits to encrypt the user's privatekey.
2284
 *
2285
 * @param string $userPwd        User password
2286
 * @param string $userPrivateKey User private key
2287
 *
2288
 * @return string
2289
 */
2290
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2291
{
2292
    // Sanitize
2293
    $antiXss = new AntiXSS();
2294
    $userPwd = $antiXss->xss_clean($userPwd);
2295
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2296
2297
    if (empty($userPwd) === false) {
2298
        // Load classes
2299
        $cipher = new Crypt_AES();
2300
        // Encrypt the privatekey
2301
        $cipher->setPassword($userPwd);        
2302
        try {
2303
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2304
        } catch (Exception $e) {
2305
            return $e;
2306
        }
2307
    }
2308
    return '';
2309
}
2310
2311
/**
2312
 * Encrypts a string using AES.
2313
 *
2314
 * @param string $data String to encrypt
2315
 * @param string $key
2316
 *
2317
 * @return array
2318
 */
2319
function doDataEncryption(string $data, string $key = NULL): array
2320
{
2321
    // Sanitize
2322
    $antiXss = new AntiXSS();
2323
    $data = $antiXss->xss_clean($data);
2324
    
2325
    // Load classes
2326
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2327
    // Generate an object key
2328
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2329
    // Set it as password
2330
    $cipher->setPassword($objectKey);
2331
    return [
2332
        'encrypted' => base64_encode($cipher->encrypt($data)),
2333
        'objectKey' => base64_encode($objectKey),
2334
    ];
2335
}
2336
2337
/**
2338
 * Decrypts a string using AES.
2339
 *
2340
 * @param string $data Encrypted data
2341
 * @param string $key  Key to uncrypt
2342
 *
2343
 * @return string
2344
 */
2345
function doDataDecryption(string $data, string $key): string
2346
{
2347
    // Sanitize
2348
    $antiXss = new AntiXSS();
2349
    $data = $antiXss->xss_clean($data);
2350
    $key = $antiXss->xss_clean($key);
2351
2352
    // Load classes
2353
    $cipher = new Crypt_AES();
2354
    // Set the object key
2355
    $cipher->setPassword(base64_decode($key));
2356
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2357
}
2358
2359
/**
2360
 * Encrypts using RSA a string using a public key.
2361
 *
2362
 * @param string $key       Key to be encrypted
2363
 * @param string $publicKey User public key
2364
 *
2365
 * @return string
2366
 */
2367
function encryptUserObjectKey(string $key, string $publicKey): string
2368
{
2369
    // Sanitize
2370
    $antiXss = new AntiXSS();
2371
    $publicKey = $antiXss->xss_clean($publicKey);
2372
    // Load classes
2373
    $rsa = new Crypt_RSA();
2374
    // Load the public key
2375
    $decodedPublicKey = base64_decode($publicKey, true);
2376
    if ($decodedPublicKey === false) {
2377
        throw new InvalidArgumentException("Error while decoding key.");
2378
    }
2379
    $rsa->loadKey($decodedPublicKey);
2380
    // Encrypt
2381
    $encrypted = $rsa->encrypt(base64_decode($key));
2382
    if (empty($encrypted)) {  // Check if key is empty or null
2383
        throw new RuntimeException("Error while encrypting key.");
2384
    }
2385
    // Return
2386
    return base64_encode($encrypted);
2387
}
2388
2389
/**
2390
 * Decrypts using RSA an encrypted string using a private key.
2391
 *
2392
 * @param string $key        Encrypted key
2393
 * @param string $privateKey User private key
2394
 *
2395
 * @return string
2396
 */
2397
function decryptUserObjectKey(string $key, string $privateKey): string
2398
{
2399
    // Sanitize
2400
    $antiXss = new AntiXSS();
2401
    $privateKey = $antiXss->xss_clean($privateKey);
2402
2403
    // Load classes
2404
    $rsa = new Crypt_RSA();
2405
    // Load the private key
2406
    $decodedPrivateKey = base64_decode($privateKey, true);
2407
    if ($decodedPrivateKey === false) {
2408
        throw new InvalidArgumentException("Error while decoding private key.");
2409
    }
2410
2411
    $rsa->loadKey($decodedPrivateKey);
2412
2413
    // Decrypt
2414
    try {
2415
        $decodedKey = base64_decode($key, true);
2416
        if ($decodedKey === false) {
2417
            throw new InvalidArgumentException("Error while decoding key.");
2418
        }
2419
2420
        // This check is needed as decrypt() in version 2 can return false in case of error
2421
        $tmpValue = $rsa->decrypt($decodedKey);
2422
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2423
            return base64_encode($tmpValue);
2424
        } else {
2425
            return '';
2426
        }
2427
    } catch (Exception $e) {
2428
        if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2429
            error_log('TEAMPASS Error - ldap - '.$e->getMessage());
2430
        }
2431
        return 'Exception: could not decrypt object';
2432
    }
2433
}
2434
2435
/**
2436
 * Encrypts a file.
2437
 *
2438
 * @param string $fileInName File name
2439
 * @param string $fileInPath Path to file
2440
 *
2441
 * @return array
2442
 */
2443
function encryptFile(string $fileInName, string $fileInPath): array
2444
{
2445
    if (defined('FILE_BUFFER_SIZE') === false) {
2446
        define('FILE_BUFFER_SIZE', 128 * 1024);
2447
    }
2448
2449
    // Load classes
2450
    $cipher = new Crypt_AES();
2451
2452
    // Generate an object key
2453
    $objectKey = uniqidReal(32);
2454
    // Set it as password
2455
    $cipher->setPassword($objectKey);
2456
    // Prevent against out of memory
2457
    $cipher->enableContinuousBuffer();
2458
2459
    // Encrypt the file content
2460
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2461
    $fileContent = file_get_contents($filePath);
2462
    $plaintext = $fileContent;
2463
    $ciphertext = $cipher->encrypt($plaintext);
2464
2465
    // Save new file
2466
    // deepcode ignore InsecureHash: is simply used to get a unique name
2467
    $hash = md5($plaintext);
2468
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2469
    file_put_contents($fileOut, $ciphertext);
2470
    unlink($fileInPath . '/' . $fileInName);
2471
    return [
2472
        'fileHash' => base64_encode($hash),
2473
        'objectKey' => base64_encode($objectKey),
2474
    ];
2475
}
2476
2477
/**
2478
 * Decrypt a file.
2479
 *
2480
 * @param string $fileName File name
2481
 * @param string $filePath Path to file
2482
 * @param string $key      Key to use
2483
 *
2484
 * @return string|array
2485
 */
2486
function decryptFile(string $fileName, string $filePath, string $key): string|array
2487
{
2488
    if (! defined('FILE_BUFFER_SIZE')) {
2489
        define('FILE_BUFFER_SIZE', 128 * 1024);
2490
    }
2491
    
2492
    // Load classes
2493
    $cipher = new Crypt_AES();
2494
    $antiXSS = new AntiXSS();
2495
    
2496
    // Get file name
2497
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2498
2499
    // Set the object key
2500
    $cipher->setPassword(base64_decode($key));
2501
    // Prevent against out of memory
2502
    $cipher->enableContinuousBuffer();
2503
    $cipher->disablePadding();
2504
    // Get file content
2505
    $safeFilePath = realpath($filePath . '/' . TP_FILE_PREFIX . $safeFileName);
2506
    if ($safeFilePath !== false && file_exists($safeFilePath)) {
2507
        $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2508
    } else {
2509
        // Handle the error: file doesn't exist or path is invalid
2510
        return [
2511
            'error' => true,
2512
            'message' => 'This file has not been found.',
2513
        ];
2514
    }
2515
2516
    if (WIP) error_log('DEBUG: File image url -> '.filter_var($safeFilePath, FILTER_SANITIZE_URL));
2517
2518
    // Decrypt file content and return
2519
    return base64_encode($cipher->decrypt($ciphertext));
2520
}
2521
2522
/**
2523
 * Generate a simple password
2524
 *
2525
 * @param int $length Length of string
2526
 * @param bool $symbolsincluded Allow symbols
2527
 *
2528
 * @return string
2529
 */
2530
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2531
{
2532
    // Generate new user password
2533
    $small_letters = range('a', 'z');
2534
    $big_letters = range('A', 'Z');
2535
    $digits = range(0, 9);
2536
    $symbols = $symbolsincluded === true ?
2537
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2538
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2539
    $count = count($res);
2540
    // first variant
2541
2542
    $random_string = '';
2543
    for ($i = 0; $i < $length; ++$i) {
2544
        $random_string .= $res[random_int(0, $count - 1)];
2545
    }
2546
2547
    return $random_string;
2548
}
2549
2550
/**
2551
 * Permit to store the sharekey of an object for users.
2552
 *
2553
 * @param string $object_name             Type for table selection
2554
 * @param int    $post_folder_is_personal Personal
2555
 * @param int    $post_object_id          Object
2556
 * @param string $objectKey               Object key
2557
 * @param array  $SETTINGS                Teampass settings
2558
 * @param int    $user_id                 User ID if needed
2559
 * @param bool   $onlyForUser             If is TRUE, then the sharekey is only for the user
2560
 * @param bool   $deleteAll               If is TRUE, then all existing entries are deleted
2561
 * @param array  $objectKeyArray          Array of objects
2562
 * @param int    $all_users_except_id     All users except this one
2563
 * @param int    $apiUserId               API User ID
2564
 *
2565
 * @return void
2566
 */
2567
function storeUsersShareKey(
2568
    string $object_name,
2569
    int $post_folder_is_personal,
2570
    int $post_object_id,
2571
    string $objectKey,
2572
    bool $onlyForUser = false,
2573
    bool $deleteAll = true,
2574
    array $objectKeyArray = [],
2575
    int $all_users_except_id = -1,
2576
    int $apiUserId = -1
2577
): void {
2578
    
2579
    $session = SessionManager::getSession();
2580
    loadClasses('DB');
2581
2582
    // Delete existing entries for this object
2583
    if ($deleteAll === true) {
2584
        DB::delete(
2585
            $object_name,
2586
            'object_id = %i',
2587
            $post_object_id
2588
        );
2589
    }
2590
2591
    // Get the user ID
2592
    $userId = ($apiUserId === -1) ? (int) $session->get('user-id') : $apiUserId;
2593
    
2594
    // $onlyForUser is only dynamically set by external calls
2595
    if (
2596
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2597
    ) {
2598
        // Only create the sharekey for a user
2599
        $user = DB::queryFirstRow(
2600
            'SELECT public_key
2601
            FROM ' . prefixTable('users') . '
2602
            WHERE id = ' . $userId . '
2603
            AND public_key != ""'
2604
        );
2605
2606
        if (empty($objectKey) === false) {
2607
            DB::insert(
2608
                $object_name,
2609
                [
2610
                    'object_id' => (int) $post_object_id,
2611
                    'user_id' => $userId,
2612
                    'share_key' => encryptUserObjectKey(
2613
                        $objectKey,
2614
                        $user['public_key']
2615
                    ),
2616
                ]
2617
            );
2618
        } else if (count($objectKeyArray) > 0) {
2619
            foreach ($objectKeyArray as $object) {
2620
                DB::insert(
2621
                    $object_name,
2622
                    [
2623
                        'object_id' => (int) $object['objectId'],
2624
                        'user_id' => $userId,
2625
                        'share_key' => encryptUserObjectKey(
2626
                            $object['objectKey'],
2627
                            $user['public_key']
2628
                        ),
2629
                    ]
2630
                );
2631
            }
2632
        }
2633
    } else {
2634
        // Create sharekey for each user
2635
        $users = DB::query(
2636
            'SELECT id, public_key
2637
            FROM ' . prefixTable('users') . '
2638
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '"'.($all_users_except_id === -1 ? '' : ', "'.$all_users_except_id.'"').')
2639
            AND public_key != ""'
2640
        );
2641
        //DB::debugmode(false);
2642
        foreach ($users as $user) {
2643
            // Insert in DB the new object key for this item by user
2644
            if (count($objectKeyArray) === 0) {
2645
                if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case1 - ' . $object_name . ' - ' . $post_object_id . ' - ' . $user['id'] . ' - ' . $objectKey);
2646
                DB::insert(
2647
                    $object_name,
2648
                    [
2649
                        'object_id' => $post_object_id,
2650
                        'user_id' => (int) $user['id'],
2651
                        'share_key' => encryptUserObjectKey(
2652
                            $objectKey,
2653
                            $user['public_key']
2654
                        ),
2655
                    ]
2656
                );
2657
            } else {
2658
                foreach ($objectKeyArray as $object) {
2659
                    if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case2 - ' . $object_name . ' - ' . $object['objectId'] . ' - ' . $user['id'] . ' - ' . $object['objectKey']);
2660
                    DB::insert(
2661
                        $object_name,
2662
                        [
2663
                            'object_id' => (int) $object['objectId'],
2664
                            'user_id' => (int) $user['id'],
2665
                            'share_key' => encryptUserObjectKey(
2666
                                $object['objectKey'],
2667
                                $user['public_key']
2668
                            ),
2669
                        ]
2670
                    );
2671
                }
2672
            }
2673
        }
2674
    }
2675
}
2676
2677
/**
2678
 * Is this string base64 encoded?
2679
 *
2680
 * @param string $str Encoded string?
2681
 *
2682
 * @return bool
2683
 */
2684
function isBase64(string $str): bool
2685
{
2686
    $str = (string) trim($str);
2687
    if (! isset($str[0])) {
2688
        return false;
2689
    }
2690
2691
    $base64String = (string) base64_decode($str, true);
2692
    if ($base64String && base64_encode($base64String) === $str) {
2693
        return true;
2694
    }
2695
2696
    return false;
2697
}
2698
2699
/**
2700
 * Undocumented function
2701
 *
2702
 * @param string $field Parameter
2703
 *
2704
 * @return array|bool|resource|string
2705
 */
2706
function filterString(string $field)
2707
{
2708
    // Sanitize string
2709
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2710
    if (empty($field) === false) {
2711
        // Load AntiXSS
2712
        $antiXss = new AntiXSS();
2713
        // Return
2714
        return $antiXss->xss_clean($field);
2715
    }
2716
2717
    return false;
2718
}
2719
2720
/**
2721
 * CHeck if provided credentials are allowed on server
2722
 *
2723
 * @param string $login    User Login
2724
 * @param string $password User Pwd
2725
 * @param array  $SETTINGS Teampass settings
2726
 *
2727
 * @return bool
2728
 */
2729
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2730
{
2731
    // Build ldap configuration array
2732
    $config = [
2733
        // Mandatory Configuration Options
2734
        'hosts' => [$SETTINGS['ldap_hosts']],
2735
        'base_dn' => $SETTINGS['ldap_bdn'],
2736
        'username' => $SETTINGS['ldap_username'],
2737
        'password' => $SETTINGS['ldap_password'],
2738
2739
        // Optional Configuration Options
2740
        'port' => $SETTINGS['ldap_port'],
2741
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2742
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2743
        'version' => 3,
2744
        'timeout' => 5,
2745
        'follow_referrals' => false,
2746
2747
        // Custom LDAP Options
2748
        'options' => [
2749
            // See: http://php.net/ldap_set_option
2750
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2751
        ],
2752
    ];
2753
    
2754
    $connection = new Connection($config);
2755
    // Connect to LDAP
2756
    try {
2757
        $connection->connect();
2758
    } catch (\LdapRecord\Auth\BindException $e) {
2759
        $error = $e->getDetailedError();
2760
        if ($error && defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2761
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2762
        }
2763
        // deepcode ignore ServerLeak: No important data is sent
2764
        echo 'An error occurred.';
2765
        return false;
2766
    }
2767
2768
    // Authenticate user
2769
    try {
2770
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2771
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2772
        } else {
2773
            $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);
2774
        }
2775
    } catch (\LdapRecord\Auth\BindException $e) {
2776
        $error = $e->getDetailedError();
2777
        if ($error && defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
2778
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2779
        }
2780
        // deepcode ignore ServerLeak: No important data is sent
2781
        echo 'An error occurred.';
2782
        return false;
2783
    }
2784
2785
    return true;
2786
}
2787
2788
/**
2789
 * Removes from DB all sharekeys of this user
2790
 *
2791
 * @param int $userId User's id
2792
 * @param array   $SETTINGS Teampass settings
2793
 *
2794
 * @return bool
2795
 */
2796
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2797
{
2798
    // Load class DB
2799
    loadClasses('DB');
2800
2801
    // Remove all item sharekeys items
2802
    // expect if personal item
2803
    DB::delete(
2804
        prefixTable('sharekeys_items'),
2805
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2806
        $userId
2807
    );
2808
    // Remove all item sharekeys files
2809
    DB::delete(
2810
        prefixTable('sharekeys_files'),
2811
        'user_id = %i AND object_id NOT IN (
2812
            SELECT f.id 
2813
            FROM ' . prefixTable('items') . ' AS i 
2814
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2815
            WHERE i.perso = 1
2816
        )',
2817
        $userId
2818
    );
2819
    // Remove all item sharekeys fields
2820
    DB::delete(
2821
        prefixTable('sharekeys_fields'),
2822
        'user_id = %i AND object_id NOT IN (
2823
            SELECT c.id 
2824
            FROM ' . prefixTable('items') . ' AS i 
2825
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2826
            WHERE i.perso = 1
2827
        )',
2828
        $userId
2829
    );
2830
    // Remove all item sharekeys logs
2831
    DB::delete(
2832
        prefixTable('sharekeys_logs'),
2833
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2834
        $userId
2835
    );
2836
    // Remove all item sharekeys suggestions
2837
    DB::delete(
2838
        prefixTable('sharekeys_suggestions'),
2839
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2840
        $userId
2841
    );
2842
    return false;
2843
}
2844
2845
/**
2846
 * Manage list of timezones   $SETTINGS Teampass settings
2847
 *
2848
 * @return array
2849
 */
2850
function timezone_list()
2851
{
2852
    static $timezones = null;
2853
    if ($timezones === null) {
2854
        $timezones = [];
2855
        $offsets = [];
2856
        $now = new DateTime('now', new DateTimeZone('UTC'));
2857
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2858
            $now->setTimezone(new DateTimeZone($timezone));
2859
            $offsets[] = $offset = $now->getOffset();
2860
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2861
        }
2862
2863
        array_multisort($offsets, $timezones);
2864
    }
2865
2866
    return $timezones;
2867
}
2868
2869
/**
2870
 * Provide timezone offset
2871
 *
2872
 * @param int $offset Timezone offset
2873
 *
2874
 * @return string
2875
 */
2876
function format_GMT_offset($offset): string
2877
{
2878
    $hours = intval($offset / 3600);
2879
    $minutes = abs(intval($offset % 3600 / 60));
2880
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2881
}
2882
2883
/**
2884
 * Provides timezone name
2885
 *
2886
 * @param string $name Timezone name
2887
 *
2888
 * @return string
2889
 */
2890
function format_timezone_name($name): string
2891
{
2892
    $name = str_replace('/', ', ', $name);
2893
    $name = str_replace('_', ' ', $name);
2894
2895
    return str_replace('St ', 'St. ', $name);
2896
}
2897
2898
/**
2899
 * Provides info if user should use MFA based on roles
2900
 *
2901
 * @param string $userRolesIds  User roles ids
2902
 * @param string $mfaRoles      Roles for which MFA is requested
2903
 *
2904
 * @return bool
2905
 */
2906
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
2907
{
2908
    if (empty($mfaRoles) === true) {
2909
        return true;
2910
    }
2911
2912
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2913
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2914
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2915
        return true;
2916
    }
2917
2918
    return false;
2919
}
2920
2921
/**
2922
 * Permits to clean a string for export purpose
2923
 *
2924
 * @param string $text
2925
 * @param bool $emptyCheckOnly
2926
 * 
2927
 * @return string
2928
 */
2929
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
2930
{
2931
    if (is_null($text) === true || empty($text) === true) {
2932
        return '';
2933
    }
2934
    // only expected to check if $text was empty
2935
    elseif ($emptyCheckOnly === true) {
2936
        return $text;
2937
    }
2938
2939
    return strip_tags(
2940
        cleanString(
2941
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
2942
            true)
2943
        );
2944
}
2945
2946
/**
2947
 * Permits to check if user ID is valid
2948
 *
2949
 * @param integer $post_user_id
2950
 * @return bool
2951
 */
2952
function isUserIdValid($userId): bool
2953
{
2954
    if (is_null($userId) === false
2955
        && isset($userId) === true
2956
        && empty($userId) === false
2957
    ) {
2958
        return true;
2959
    }
2960
    return false;
2961
}
2962
2963
/**
2964
 * Check if a key exists and if its value equal the one expected
2965
 *
2966
 * @param string $key
2967
 * @param integer|string $value
2968
 * @param array $array
2969
 * 
2970
 * @return boolean
2971
 */
2972
function isKeyExistingAndEqual(
2973
    string $key,
2974
    /*PHP8 - integer|string*/$value,
2975
    array $array
2976
): bool
2977
{
2978
    if (isset($array[$key]) === true
2979
        && (is_int($value) === true ?
2980
            (int) $array[$key] === $value :
2981
            (string) $array[$key] === $value)
2982
    ) {
2983
        return true;
2984
    }
2985
    return false;
2986
}
2987
2988
/**
2989
 * Check if a variable is not set or equal to a value
2990
 *
2991
 * @param string|null $var
2992
 * @param integer|string $value
2993
 * 
2994
 * @return boolean
2995
 */
2996
function isKeyNotSetOrEqual(
2997
    /*PHP8 - string|null*/$var,
2998
    /*PHP8 - integer|string*/$value
2999
): bool
3000
{
3001
    if (isset($var) === false
3002
        || (is_int($value) === true ?
3003
            (int) $var === $value :
3004
            (string) $var === $value)
3005
    ) {
3006
        return true;
3007
    }
3008
    return false;
3009
}
3010
3011
/**
3012
 * Check if a key exists and if its value < to the one expected
3013
 *
3014
 * @param string $key
3015
 * @param integer $value
3016
 * @param array $array
3017
 * 
3018
 * @return boolean
3019
 */
3020
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3021
{
3022
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3023
        return true;
3024
    }
3025
    return false;
3026
}
3027
3028
/**
3029
 * Check if a key exists and if its value > to the one expected
3030
 *
3031
 * @param string $key
3032
 * @param integer $value
3033
 * @param array $array
3034
 * 
3035
 * @return boolean
3036
 */
3037
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3038
{
3039
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3040
        return true;
3041
    }
3042
    return false;
3043
}
3044
3045
/**
3046
 * Check if values in array are set
3047
 * Return true if all set
3048
 * Return false if one of them is not set
3049
 *
3050
 * @param array $arrayOfValues
3051
 * @return boolean
3052
 */
3053
function isSetArrayOfValues(array $arrayOfValues): bool
3054
{
3055
    foreach($arrayOfValues as $value) {
3056
        if (isset($value) === false) {
3057
            return false;
3058
        }
3059
    }
3060
    return true;
3061
}
3062
3063
/**
3064
 * Check if values in array are set
3065
 * Return true if all set
3066
 * Return false if one of them is not set
3067
 *
3068
 * @param array $arrayOfValues
3069
 * @param integer|string $value
3070
 * @return boolean
3071
 */
3072
function isArrayOfVarsEqualToValue(
3073
    array $arrayOfVars,
3074
    /*PHP8 - integer|string*/$value
3075
) : bool
3076
{
3077
    foreach($arrayOfVars as $variable) {
3078
        if ($variable !== $value) {
3079
            return false;
3080
        }
3081
    }
3082
    return true;
3083
}
3084
3085
/**
3086
 * Checks if at least one variable in array is equal to value
3087
 *
3088
 * @param array $arrayOfValues
3089
 * @param integer|string $value
3090
 * @return boolean
3091
 */
3092
function isOneVarOfArrayEqualToValue(
3093
    array $arrayOfVars,
3094
    /*PHP8 - integer|string*/$value
3095
) : bool
3096
{
3097
    foreach($arrayOfVars as $variable) {
3098
        if ($variable === $value) {
3099
            return true;
3100
        }
3101
    }
3102
    return false;
3103
}
3104
3105
/**
3106
 * Checks is value is null, not set OR empty
3107
 *
3108
 * @param string|int|null $value
3109
 * @return boolean
3110
 */
3111
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3112
{
3113
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3114
        return true;
3115
    }
3116
    return false;
3117
}
3118
3119
/**
3120
 * Checks if value is set and if empty is equal to passed boolean
3121
 *
3122
 * @param string|int $value
3123
 * @param boolean $boolean
3124
 * @return boolean
3125
 */
3126
function isValueSetEmpty($value, $boolean = true) : bool
3127
{
3128
    if (isset($value) === true && empty($value) === $boolean) {
3129
        return true;
3130
    }
3131
    return false;
3132
}
3133
3134
/**
3135
 * Ensure Complexity is translated
3136
 *
3137
 * @return void
3138
 */
3139
function defineComplexity() : void
3140
{
3141
    // Load user's language
3142
    $session = SessionManager::getSession();
3143
    $lang = new Language($session->get('user-language') ?? 'english');
3144
    
3145
    if (defined('TP_PW_COMPLEXITY') === false) {
3146
        define(
3147
            'TP_PW_COMPLEXITY',
3148
            [
3149
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3150
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3151
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3152
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3153
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3154
            ]
3155
        );
3156
    }
3157
}
3158
3159
/**
3160
 * Uses Sanitizer to perform data sanitization
3161
 *
3162
 * @param array     $data
3163
 * @param array     $filters
3164
 * @return array|string
3165
 */
3166
function dataSanitizer(array $data, array $filters): array|string
3167
{
3168
    // Load Sanitizer library
3169
    $sanitizer = new Sanitizer($data, $filters);
3170
3171
    // Load AntiXSS
3172
    $antiXss = new AntiXSS();
3173
3174
    // Sanitize post and get variables
3175
    return $antiXss->xss_clean($sanitizer->sanitize());
3176
}
3177
3178
/**
3179
 * Permits to manage the cache tree for a user
3180
 *
3181
 * @param integer $user_id
3182
 * @param string $data
3183
 * @param array $SETTINGS
3184
 * @param string $field_update
3185
 * @return void
3186
 */
3187
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3188
{
3189
    // Load class DB
3190
    loadClasses('DB');
3191
3192
    // Exists ?
3193
    $userCacheId = DB::queryfirstrow(
3194
        'SELECT increment_id
3195
        FROM ' . prefixTable('cache_tree') . '
3196
        WHERE user_id = %i',
3197
        $user_id
3198
    );
3199
    
3200
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3201
        // insert in table
3202
        DB::insert(
3203
            prefixTable('cache_tree'),
3204
            array(
3205
                'data' => $data,
3206
                'timestamp' => time(),
3207
                'user_id' => $user_id,
3208
                'visible_folders' => '',
3209
            )
3210
        );
3211
    } else {
3212
        if (empty($field_update) === true) {
3213
            DB::update(
3214
                prefixTable('cache_tree'),
3215
                [
3216
                    'timestamp' => time(),
3217
                    'data' => $data,
3218
                ],
3219
                'increment_id = %i',
3220
                $userCacheId['increment_id']
3221
            );
3222
        /* USELESS
3223
        } else {
3224
            DB::update(
3225
                prefixTable('cache_tree'),
3226
                [
3227
                    $field_update => $data,
3228
                ],
3229
                'increment_id = %i',
3230
                $userCacheId['increment_id']
3231
            );*/
3232
        }
3233
    }
3234
}
3235
3236
/**
3237
 * Permits to calculate a %
3238
 *
3239
 * @param float $nombre
3240
 * @param float $total
3241
 * @param float $pourcentage
3242
 * @return float
3243
 */
3244
function pourcentage(float $nombre, float $total, float $pourcentage): float
3245
{ 
3246
    $resultat = ($nombre/$total) * $pourcentage;
3247
    return round($resultat);
3248
}
3249
3250
/**
3251
 * Load the folders list from the cache
3252
 *
3253
 * @param string $fieldName
3254
 * @param string $sessionName
3255
 * @param boolean $forceRefresh
3256
 * @return array
3257
 */
3258
function loadFoldersListByCache(
3259
    string $fieldName,
3260
    string $sessionName,
3261
    bool $forceRefresh = false
3262
): array
3263
{
3264
    // Case when refresh is EXPECTED / MANDATORY
3265
    if ($forceRefresh === true) {
3266
        return [
3267
            'state' => false,
3268
            'data' => [],
3269
        ];
3270
    }
3271
    
3272
    $session = SessionManager::getSession();
3273
3274
    // Get last folder update
3275
    $lastFolderChange = DB::queryfirstrow(
3276
        'SELECT valeur FROM ' . prefixTable('misc') . '
3277
        WHERE type = %s AND intitule = %s',
3278
        'timestamp',
3279
        'last_folder_change'
3280
    );
3281
    if (DB::count() === 0) {
3282
        $lastFolderChange['valeur'] = 0;
3283
    }
3284
3285
    // Case when an update in the tree has been done
3286
    // Refresh is then mandatory
3287
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3288
        return [
3289
            'state' => false,
3290
            'data' => [],
3291
        ];
3292
    }
3293
3294
    // Does this user has the tree structure in session?
3295
    // If yes then use it
3296
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3297
        return [
3298
            'state' => true,
3299
            'data' => json_encode($session->get('user-folders_list')[0]),
3300
            'extra' => 'to_be_parsed',
3301
        ];
3302
    }
3303
    
3304
    // Does this user has a tree cache
3305
    $userCacheTree = DB::queryfirstrow(
3306
        'SELECT '.$fieldName.'
3307
        FROM ' . prefixTable('cache_tree') . '
3308
        WHERE user_id = %i',
3309
        $session->get('user-id')
3310
    );
3311
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3312
        SessionManager::addRemoveFromSessionAssociativeArray(
3313
            'user-folders_list',
3314
            [$userCacheTree[$fieldName]],
3315
            'add'
3316
        );
3317
        return [
3318
            'state' => true,
3319
            'data' => $userCacheTree[$fieldName],
3320
            'extra' => '',
3321
        ];
3322
    }
3323
3324
    return [
3325
        'state' => false,
3326
        'data' => [],
3327
    ];
3328
}
3329
3330
3331
/**
3332
 * Permits to refresh the categories of folders
3333
 *
3334
 * @param array $folderIds
3335
 * @return void
3336
 */
3337
function handleFoldersCategories(
3338
    array $folderIds
3339
)
3340
{
3341
    // Load class DB
3342
    loadClasses('DB');
3343
3344
    $arr_data = array();
3345
3346
    // force full list of folders
3347
    if (count($folderIds) === 0) {
3348
        $folderIds = DB::queryFirstColumn(
3349
            'SELECT id
3350
            FROM ' . prefixTable('nested_tree') . '
3351
            WHERE personal_folder=%i',
3352
            0
3353
        );
3354
    }
3355
3356
    // Get complexity
3357
    defineComplexity();
3358
3359
    // update
3360
    foreach ($folderIds as $folder) {
3361
        // Do we have Categories
3362
        // get list of associated Categories
3363
        $arrCatList = array();
3364
        $rows_tmp = DB::query(
3365
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3366
            f.id_category AS category_id
3367
            FROM ' . prefixTable('categories_folders') . ' AS f
3368
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3369
            WHERE id_folder=%i',
3370
            $folder
3371
        );
3372
        if (DB::count() > 0) {
3373
            foreach ($rows_tmp as $row) {
3374
                $arrCatList[$row['id']] = array(
3375
                    'id' => $row['id'],
3376
                    'title' => $row['title'],
3377
                    'level' => $row['level'],
3378
                    'type' => $row['type'],
3379
                    'masked' => $row['masked'],
3380
                    'order' => $row['order'],
3381
                    'encrypted_data' => $row['encrypted_data'],
3382
                    'role_visibility' => $row['role_visibility'],
3383
                    'is_mandatory' => $row['is_mandatory'],
3384
                    'category_id' => $row['category_id'],
3385
                );
3386
            }
3387
        }
3388
        $arr_data['categories'] = $arrCatList;
3389
3390
        // Now get complexity
3391
        $valTemp = '';
3392
        $data = DB::queryFirstRow(
3393
            'SELECT valeur
3394
            FROM ' . prefixTable('misc') . '
3395
            WHERE type = %s AND intitule=%i',
3396
            'complex',
3397
            $folder
3398
        );
3399
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3400
            $valTemp = array(
3401
                'value' => $data['valeur'],
3402
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3403
            );
3404
        }
3405
        $arr_data['complexity'] = $valTemp;
3406
3407
        // Now get Roles
3408
        $valTemp = '';
3409
        $rows_tmp = DB::query(
3410
            'SELECT t.title
3411
            FROM ' . prefixTable('roles_values') . ' as v
3412
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3413
            WHERE v.folder_id = %i
3414
            GROUP BY title',
3415
            $folder
3416
        );
3417
        foreach ($rows_tmp as $record) {
3418
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3419
        }
3420
        $arr_data['visibilityRoles'] = $valTemp;
3421
3422
        // now save in DB
3423
        DB::update(
3424
            prefixTable('nested_tree'),
3425
            array(
3426
                'categories' => json_encode($arr_data),
3427
            ),
3428
            'id = %i',
3429
            $folder
3430
        );
3431
    }
3432
}
3433
3434
/**
3435
 * List all users that have specific roles
3436
 *
3437
 * @param array $roles
3438
 * @return array
3439
 */
3440
function getUsersWithRoles(
3441
    array $roles
3442
): array
3443
{
3444
    $session = SessionManager::getSession();
3445
    $arrUsers = array();
3446
3447
    foreach ($roles as $role) {
3448
        // loop on users and check if user has this role
3449
        $rows = DB::query(
3450
            'SELECT id, fonction_id
3451
            FROM ' . prefixTable('users') . '
3452
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3453
            $session->get('user-id')
3454
        );
3455
        foreach ($rows as $user) {
3456
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3457
            if (in_array($role, $userRoles, true) === true) {
3458
                array_push($arrUsers, $user['id']);
3459
            }
3460
        }
3461
    }
3462
3463
    return $arrUsers;
3464
}
3465
3466
3467
/**
3468
 * Get all users informations
3469
 *
3470
 * @param integer $userId
3471
 * @return array
3472
 */
3473
function getFullUserInfos(
3474
    int $userId
3475
): array
3476
{
3477
    if (empty($userId) === true) {
3478
        return array();
3479
    }
3480
3481
    $val = DB::queryfirstrow(
3482
        'SELECT *
3483
        FROM ' . prefixTable('users') . '
3484
        WHERE id = %i',
3485
        $userId
3486
    );
3487
3488
    return $val;
3489
}
3490
3491
/**
3492
 * Is required an upgrade
3493
 *
3494
 * @return boolean
3495
 */
3496
function upgradeRequired(): bool
3497
{
3498
    // Get settings.php
3499
    include_once __DIR__. '/../includes/config/settings.php';
3500
3501
    // Get timestamp in DB
3502
    $val = DB::queryfirstrow(
3503
        'SELECT valeur
3504
        FROM ' . prefixTable('misc') . '
3505
        WHERE type = %s AND intitule = %s',
3506
        'admin',
3507
        'upgrade_timestamp'
3508
    );
3509
3510
    // Check if upgrade is required
3511
    return (
3512
        is_null($val) || count($val) === 0 || !defined('UPGRADE_MIN_DATE') || 
3513
        empty($val['valeur']) || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE
3514
    );
3515
}
3516
3517
/**
3518
 * Permits to change the user keys on his demand
3519
 *
3520
 * @param integer $userId
3521
 * @param string $passwordClear
3522
 * @param integer $nbItemsToTreat
3523
 * @param string $encryptionKey
3524
 * @param boolean $deleteExistingKeys
3525
 * @param boolean $sendEmailToUser
3526
 * @param boolean $encryptWithUserPassword
3527
 * @param boolean $generate_user_new_password
3528
 * @param string $emailBody
3529
 * @param boolean $user_self_change
3530
 * @param string $recovery_public_key
3531
 * @param string $recovery_private_key
3532
 * @return string
3533
 */
3534
function handleUserKeys(
3535
    int $userId,
3536
    string $passwordClear,
3537
    int $nbItemsToTreat,
3538
    string $encryptionKey = '',
3539
    bool $deleteExistingKeys = false,
3540
    bool $sendEmailToUser = true,
3541
    bool $encryptWithUserPassword = false,
3542
    bool $generate_user_new_password = false,
3543
    string $emailBody = '',
3544
    bool $user_self_change = false,
3545
    string $recovery_public_key = '',
3546
    string $recovery_private_key = ''
3547
): string
3548
{
3549
    $session = SessionManager::getSession();
3550
    $lang = new Language($session->get('user-language') ?? 'english');
3551
3552
    // prepapre background tasks for item keys generation        
3553
    $userTP = DB::queryFirstRow(
3554
        'SELECT pw, public_key, private_key
3555
        FROM ' . prefixTable('users') . '
3556
        WHERE id = %i',
3557
        TP_USER_ID
3558
    );
3559
    if (DB::count() === 0) {
3560
        return prepareExchangedData(
3561
            array(
3562
                'error' => true,
3563
                'message' => 'User not exists',
3564
            ),
3565
            'encode'
3566
        );
3567
    }
3568
3569
    // Do we need to generate new user password
3570
    if ($generate_user_new_password === true) {
3571
        // Generate a new password
3572
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3573
    }
3574
3575
    // Create password hash
3576
    $passwordManager = new PasswordManager();
3577
    $hashedPassword = $passwordManager->hashPassword($passwordClear);
3578
    if ($passwordManager->verifyPassword($hashedPassword, $passwordClear) === false) {
3579
        return prepareExchangedData(
3580
            array(
3581
                'error' => true,
3582
                'message' => $lang->get('pw_hash_not_correct'),
3583
            ),
3584
            'encode'
3585
        );
3586
    }
3587
3588
    // Check if valid public/private keys
3589
    if ($recovery_public_key !== '' && $recovery_private_key !== '') {
3590
        try {
3591
            // Generate random string
3592
            $random_str = generateQuickPassword(12, false);
3593
            // Encrypt random string with user publick key
3594
            $encrypted = encryptUserObjectKey($random_str, $recovery_public_key);
3595
            // Decrypt $encrypted with private key
3596
            $decrypted = decryptUserObjectKey($encrypted, $recovery_private_key);
3597
            // Check if decryptUserObjectKey returns our random string
3598
            if ($decrypted !== $random_str) {
3599
                throw new Exception('Public/Private keypair invalid.');
3600
            }
3601
        } catch (Exception $e) {
3602
            // Show error message to user and log event
3603
            if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
3604
                error_log('ERROR: User '.$userId.' - '.$e->getMessage());
3605
            }
3606
            return prepareExchangedData([
3607
                    'error' => true,
3608
                    'message' => $lang->get('pw_encryption_error'),
3609
                ],
3610
                'encode'
3611
            );
3612
        }
3613
    }
3614
3615
    // Generate new keys
3616
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3617
        $userKeys = [
3618
            'public_key' => $recovery_public_key,
3619
            'private_key_clear' => $recovery_private_key,
3620
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3621
        ];
3622
    } else {
3623
        $userKeys = generateUserKeys($passwordClear);
3624
    }
3625
3626
    // Save in DB
3627
    DB::update(
3628
        prefixTable('users'),
3629
        array(
3630
            'pw' => $hashedPassword,
3631
            'public_key' => $userKeys['public_key'],
3632
            'private_key' => $userKeys['private_key'],
3633
            'keys_recovery_time' => NULL,
3634
        ),
3635
        'id=%i',
3636
        $userId
3637
    );
3638
3639
    // update session too
3640
    if ($userId === $session->get('user-id')) {
3641
        $session->set('user-private_key', $userKeys['private_key_clear']);
3642
        $session->set('user-public_key', $userKeys['public_key']);
3643
        // Notify user that he must re download his keys:
3644
        $session->set('user-keys_recovery_time', NULL);
3645
    }
3646
3647
    // Manage empty encryption key
3648
    // Let's take the user's password if asked and if no encryption key provided
3649
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3650
3651
    // Create process
3652
    DB::insert(
3653
        prefixTable('background_tasks'),
3654
        array(
3655
            'created_at' => time(),
3656
            'process_type' => 'create_user_keys',
3657
            'arguments' => json_encode([
3658
                'new_user_id' => (int) $userId,
3659
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3660
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3661
                'owner_id' => (int) TP_USER_ID,
3662
                'creator_pwd' => $userTP['pw'],
3663
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3664
                'otp_provided_new_value' => 1,
3665
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3666
                'user_self_change' => $user_self_change === true ? 1 : 0,
3667
            ]),
3668
        )
3669
    );
3670
    $processId = DB::insertId();
3671
3672
    // Delete existing keys
3673
    if ($deleteExistingKeys === true) {
3674
        deleteUserObjetsKeys(
3675
            (int) $userId,
3676
        );
3677
    }
3678
3679
    // Create tasks
3680
    createUserTasks($processId, $nbItemsToTreat);
3681
3682
    // update user's new status
3683
    DB::update(
3684
        prefixTable('users'),
3685
        [
3686
            'is_ready_for_usage' => 0,
3687
            'otp_provided' => 1,
3688
            'ongoing_process_id' => $processId,
3689
            'special' => 'generate-keys',
3690
        ],
3691
        'id=%i',
3692
        $userId
3693
    );
3694
3695
    return prepareExchangedData(
3696
        array(
3697
            'error' => false,
3698
            'message' => '',
3699
            'user_password' => $generate_user_new_password === true ? $passwordClear : '',
3700
        ),
3701
        'encode'
3702
    );
3703
}
3704
3705
/**
3706
 * Permits to generate a new password for a user
3707
 *
3708
 * @param integer $processId
3709
 * @param integer $nbItemsToTreat
3710
 * @return void
3711
 
3712
 */
3713
function createUserTasks($processId, $nbItemsToTreat): void
3714
{
3715
    DB::insert(
3716
        prefixTable('background_subtasks'),
3717
        array(
3718
            'task_id' => $processId,
3719
            'created_at' => time(),
3720
            'task' => json_encode([
3721
                'step' => 'step0',
3722
                'index' => 0,
3723
                'nb' => $nbItemsToTreat,
3724
            ]),
3725
        )
3726
    );
3727
3728
    DB::insert(
3729
        prefixTable('background_subtasks'),
3730
        array(
3731
            'task_id' => $processId,
3732
            'created_at' => time(),
3733
            'task' => json_encode([
3734
                'step' => 'step10',
3735
                'index' => 0,
3736
                'nb' => $nbItemsToTreat,
3737
            ]),
3738
        )
3739
    );
3740
3741
    DB::insert(
3742
        prefixTable('background_subtasks'),
3743
        array(
3744
            'task_id' => $processId,
3745
            'created_at' => time(),
3746
            'task' => json_encode([
3747
                'step' => 'step20',
3748
                'index' => 0,
3749
                'nb' => $nbItemsToTreat,
3750
            ]),
3751
        )
3752
    );
3753
3754
    DB::insert(
3755
        prefixTable('background_subtasks'),
3756
        array(
3757
            'task_id' => $processId,
3758
            'created_at' => time(),
3759
            'task' => json_encode([
3760
                'step' => 'step30',
3761
                'index' => 0,
3762
                'nb' => $nbItemsToTreat,
3763
            ]),
3764
        )
3765
    );
3766
3767
    DB::insert(
3768
        prefixTable('background_subtasks'),
3769
        array(
3770
            'task_id' => $processId,
3771
            'created_at' => time(),
3772
            'task' => json_encode([
3773
                'step' => 'step40',
3774
                'index' => 0,
3775
                'nb' => $nbItemsToTreat,
3776
            ]),
3777
        )
3778
    );
3779
3780
    DB::insert(
3781
        prefixTable('background_subtasks'),
3782
        array(
3783
            'task_id' => $processId,
3784
            'created_at' => time(),
3785
            'task' => json_encode([
3786
                'step' => 'step50',
3787
                'index' => 0,
3788
                'nb' => $nbItemsToTreat,
3789
            ]),
3790
        )
3791
    );
3792
3793
    DB::insert(
3794
        prefixTable('background_subtasks'),
3795
        array(
3796
            'task_id' => $processId,
3797
            'created_at' => time(),
3798
            'task' => json_encode([
3799
                'step' => 'step60',
3800
                'index' => 0,
3801
                'nb' => $nbItemsToTreat,
3802
            ]),
3803
        )
3804
    );
3805
}
3806
3807
/**
3808
 * Permeits to check the consistency of date versus columns definition
3809
 *
3810
 * @param string $table
3811
 * @param array $dataFields
3812
 * @return array
3813
 */
3814
function validateDataFields(
3815
    string $table,
3816
    array $dataFields
3817
): array
3818
{
3819
    // Get table structure
3820
    $result = DB::query(
3821
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3822
        DB_NAME,
3823
        $table
3824
    );
3825
3826
    foreach ($result as $row) {
3827
        $field = $row['COLUMN_NAME'];
3828
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3829
3830
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3831
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3832
                return [
3833
                    'state' => false,
3834
                    'field' => $field,
3835
                    'maxLength' => $maxLength,
3836
                    'currentLength' => strlen((string) $dataFields[$field]),
3837
                ];
3838
            }
3839
        }
3840
    }
3841
    
3842
    return [
3843
        'state' => true,
3844
        'message' => '',
3845
    ];
3846
}
3847
3848
/**
3849
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3850
 *
3851
 * @param string $string
3852
 * @return string
3853
 */
3854
function filterVarBack(string $string): string
3855
{
3856
    $arr = [
3857
        '&#060;' => '<',
3858
        '&#062;' => '>',
3859
        '&#034;' => '"',
3860
        '&#039;' => "'",
3861
        '&#038;' => '&',
3862
    ];
3863
3864
    foreach ($arr as $key => $value) {
3865
        $string = str_replace($key, $value, $string);
3866
    }
3867
3868
    return $string;
3869
}
3870
3871
/**
3872
 * 
3873
 */
3874
function storeTask(
3875
    string $taskName,
3876
    int $user_id,
3877
    int $is_personal_folder,
3878
    int $folder_destination_id,
3879
    int $item_id,
3880
    string $object_keys,
3881
    array $fields_keys = [],
3882
    array $files_keys = []
3883
)
3884
{
3885
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3886
        // Create process
3887
        DB::insert(
3888
            prefixTable('background_tasks'),
3889
            array(
3890
                'created_at' => time(),
3891
                'process_type' => $taskName,
3892
                'arguments' => json_encode([
3893
                    'item_id' => $item_id,
3894
                    'object_key' => $object_keys,
3895
                ]),
3896
                'item_id' => $item_id,
3897
            )
3898
        );
3899
        $processId = DB::insertId();
3900
3901
        // Create tasks
3902
        // 1- Create password sharekeys for users of this new ITEM
3903
        DB::insert(
3904
            prefixTable('background_subtasks'),
3905
            array(
3906
                'task_id' => $processId,
3907
                'created_at' => time(),
3908
                'task' => json_encode([
3909
                    'step' => 'create_users_pwd_key',
3910
                    'index' => 0,
3911
                ]),
3912
            )
3913
        );
3914
3915
        // 2- Create fields sharekeys for users of this new ITEM
3916
        DB::insert(
3917
            prefixTable('background_subtasks'),
3918
            array(
3919
                'task_id' => $processId,
3920
                'created_at' => time(),
3921
                'task' => json_encode([
3922
                    'step' => 'create_users_fields_key',
3923
                    'index' => 0,
3924
                    'fields_keys' => $fields_keys,
3925
                ]),
3926
            )
3927
        );
3928
3929
        // 3- Create files 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_files_key',
3937
                    'index' => 0,
3938
                    'files_keys' => $files_keys,
3939
                ]),
3940
            )
3941
        );
3942
    }
3943
}
3944
3945
/**
3946
 * 
3947
 */
3948
function createTaskForItem(
3949
    string $processType,
3950
    string|array $taskName,
3951
    int $itemId,
3952
    int $userId,
3953
    string $objectKey,
3954
    int $parentId = -1,
3955
    array $fields_keys = [],
3956
    array $files_keys = []
3957
)
3958
{
3959
    // 1- Create main process
3960
    // ---
3961
    
3962
    // Create process
3963
    DB::insert(
3964
        prefixTable('background_tasks'),
3965
        array(
3966
            'created_at' => time(),
3967
            'process_type' => $processType,
3968
            'arguments' => json_encode([
3969
                'all_users_except_id' => (int) $userId,
3970
                'item_id' => (int) $itemId,
3971
                'object_key' => $objectKey,
3972
                'author' => (int) $userId,
3973
            ]),
3974
            'item_id' => (int) $parentId !== -1 ?  $parentId : null,
3975
        )
3976
    );
3977
    $processId = DB::insertId();
3978
3979
    // 2- Create expected tasks
3980
    // ---
3981
    if (is_array($taskName) === false) {
0 ignored issues
show
introduced by
The condition is_array($taskName) === false is always false.
Loading history...
3982
        $taskName = [$taskName];
3983
    }
3984
    foreach($taskName as $task) {
3985
        if (WIP === true) error_log('createTaskForItem - task: '.$task);
3986
        switch ($task) {
3987
            case 'item_password':
3988
                
3989
                DB::insert(
3990
                    prefixTable('background_subtasks'),
3991
                    array(
3992
                        'task_id' => $processId,
3993
                        'created_at' => time(),
3994
                        'task' => json_encode([
3995
                            'step' => 'create_users_pwd_key',
3996
                            'index' => 0,
3997
                        ]),
3998
                    )
3999
                );
4000
4001
                break;
4002
            case 'item_field':
4003
                
4004
                DB::insert(
4005
                    prefixTable('background_subtasks'),
4006
                    array(
4007
                        'task_id' => $processId,
4008
                        'created_at' => time(),
4009
                        'task' => json_encode([
4010
                            'step' => 'create_users_fields_key',
4011
                            'index' => 0,
4012
                            'fields_keys' => $fields_keys,
4013
                        ]),
4014
                    )
4015
                );
4016
4017
                break;
4018
            case 'item_file':
4019
4020
                DB::insert(
4021
                    prefixTable('background_subtasks'),
4022
                    array(
4023
                        'task_id' => $processId,
4024
                        'created_at' => time(),
4025
                        'task' => json_encode([
4026
                            'step' => 'create_users_files_key',
4027
                            'index' => 0,
4028
                            'fields_keys' => $files_keys,
4029
                        ]),
4030
                    )
4031
                );
4032
                break;
4033
            default:
4034
                # code...
4035
                break;
4036
        }
4037
    }
4038
}
4039
4040
4041
function deleteProcessAndRelatedTasks(int $processId)
4042
{
4043
    // Delete process
4044
    DB::delete(
4045
        prefixTable('background_tasks'),
4046
        'id=%i',
4047
        $processId
4048
    );
4049
4050
    // Delete tasks
4051
    DB::delete(
4052
        prefixTable('background_subtasks'),
4053
        'task_id=%i',
4054
        $processId
4055
    );
4056
4057
}
4058
4059
/**
4060
 * Return PHP binary path
4061
 *
4062
 * @return string
4063
 */
4064
function getPHPBinary(): string
4065
{
4066
    // Get PHP binary path
4067
    $phpBinaryFinder = new PhpExecutableFinder();
4068
    $phpBinaryPath = $phpBinaryFinder->find();
4069
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4070
}
4071
4072
4073
4074
/**
4075
 * Delete unnecessary keys for personal items
4076
 *
4077
 * @param boolean $allUsers
4078
 * @param integer $user_id
4079
 * @return void
4080
 */
4081
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4082
{
4083
    if ($allUsers === true) {
4084
        // Load class DB
4085
        if (class_exists('DB') === false) {
4086
            loadClasses('DB');
4087
        }
4088
4089
        $users = DB::query(
4090
            'SELECT id
4091
            FROM ' . prefixTable('users') . '
4092
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4093
            ORDER BY login ASC'
4094
        );
4095
        foreach ($users as $user) {
4096
            purgeUnnecessaryKeysForUser((int) $user['id']);
4097
        }
4098
    } else {
4099
        purgeUnnecessaryKeysForUser((int) $user_id);
4100
    }
4101
}
4102
4103
/**
4104
 * Delete unnecessary keys for personal items
4105
 *
4106
 * @param integer $user_id
4107
 * @return void
4108
 */
4109
function purgeUnnecessaryKeysForUser(int $user_id=0)
4110
{
4111
    if ($user_id === 0) {
4112
        return;
4113
    }
4114
4115
    // Load class DB
4116
    loadClasses('DB');
4117
4118
    $personalItems = DB::queryFirstColumn(
4119
        'SELECT id
4120
        FROM ' . prefixTable('items') . ' AS i
4121
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4122
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4123
        $user_id
4124
    );
4125
    if (count($personalItems) > 0) {
4126
        // Item keys
4127
        DB::delete(
4128
            prefixTable('sharekeys_items'),
4129
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4130
            $personalItems,
4131
            $user_id
4132
        );
4133
        // Files keys
4134
        DB::delete(
4135
            prefixTable('sharekeys_files'),
4136
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4137
            $personalItems,
4138
            $user_id
4139
        );
4140
        // Fields keys
4141
        DB::delete(
4142
            prefixTable('sharekeys_fields'),
4143
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4144
            $personalItems,
4145
            $user_id
4146
        );
4147
        // Logs keys
4148
        DB::delete(
4149
            prefixTable('sharekeys_logs'),
4150
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4151
            $personalItems,
4152
            $user_id
4153
        );
4154
    }
4155
}
4156
4157
/**
4158
 * Generate recovery keys file
4159
 *
4160
 * @param integer $userId
4161
 * @param array $SETTINGS
4162
 * @return string
4163
 */
4164
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4165
{
4166
    $session = SessionManager::getSession();
4167
    // Check if user exists
4168
    $userInfo = DB::queryFirstRow(
4169
        'SELECT login
4170
        FROM ' . prefixTable('users') . '
4171
        WHERE id = %i',
4172
        $userId
4173
    );
4174
4175
    if (DB::count() > 0) {
4176
        $now = (int) time();
4177
        // Prepare file content
4178
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4179
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4180
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4181
            "Public Key:\n".$session->get('user-public_key')."\n\n".
4182
            "Private Key:\n".$session->get('user-private_key')."\n\n";
4183
4184
        // Update user's keys_recovery_time
4185
        DB::update(
4186
            prefixTable('users'),
4187
            [
4188
                'keys_recovery_time' => $now,
4189
            ],
4190
            'id=%i',
4191
            $userId
4192
        );
4193
        $session->set('user-keys_recovery_time', $now);
4194
4195
        //Log into DB the user's disconnection
4196
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4197
        
4198
        // Return data
4199
        return prepareExchangedData(
4200
            array(
4201
                'error' => false,
4202
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4203
                'timestamp' => $now,
4204
                'content' => base64_encode($export_value),
4205
                'login' => $userInfo['login'],
4206
            ),
4207
            'encode'
4208
        );
4209
    }
4210
4211
    return prepareExchangedData(
4212
        array(
4213
            'error' => true,
4214
            'datetime' => '',
4215
        ),
4216
        'encode'
4217
    );
4218
}
4219
4220
/**
4221
 * Permits to load expected classes
4222
 *
4223
 * @param string $className
4224
 * @return void
4225
 */
4226
function loadClasses(string $className = ''): void
4227
{
4228
    require_once __DIR__. '/../includes/config/include.php';
4229
    require_once __DIR__. '/../includes/config/settings.php';
4230
    require_once __DIR__.'/../vendor/autoload.php';
4231
4232
    if (defined('DB_PASSWD_CLEAR') === false) {
4233
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4234
    }
4235
4236
    if (empty($className) === false) {
4237
        // Load class DB
4238
        if ((string) $className === 'DB') {
4239
            //Connect to DB
4240
            DB::$host = DB_HOST;
4241
            DB::$user = DB_USER;
4242
            DB::$password = DB_PASSWD_CLEAR;
4243
            DB::$dbName = DB_NAME;
4244
            DB::$port = DB_PORT;
4245
            DB::$encoding = DB_ENCODING;
4246
            DB::$ssl = DB_SSL;
4247
            DB::$connect_options = DB_CONNECT_OPTIONS;
4248
        }
4249
    }
4250
}
4251
4252
/**
4253
 * Returns the page the user is visiting.
4254
 *
4255
 * @return string The page name
4256
 */
4257
function getCurrectPage($SETTINGS)
4258
{
4259
    
4260
    $request = SymfonyRequest::createFromGlobals();
4261
4262
    // Parse the url
4263
    parse_str(
4264
        substr(
4265
            (string) $request->getRequestUri(),
4266
            strpos((string) $request->getRequestUri(), '?') + 1
4267
        ),
4268
        $result
4269
    );
4270
4271
    return $result['page'];
4272
}
4273
4274
/**
4275
 * Permits to return value if set
4276
 *
4277
 * @param string|int $value
4278
 * @param string|int|null $retFalse
4279
 * @param string|int $retTrue
4280
 * @return mixed
4281
 */
4282
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4283
{
4284
4285
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4286
}
4287
4288
4289
/**
4290
 * SEnd email to user
4291
 *
4292
 * @param string $post_receipt
4293
 * @param string $post_body
4294
 * @param string $post_subject
4295
 * @param array $post_replace
4296
 * @param boolean $immediate_email
4297
 * @param string $encryptedUserPassword
4298
 * @return string
4299
 */
4300
function sendMailToUser(
4301
    string $post_receipt,
4302
    string $post_body,
4303
    string $post_subject,
4304
    array $post_replace,
4305
    bool $immediate_email = false,
4306
    $encryptedUserPassword = ''
4307
): ?string {
4308
    global $SETTINGS;
4309
    $emailSettings = new EmailSettings($SETTINGS);
4310
    $emailService = new EmailService();
4311
4312
    // Sanitize inputs
4313
    $post_receipt = filter_var($post_receipt, FILTER_SANITIZE_EMAIL);
4314
    $post_subject = htmlspecialchars($post_subject, ENT_QUOTES, 'UTF-8');
4315
    $post_body = htmlspecialchars($post_body, ENT_QUOTES, 'UTF-8');
4316
4317
    if (count($post_replace) > 0) {
4318
        $post_body = str_replace(
4319
            array_keys($post_replace),
4320
            array_values($post_replace),
4321
            $post_body
4322
        );
4323
    }
4324
4325
    // Remove newlines to prevent header injection
4326
    $post_body = str_replace(array("\r", "\n"), '', $post_body);    
4327
4328
    if ($immediate_email === true) {
4329
        // Send email
4330
        $ret = $emailService->sendMail(
4331
            $post_subject,
4332
            $post_body,
0 ignored issues
show
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4302
  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 4315
  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 4326
  2. 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 4302
  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 4315
  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 4326
  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 4302
  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 4315
  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 4326
  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 4302
  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 4315
  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 4326
  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 4302
  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 4315
  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 4326
  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 4302
  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 4315
  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 4326

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 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 4302
  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 4315
  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 4326
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 77
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 77
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4302
  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 4315
  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 4326
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 81
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 75
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 93
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 118
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 152
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 185
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 841
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 882
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4302
  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 4315
  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 4326
  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 4302
  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 4315
  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 4326
  5. 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 4302
  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 4315
  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 4326
  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 4302
  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 4315
  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 4326

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...
4333
            $post_receipt,
4334
            $emailSettings,
4335
            '',
4336
            false
4337
        );
4338
    
4339
        $ret = json_decode($ret, true);
4340
    
4341
        return prepareExchangedData(
4342
            array(
4343
                'error' => empty($ret['error']) === true ? false : true,
4344
                'message' => $ret['message'],
4345
            ),
4346
            'encode'
4347
        );
4348
    } else {
4349
        // Send through task handler
4350
        prepareSendingEmail(
4351
            $post_subject,
4352
            $post_body,
4353
            $post_receipt,
4354
            "",
4355
            $encryptedUserPassword,
4356
        );
4357
    }
4358
4359
    return null;
4360
}
4361
4362
/**
4363
 * Converts a password strengh value to zxcvbn level
4364
 * 
4365
 * @param integer $passwordStrength
4366
 * 
4367
 * @return integer
4368
 */
4369
function convertPasswordStrength($passwordStrength): int
4370
{
4371
    if ($passwordStrength === 0) {
4372
        return TP_PW_STRENGTH_1;
4373
    } else if ($passwordStrength === 1) {
4374
        return TP_PW_STRENGTH_2;
4375
    } else if ($passwordStrength === 2) {
4376
        return TP_PW_STRENGTH_3;
4377
    } else if ($passwordStrength === 3) {
4378
        return TP_PW_STRENGTH_4;
4379
    } else {
4380
        return TP_PW_STRENGTH_5;
4381
    }
4382
}
4383
4384
/**
4385
 * Vérifie si les IDs d'un tableau existent bien dans la table.
4386
 *
4387
 * @param array $ids - Tableau d'IDs à vérifier
4388
 * @param string $tableName - Nom de la table dans laquelle vérifier les IDs
4389
 * @param string $fieldName - Nom du champ dans lequel vérifier les IDs
4390
 * @return array - IDs qui n'existent pas dans la table
4391
 */
4392
function checkIdsExist(array $ids, string $tableName, string $fieldName) : array
4393
{
4394
    // Assure-toi que le tableau d'IDs n'est pas vide
4395
    if (empty($ids)) {
4396
        return [];
4397
    }
4398
4399
    // Nettoyage des IDs pour éviter les injections SQL
4400
    $ids = array_map('intval', $ids);  // Assure que chaque ID est un entier
4401
4402
    // Construction de la requête SQL pour vérifier les IDs dans la table
4403
    $result = DB::query('SELECT id FROM ' . prefixTable($tableName) . ' WHERE ' . $fieldName . ' IN %li', $ids);
4404
4405
    // Extraire les IDs existants de la table
4406
    $existingIds = array_column($result, 'id');
4407
4408
    // Trouver les IDs manquants en comparant les deux tableaux
4409
    $missingIds = array_diff($ids, $existingIds);
4410
4411
    return $missingIds; // Renvoie les IDs qui n'existent pas dans la table
4412
}
4413
4414
/**
4415
 * Check that a password is strong. The password needs to have at least :
4416
 *   - length >= 10.
4417
 *   - Uppercase and lowercase chars.
4418
 *   - Number or special char.
4419
 *   - Not contain username, name or mail part.
4420
 *   - Different from previous password.
4421
 * 
4422
 * @param string $password - Password to ckeck.
4423
 * @return bool - true if the password is strong, false otherwise.
4424
 */
4425
function isPasswordStrong($password) {
4426
    $session = SessionManager::getSession();
4427
4428
    // Password can't contain login, name or lastname
4429
    $forbiddenWords = [
4430
        $session->get('user-login'),
4431
        $session->get('user-name'),
4432
        $session->get('user-lastname'),
4433
    ];
4434
4435
    // Cut out the email
4436
    if ($email = $session->get('user-email')) {
4437
        $emailParts = explode('@', $email);
4438
4439
        if (count($emailParts) === 2) {
4440
            // Mail username (removed @domain.tld)
4441
            $forbiddenWords[] = $emailParts[0];
4442
4443
            // Organisation name (removed username@ and .tld)
4444
            $domain = explode('.', $emailParts[1]);
4445
            if (count($domain) > 1)
4446
                $forbiddenWords[] = $domain[0];
4447
        }
4448
    }
4449
4450
    // Search forbidden words in password
4451
    foreach ($forbiddenWords as $word) {
4452
        if (empty($word))
4453
            continue;
4454
4455
        // Stop if forbidden word found in password
4456
        if (stripos($password, $word) !== false)
4457
            return false;
4458
    }
4459
4460
    // Get password complexity
4461
    $length = strlen($password);
4462
    $hasUppercase = preg_match('/[A-Z]/', $password);
4463
    $hasLowercase = preg_match('/[a-z]/', $password);
4464
    $hasNumber = preg_match('/[0-9]/', $password);
4465
    $hasSpecialChar = preg_match('/[\W_]/', $password);
4466
4467
    // Get current user hash
4468
    $userHash = DB::queryFirstRow(
4469
        "SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
4470
        $session->get('user-id')
4471
    )['pw'];
4472
4473
    $passwordManager = new PasswordManager();
4474
    
4475
    return $length >= 8
4476
           && $hasUppercase
4477
           && $hasLowercase
4478
           && ($hasNumber || $hasSpecialChar)
4479
           && !$passwordManager->verifyPassword($userHash, $password);
4480
}
4481