Completed
Push — master ( 958461...fdce8a )
by Nils
47s queued 17s
created

handleUserKeys()   F

Complexity

Conditions 20
Paths 139

Size

Total Lines 164
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 20
eloc 88
c 7
b 0
f 0
nc 139
nop 12
dl 0
loc 164
rs 3.8416

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

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

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

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

4340
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4341
            $post_body
4342
        );
4343
    }
4344
4345
    if ($immediate_email === true) {
4346
        
4347
        $ret = $emailService->sendMail(
4348
            $post_subject,
4349
            $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 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328

Used in path-write context

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

General Strategies to prevent injection

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

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

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

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

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328

Used in path-write context

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

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
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 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4328

Used in path-write context

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

General Strategies to prevent injection

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

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

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

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