Passed
Push — master ( f323ac...806c75 )
by Nils
04:16
created

notifyOnChange()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 4
eloc 26
c 5
b 0
f 0
nc 2
nop 3
dl 0
loc 39
rs 9.504
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 *
15
 * @file      main.functions.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2022 Teampass.net
21
 *
22
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
23
 * ---
24
 *
25
 * @see       https://www.teampass.net
26
 */
27
28
use LdapRecord\Connection;
29
use ForceUTF8\Encoding;
30
31
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
32
    //die('Hacking attempt...');
33
}
34
35
// Load config if $SETTINGS not defined
36
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
37
    include_once __DIR__ . '/../includes/config/tp.config.php';
38
}
39
40
header('Content-type: text/html; charset=utf-8');
41
header('Cache-Control: no-cache, must-revalidate');
42
/**
43
 * Convert language code to string.
44
 *
45
 * @param string $string String to get
46
 */
47
function langHdl(string $string): string
48
{
49
    if (empty($string) === true) {
50
        // Manage error
51
        return 'ERROR in language strings!';
52
    }
53
54
    // Load superglobal
55
    if (file_exists(__DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
56
        include_once __DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
57
    } elseif (file_exists(__DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
58
        include_once __DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
59
    } elseif (file_exists(__DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
60
        include_once __DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
61
    } else {
62
        throw new Exception("Error file '/includes/libraries/protect/SuperGlobal/SuperGlobal.php' not exists", 1);
63
    }
64
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
65
    // Get language string
66
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
67
    if (isset($session_language) === false) {
68
        /* 
69
            Load the English version to $_SESSION so we don't 
70
            return bad JSON (multiple includes add BOM characters to the json returned 
71
            which makes jquery unhappy on the UI, especially on the log page)
72
            and improve performance by avoiding to include the file for every missing strings.
73
        */
74
        $session_language = $_SESSION['teampass']['en_lang'][trim($string)];
75
        if (isset($session_language) === false) {
76
            $_SESSION['teampass']['en_lang'] = include_once __DIR__. '/../includes/language/english.php';
77
            $session_language = $_SESSION['teampass']['en_lang'][trim($string)];
78
        }
79
    }
80
    // If after all this, we still don't have the string even in english (especially with old logs), return the language code
81
    if (empty($session_language) === true) {
82
        return trim($string);
83
    }
84
    return addslashes($session_language);
85
}
86
87
/**
88
 * genHash().
89
 *
90
 * Generate a hash for user login
91
 *
92
 * @param string $password What password
93
 * @param string $cost     What cost
94
 *
95
 * @return string|void
96
 */
97
function bCrypt(
98
    string $password,
99
    string $cost
100
): ?string
101
{
102
    $salt = sprintf('$2y$%02d$', $cost);
103
    if (function_exists('openssl_random_pseudo_bytes')) {
104
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
105
    } else {
106
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
107
        for ($i = 0; $i < 22; ++$i) {
108
            $salt .= $chars[mt_rand(0, 63)];
109
        }
110
    }
111
112
    return crypt($password, $salt);
113
}
114
115
/**
116
 * Defuse cryption function.
117
 *
118
 * @param string $message   what to de/crypt
119
 * @param string $ascii_key key to use
120
 * @param string $type      operation to perform
121
 * @param array  $SETTINGS  Teampass settings
122
 *
123
 * @return array
124
 */
125
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
126
{
127
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH . '/teampass-seckey.txt') : $ascii_key;
128
    $err = false;
129
    
130
    $path = __DIR__.'/../includes/libraries/Encryption/Encryption/';
131
132
    include_once $path . 'Exception/CryptoException.php';
133
    include_once $path . 'Exception/BadFormatException.php';
134
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
135
    include_once $path . 'Exception/IOException.php';
136
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
137
    include_once $path . 'Crypto.php';
138
    include_once $path . 'Encoding.php';
139
    include_once $path . 'DerivedKeys.php';
140
    include_once $path . 'Key.php';
141
    include_once $path . 'KeyOrPassword.php';
142
    include_once $path . 'File.php';
143
    include_once $path . 'RuntimeTests.php';
144
    include_once $path . 'KeyProtectedByPassword.php';
145
    include_once $path . 'Core.php';
146
    
147
    // convert KEY
148
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
149
    try {
150
        if ($type === 'encrypt') {
151
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
152
        } elseif ($type === 'decrypt') {
153
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
154
        }
155
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
156
        $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.';
157
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
158
        $err = $ex;
159
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
160
        $err = $ex;
161
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
162
        $err = $ex;
163
    } catch (Defuse\Crypto\Exception\IOException $ex) {
164
        $err = $ex;
165
    }
166
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
167
168
    return [
169
        'string' => $text ?? '',
170
        'error' => $err,
171
    ];
172
}
173
174
/**
175
 * Generating a defuse key.
176
 *
177
 * @return string
178
 */
179
function defuse_generate_key()
180
{
181
    // load PhpEncryption library
182
    if (file_exists('../includes/config/tp.config.php') === true) {
183
        $path = '../includes/libraries/Encryption/Encryption/';
184
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
185
        $path = './includes/libraries/Encryption/Encryption/';
186
    } else {
187
        $path = '../includes/libraries/Encryption/Encryption/';
188
    }
189
190
    include_once $path . 'Exception/CryptoException.php';
191
    include_once $path . 'Exception/BadFormatException.php';
192
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
193
    include_once $path . 'Exception/IOException.php';
194
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
195
    include_once $path . 'Crypto.php';
196
    include_once $path . 'Encoding.php';
197
    include_once $path . 'DerivedKeys.php';
198
    include_once $path . 'Key.php';
199
    include_once $path . 'KeyOrPassword.php';
200
    include_once $path . 'File.php';
201
    include_once $path . 'RuntimeTests.php';
202
    include_once $path . 'KeyProtectedByPassword.php';
203
    include_once $path . 'Core.php';
204
205
    $key = \Defuse\Crypto\Key::createNewRandomKey();
206
    $key = $key->saveToAsciiSafeString();
207
    return $key;
208
}
209
210
/**
211
 * Generate a Defuse personal key.
212
 *
213
 * @param string $psk psk used
214
 *
215
 * @return string
216
 */
217
function defuse_generate_personal_key(string $psk): string
218
{
219
    // load PhpEncryption library
220
    if (file_exists('../includes/config/tp.config.php') === true) {
221
        $path = '../includes/libraries/Encryption/Encryption/';
222
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
223
        $path = './includes/libraries/Encryption/Encryption/';
224
    } else {
225
        $path = '../includes/libraries/Encryption/Encryption/';
226
    }
227
228
    include_once $path . 'Exception/CryptoException.php';
229
    include_once $path . 'Exception/BadFormatException.php';
230
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
231
    include_once $path . 'Exception/IOException.php';
232
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
233
    include_once $path . 'Crypto.php';
234
    include_once $path . 'Encoding.php';
235
    include_once $path . 'DerivedKeys.php';
236
    include_once $path . 'Key.php';
237
    include_once $path . 'KeyOrPassword.php';
238
    include_once $path . 'File.php';
239
    include_once $path . 'RuntimeTests.php';
240
    include_once $path . 'KeyProtectedByPassword.php';
241
    include_once $path . 'Core.php';
242
    
243
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
244
    return $protected_key->saveToAsciiSafeString(); // save this in user table
245
}
246
247
/**
248
 * Validate persoanl key with defuse.
249
 *
250
 * @param string $psk                   the user's psk
251
 * @param string $protected_key_encoded special key
252
 *
253
 * @return string
254
 */
255
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
256
{
257
    // load PhpEncryption library
258
    if (file_exists('../includes/config/tp.config.php') === true) {
259
        $path = '../includes/libraries/Encryption/Encryption/';
260
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
261
        $path = './includes/libraries/Encryption/Encryption/';
262
    } else {
263
        $path = '../includes/libraries/Encryption/Encryption/';
264
    }
265
266
    include_once $path . 'Exception/CryptoException.php';
267
    include_once $path . 'Exception/BadFormatException.php';
268
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
269
    include_once $path . 'Exception/IOException.php';
270
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
271
    include_once $path . 'Crypto.php';
272
    include_once $path . 'Encoding.php';
273
    include_once $path . 'DerivedKeys.php';
274
    include_once $path . 'Key.php';
275
    include_once $path . 'KeyOrPassword.php';
276
    include_once $path . 'File.php';
277
    include_once $path . 'RuntimeTests.php';
278
    include_once $path . 'KeyProtectedByPassword.php';
279
    include_once $path . 'Core.php';
280
281
    try {
282
        $protected_key_encoded = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
283
        $user_key = $protected_key_encoded->unlockKey($psk);
284
        $user_key_encoded = $user_key->saveToAsciiSafeString();
285
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
286
        return 'Error - Major issue as the encryption is broken.';
287
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
288
        return 'Error - The saltkey is not the correct one.';
289
    }
290
291
    return $user_key_encoded;
292
    // store it in session once user has entered his psk
293
}
294
295
/**
296
 * Decrypt a defuse string if encrypted.
297
 *
298
 * @param string $value Encrypted string
299
 *
300
 * @return string Decrypted string
301
 */
302
function defuseReturnDecrypted(string $value, $SETTINGS): string
303
{
304
    if (substr($value, 0, 3) === 'def') {
305
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
306
    }
307
308
    return $value;
309
}
310
311
/**
312
 * Trims a string depending on a specific string.
313
 *
314
 * @param string|array $chaine  what to trim
315
 * @param string       $element trim on what
316
 *
317
 * @return string
318
 */
319
function trimElement($chaine, string $element): string
320
{
321
    if (! empty($chaine)) {
322
        if (is_array($chaine) === true) {
323
            $chaine = implode(';', $chaine);
324
        }
325
        $chaine = trim($chaine);
326
        if (substr($chaine, 0, 1) === $element) {
327
            $chaine = substr($chaine, 1);
328
        }
329
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
330
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
331
        }
332
    }
333
334
    return $chaine;
335
}
336
337
/**
338
 * Permits to suppress all "special" characters from string.
339
 *
340
 * @param string $string  what to clean
341
 * @param bool   $special use of special chars?
342
 *
343
 * @return string
344
 */
345
function cleanString(string $string, bool $special = false): string
346
{
347
    // Create temporary table for special characters escape
348
    $tabSpecialChar = [];
349
    for ($i = 0; $i <= 31; ++$i) {
350
        $tabSpecialChar[] = chr($i);
351
    }
352
    array_push($tabSpecialChar, '<br />');
353
    if ((int) $special === 1) {
354
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
355
    }
356
357
    return str_replace($tabSpecialChar, "\n", $string);
358
}
359
360
/**
361
 * Erro manager for DB.
362
 *
363
 * @param array $params output from query
364
 *
365
 * @return void
366
 */
367
function db_error_handler(array $params): void
368
{
369
    echo 'Error: ' . $params['error'] . "<br>\n";
370
    echo 'Query: ' . $params['query'] . "<br>\n";
371
    throw new Exception('Error - Query', 1);
372
}
373
374
/**
375
 * Identify user's rights
376
 *
377
 * @param string|array $groupesVisiblesUser  [description]
378
 * @param string|array $groupesInterditsUser [description]
379
 * @param string       $isAdmin              [description]
380
 * @param string       $idFonctions          [description]
381
 *
382
 * @return bool
383
 */
384
function identifyUserRights(
385
    $groupesVisiblesUser,
386
    $groupesInterditsUser,
387
    $isAdmin,
388
    $idFonctions,
389
    $SETTINGS
390
) {
391
    //load ClassLoader
392
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
393
    // Load superglobal
394
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
395
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
396
    //Connect to DB
397
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
398
    if (defined('DB_PASSWD_CLEAR') === false) {
399
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
400
    }
401
    DB::$host = DB_HOST;
402
    DB::$user = DB_USER;
403
    DB::$password = DB_PASSWD_CLEAR;
404
    DB::$dbName = DB_NAME;
405
    DB::$port = DB_PORT;
406
    DB::$encoding = DB_ENCODING;
407
    DB::$ssl = DB_SSL;
408
    DB::$connect_options = DB_CONNECT_OPTIONS;
409
    //Build tree
410
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
411
    $tree->register();
412
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
413
414
    // Check if user is ADMINISTRATOR    
415
    (int) $isAdmin === 1 ?
416
        identAdmin(
417
            $idFonctions,
418
            $SETTINGS, /** @scrutinizer ignore-type */
419
            $tree
420
        )
421
        :
422
        identUser(
423
            $groupesVisiblesUser,
424
            $groupesInterditsUser,
425
            $idFonctions,
426
            $SETTINGS, /** @scrutinizer ignore-type */
427
            $tree
428
        );
429
430
    // update user's timestamp
431
    DB::update(
432
        prefixTable('users'),
433
        [
434
            'timestamp' => time(),
435
        ],
436
        'id=%i',
437
        $superGlobal->get('user_id', 'SESSION')
438
    );
439
440
    return true;
441
}
442
443
/**
444
 * Identify administrator.
445
 *
446
 * @param string $idFonctions Roles of user
447
 * @param array  $SETTINGS    Teampass settings
448
 * @param array  $tree        Tree of folders
449
 *
450
 * @return bool
451
 */
452
function identAdmin($idFonctions, $SETTINGS, $tree)
453
{
454
    // Load superglobal
455
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
456
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
457
    // Init
458
    $groupesVisibles = [];
459
    $superGlobal->put('personal_folders', [], 'SESSION');
460
    $superGlobal->put('groupes_visibles', [], 'SESSION');
461
    $superGlobal->put('no_access_folders', [], 'SESSION');
462
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
463
    $superGlobal->put('read_only_folders', [], 'SESSION');
464
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
465
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
466
    $superGlobal->put('list_folders_limited', [], 'SESSION');
467
    $superGlobal->put('no_access_folders', [], 'SESSION');
468
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
469
    // Get superglobals
470
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
471
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
472
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
473
    // Get list of Folders
474
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
475
    foreach ($rows as $record) {
476
        array_push($groupesVisibles, $record['id']);
477
    }
478
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
479
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
480
    // Exclude all PF
481
    $where = new WhereClause('and');
482
    // create a WHERE statement of pieces joined by ANDs
483
    $where->add('personal_folder=%i', 1);
484
    if (
485
        isset($SETTINGS['enable_pf_feature']) === true
486
        && (int) $SETTINGS['enable_pf_feature'] === 1
487
    ) {
488
        $where->add('title=%s', $globalsUserId);
489
        $where->negateLast();
490
    }
491
    // Get ID of personal folder
492
    $persfld = DB::queryfirstrow(
493
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
494
        $globalsUserId
495
    );
496
    if (empty($persfld['id']) === false) {
497
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
498
            array_push($globalsVisibleFolders, $persfld['id']);
499
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
500
            // get all descendants
501
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
502
            $tree->rebuild();
503
            $tst = $tree->getDescendants($persfld['id']);
504
            foreach ($tst as $t) {
505
                array_push($globalsVisibleFolders, $t->id);
506
                array_push($globalsPersonalVisibleFolders, $t->id);
507
            }
508
        }
509
    }
510
511
    // get complete list of ROLES
512
    $tmp = explode(';', $idFonctions);
513
    $rows = DB::query(
514
        'SELECT * FROM ' . prefixTable('roles_title') . '
515
        ORDER BY title ASC'
516
    );
517
    foreach ($rows as $record) {
518
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
519
            array_push($tmp, $record['id']);
520
        }
521
    }
522
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
523
    $superGlobal->put('is_admin', 1, 'SESSION');
524
    // Check if admin has created Folders and Roles
525
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
526
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
527
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
528
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
529
530
    return true;
531
}
532
533
/**
534
 * Permits to convert an element to array.
535
 *
536
 * @param string|array $element Any value to be returned as array
537
 *
538
 * @return array
539
 */
540
function convertToArray($element): array
541
{
542
    if (is_string($element) === true) {
543
        if (empty($element) === true) {
544
            return [];
545
        }
546
        return explode(
547
            ';',
548
            trimElement($element, ';')
549
        );
550
    }
551
    return $element;
552
}
553
554
/**
555
 * Defines the rights the user has.
556
 *
557
 * @param string|array $allowedFolders  Allowed folders
558
 * @param string|array $noAccessFolders Not allowed folders
559
 * @param string|array $userRoles       Roles of user
560
 * @param array        $SETTINGS        Teampass settings
561
 * @param object       $tree            Tree of folders
562
 * 
563
 * @return bool
564
 */
565
function identUser(
566
    $allowedFolders,
567
    $noAccessFolders,
568
    $userRoles,
569
    array $SETTINGS,
570
    object $tree
571
) {
572
    // Load superglobal
573
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
574
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
575
    // Init
576
    $superGlobal->put('groupes_visibles', [], 'SESSION');
577
    $superGlobal->put('personal_folders', [], 'SESSION');
578
    $superGlobal->put('no_access_folders', [], 'SESSION');
579
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
580
    $superGlobal->put('read_only_folders', [], 'SESSION');
581
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
582
    $superGlobal->put('is_admin', 0, 'SESSION');
583
    // init
584
    $personalFolders = [];
585
    $readOnlyFolders = [];
586
    $noAccessPersonalFolders = [];
587
    $restrictedFoldersForItems = [];
588
    $foldersLimited = [];
589
    $foldersLimitedFull = [];
590
    $allowedFoldersByRoles = [];
591
    // Get superglobals
592
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
593
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
594
    // Ensure consistency in array format
595
    $noAccessFolders = convertToArray($noAccessFolders);
596
    $userRoles = convertToArray($userRoles);
597
    $allowedFolders = convertToArray($allowedFolders);
598
599
    // Get list of folders depending on Roles
600
    $arrays = identUserGetFoldersFromRoles(
601
        $userRoles,
602
        $allowedFoldersByRoles,
603
        $readOnlyFolders,
604
        $allowedFolders
605
    );
606
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
607
    $readOnlyFolders = $arrays['readOnlyFolders'];
608
609
    // Does this user is allowed to see other items
610
    $inc = 0;
611
    $rows = DB::query(
612
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
613
            WHERE restricted_to LIKE %ss AND inactif = %s'.
614
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
615
        $globalsUserId . ';',
616
        '0'
617
    );
618
    foreach ($rows as $record) {
619
        // Exclude restriction on item if folder is fully accessible
620
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
621
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
622
            ++$inc;
623
        //}
624
    }
625
626
    // Check for the users roles if some specific rights exist on items
627
    $rows = DB::query(
628
        'SELECT i.id_tree, r.item_id
629
        FROM ' . prefixTable('items') . ' as i
630
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
631
        WHERE r.role_id IN %li AND i.id_tree <> ""
632
        ORDER BY i.id_tree ASC',
633
        $userRoles
634
    );
635
    $inc = 0;
636
    foreach ($rows as $record) {
637
        //if (isset($record['id_tree'])) {
638
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
639
            array_push($foldersLimitedFull, $record['item_id']);
640
            ++$inc;
641
        //}
642
    }
643
644
    // Get list of Personal Folders
645
    $arrays = identUserGetPFList(
646
        $globalsPersonalFolders,
647
        $allowedFolders,
648
        $globalsUserId,
649
        $personalFolders,
650
        $noAccessPersonalFolders,
651
        $foldersLimitedFull,
652
        $allowedFoldersByRoles,
653
        $restrictedFoldersForItems,
654
        $readOnlyFolders,
655
        $noAccessFolders,
656
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
657
        $tree
658
    );
659
    $allowedFolders = $arrays['allowedFolders'];
660
    $personalFolders = $arrays['personalFolders'];
661
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
662
663
    // Return data
664
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
665
    $superGlobal->put('groupes_visibles', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC), 'SESSION');
666
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
667
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
668
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
669
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
670
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
671
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
672
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
673
    $superGlobal->put(
674
        'all_folders_including_no_access',
675
        array_unique(array_merge(
676
            $allowedFolders,
677
            $personalFolders,
678
            $noAccessFolders,
679
            $readOnlyFolders
680
        ), SORT_NUMERIC),
681
        'SESSION'
682
    );
683
    // Folders and Roles numbers
684
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
685
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
686
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
687
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
688
    // check if change proposals on User's items
689
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
690
        $countNewItems = DB::query(
691
            'SELECT COUNT(*)
692
            FROM ' . prefixTable('items_change') . ' AS c
693
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
694
            WHERE i.action = %s AND i.id_user = %i',
695
            'at_creation',
696
            $globalsUserId
697
        );
698
        $superGlobal->put('nb_item_change_proposals', $countNewItems, 'SESSION');
699
    } else {
700
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
701
    }
702
703
    return true;
704
}
705
706
/**
707
 * Get list of folders depending on Roles
708
 * 
709
 * @param array $userRoles
710
 * @param array $allowedFoldersByRoles
711
 * @param array $readOnlyFolders
712
 * @param array $allowedFolders
713
 * 
714
 * @return array
715
 */
716
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
717
{
718
    $rows = DB::query(
719
        'SELECT *
720
        FROM ' . prefixTable('roles_values') . '
721
        WHERE role_id IN %li AND type IN %ls',
722
        $userRoles,
723
        ['W', 'ND', 'NE', 'NDNE', 'R']
724
    );
725
    foreach ($rows as $record) {
726
        if ($record['type'] === 'R') {
727
            array_push($readOnlyFolders, $record['folder_id']);
728
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
729
            array_push($allowedFoldersByRoles, $record['folder_id']);
730
        }
731
    }
732
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
733
    $readOnlyFolders = array_unique($readOnlyFolders);
734
    // Clean arrays
735
    foreach ($allowedFoldersByRoles as $value) {
736
        $key = array_search($value, $readOnlyFolders);
737
        if ($key !== false) {
738
            unset($readOnlyFolders[$key]);
739
        }
740
    }
741
742
    return [
743
        'readOnlyFolders' => $readOnlyFolders,
744
        'allowedFoldersByRoles' => $allowedFoldersByRoles
745
    ];
746
}
747
748
/**
749
 * Get list of Personal Folders
750
 * 
751
 * @param int $globalsPersonalFolders
752
 * @param array $allowedFolders
753
 * @param int $globalsUserId
754
 * @param array $personalFolders
755
 * @param array $noAccessPersonalFolders
756
 * @param array $foldersLimitedFull
757
 * @param array $allowedFoldersByRoles
758
 * @param array $restrictedFoldersForItems
759
 * @param array $readOnlyFolders
760
 * @param array $noAccessFolders
761
 * @param int $enablePfFeature
762
 * @param object $tree
763
 * 
764
 * @return array
765
 */
766
function identUserGetPFList(
767
    $globalsPersonalFolders,
768
    $allowedFolders,
769
    $globalsUserId,
770
    $personalFolders,
771
    $noAccessPersonalFolders,
772
    $foldersLimitedFull,
773
    $allowedFoldersByRoles,
774
    $restrictedFoldersForItems,
775
    $readOnlyFolders,
776
    $noAccessFolders,
777
    $enablePfFeature,
778
    $tree
779
)
780
{
781
    if (
782
        (int) $enablePfFeature === 1
783
        && (int) $globalsPersonalFolders === 1
784
    ) {
785
        $persoFld = DB::queryfirstrow(
786
            'SELECT id
787
            FROM ' . prefixTable('nested_tree') . '
788
            WHERE title = %s AND personal_folder = %i'.
789
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
790
            $globalsUserId,
791
            1
792
        );
793
        if (empty($persoFld['id']) === false) {
794
            array_push($personalFolders, $persoFld['id']);
795
            array_push($allowedFolders, $persoFld['id']);
796
            // get all descendants
797
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
798
            foreach ($ids as $id) {
799
                //array_push($allowedFolders, $id);
800
                array_push($personalFolders, $id);
801
            }
802
        }
803
    }
804
    
805
    // Exclude all other PF
806
    $where = new WhereClause('and');
807
    $where->add('personal_folder=%i', 1);
808
    if (count($personalFolders) > 0) {
809
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
810
    }
811
    if (
812
        (int) $enablePfFeature === 1
813
        && (int) $globalsPersonalFolders === 1
814
    ) {
815
        $where->add('title=%s', $globalsUserId);
816
        $where->negateLast();
817
    }
818
    $persoFlds = DB::query(
819
        'SELECT id
820
        FROM ' . prefixTable('nested_tree') . '
821
        WHERE %l',
822
        $where
823
    );
824
    foreach ($persoFlds as $persoFldId) {
825
        array_push($noAccessPersonalFolders, $persoFldId['id']);
826
    }
827
828
    // All folders visibles
829
    $allowedFolders = array_unique(array_merge(
830
        $allowedFolders,
831
        $foldersLimitedFull,
832
        $allowedFoldersByRoles,
833
        $restrictedFoldersForItems,
834
        $readOnlyFolders
835
    ), SORT_NUMERIC);
836
    // Exclude from allowed folders all the specific user forbidden folders
837
    if (count($noAccessFolders) > 0) {
838
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
839
    }
840
841
    return [
842
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
843
        'personalFolders' => $personalFolders,
844
        'noAccessPersonalFolders' => $noAccessPersonalFolders
845
    ];
846
}
847
848
849
/**
850
 * Update the CACHE table.
851
 *
852
 * @param string $action   What to do
853
 * @param array  $SETTINGS Teampass settings
854
 * @param int    $ident    Ident format
855
 * 
856
 * @return void
857
 */
858
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
859
{
860
    if ($action === 'reload') {
861
        // Rebuild full cache table
862
        cacheTableRefresh($SETTINGS);
863
    } elseif ($action === 'update_value' && is_null($ident) === false) {
864
        // UPDATE an item
865
        cacheTableUpdate($SETTINGS, $ident);
866
    } elseif ($action === 'add_value' && is_null($ident) === false) {
867
        // ADD an item
868
        cacheTableAdd($SETTINGS, $ident);
869
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
870
        // DELETE an item
871
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
872
    }
873
}
874
875
/**
876
 * Cache table - refresh.
877
 *
878
 * @param array $SETTINGS Teampass settings
879
 * 
880
 * @return void
881
 */
882
function cacheTableRefresh(array $SETTINGS): void
883
{
884
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
885
    //Connect to DB
886
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
887
    if (defined('DB_PASSWD_CLEAR') === false) {
888
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
889
    }
890
    DB::$host = DB_HOST;
891
    DB::$user = DB_USER;
892
    DB::$password = DB_PASSWD_CLEAR;
893
    DB::$dbName = DB_NAME;
894
    DB::$port = DB_PORT;
895
    DB::$encoding = DB_ENCODING;
896
    DB::$ssl = DB_SSL;
897
    DB::$connect_options = DB_CONNECT_OPTIONS;
898
    //Load Tree
899
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
900
    $tree->register();
901
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
902
    // truncate table
903
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
904
    // reload date
905
    $rows = DB::query(
906
        'SELECT *
907
        FROM ' . prefixTable('items') . ' as i
908
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
909
        AND l.action = %s
910
        AND i.inactif = %i',
911
        'at_creation',
912
        0
913
    );
914
    foreach ($rows as $record) {
915
        if (empty($record['id_tree']) === false) {
916
            // Get all TAGS
917
            $tags = '';
918
            $itemTags = DB::query(
919
                'SELECT tag
920
                FROM ' . prefixTable('tags') . '
921
                WHERE item_id = %i AND tag != ""',
922
                $record['id']
923
            );
924
            foreach ($itemTags as $itemTag) {
925
                $tags .= $itemTag['tag'] . ' ';
926
            }
927
928
            // Get renewal period
929
            $resNT = DB::queryfirstrow(
930
                'SELECT renewal_period
931
                FROM ' . prefixTable('nested_tree') . '
932
                WHERE id = %i',
933
                $record['id_tree']
934
            );
935
            // form id_tree to full foldername
936
            $folder = [];
937
            $arbo = $tree->getPath($record['id_tree'], true);
938
            foreach ($arbo as $elem) {
939
                // Check if title is the ID of a user
940
                if (is_numeric($elem->title) === true) {
941
                    // Is this a User id?
942
                    $user = DB::queryfirstrow(
943
                        'SELECT id, login
944
                        FROM ' . prefixTable('users') . '
945
                        WHERE id = %i',
946
                        $elem->title
947
                    );
948
                    if (count($user) > 0) {
949
                        $elem->title = $user['login'];
950
                    }
951
                }
952
                // Build path
953
                array_push($folder, stripslashes($elem->title));
954
            }
955
            // store data
956
            DB::insert(
957
                prefixTable('cache'),
958
                [
959
                    'id' => $record['id'],
960
                    'label' => $record['label'],
961
                    'description' => $record['description'] ?? '',
962
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
963
                    'tags' => $tags,
964
                    'id_tree' => $record['id_tree'],
965
                    'perso' => $record['perso'],
966
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
967
                    'login' => $record['login'] ?? '',
968
                    'folder' => implode(' > ', $folder),
969
                    'author' => $record['id_user'],
970
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
971
                    'timestamp' => $record['date'],
972
                ]
973
            );
974
        }
975
    }
976
}
977
978
/**
979
 * Cache table - update existing value.
980
 *
981
 * @param array  $SETTINGS Teampass settings
982
 * @param int    $ident    Ident format
983
 * 
984
 * @return void
985
 */
986
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
987
{
988
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
989
    // Load superglobal
990
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
991
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
992
    //Connect to DB
993
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
994
    if (defined('DB_PASSWD_CLEAR') === false) {
995
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
996
    }
997
    DB::$host = DB_HOST;
998
    DB::$user = DB_USER;
999
    DB::$password = DB_PASSWD_CLEAR;
1000
    DB::$dbName = DB_NAME;
1001
    DB::$port = DB_PORT;
1002
    DB::$encoding = DB_ENCODING;
1003
    DB::$ssl = DB_SSL;
1004
    DB::$connect_options = DB_CONNECT_OPTIONS;
1005
    //Load Tree
1006
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1007
    $tree->register();
1008
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1009
    // get new value from db
1010
    $data = DB::queryfirstrow(
1011
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1012
        FROM ' . prefixTable('items') . '
1013
        WHERE id=%i',
1014
        $ident
1015
    );
1016
    // Get all TAGS
1017
    $tags = '';
1018
    $itemTags = DB::query(
1019
        'SELECT tag
1020
            FROM ' . prefixTable('tags') . '
1021
            WHERE item_id = %i AND tag != ""',
1022
        $ident
1023
    );
1024
    foreach ($itemTags as $itemTag) {
1025
        $tags .= $itemTag['tag'] . ' ';
1026
    }
1027
    // form id_tree to full foldername
1028
    $folder = [];
1029
    $arbo = $tree->getPath($data['id_tree'], true);
1030
    foreach ($arbo as $elem) {
1031
        // Check if title is the ID of a user
1032
        if (is_numeric($elem->title) === true) {
1033
            // Is this a User id?
1034
            $user = DB::queryfirstrow(
1035
                'SELECT id, login
1036
                FROM ' . prefixTable('users') . '
1037
                WHERE id = %i',
1038
                $elem->title
1039
            );
1040
            if (count($user) > 0) {
1041
                $elem->title = $user['login'];
1042
            }
1043
        }
1044
        // Build path
1045
        array_push($folder, stripslashes($elem->title));
1046
    }
1047
    // finaly update
1048
    DB::update(
1049
        prefixTable('cache'),
1050
        [
1051
            'label' => $data['label'],
1052
            'description' => $data['description'],
1053
            'tags' => $tags,
1054
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1055
            'id_tree' => $data['id_tree'],
1056
            'perso' => $data['perso'],
1057
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1058
            'login' => $data['login'] ?? '',
1059
            'folder' => implode(' » ', $folder),
1060
            'author' => $superGlobal->get('user_id', 'SESSION'),
1061
        ],
1062
        'id = %i',
1063
        $ident
1064
    );
1065
}
1066
1067
/**
1068
 * Cache table - add new value.
1069
 *
1070
 * @param array  $SETTINGS Teampass settings
1071
 * @param int    $ident    Ident format
1072
 * 
1073
 * @return void
1074
 */
1075
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1076
{
1077
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1078
    // Load superglobal
1079
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1080
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1081
    // Get superglobals
1082
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1083
    //Connect to DB
1084
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1085
    if (defined('DB_PASSWD_CLEAR') === false) {
1086
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1087
    }
1088
    DB::$host = DB_HOST;
1089
    DB::$user = DB_USER;
1090
    DB::$password = DB_PASSWD_CLEAR;
1091
    DB::$dbName = DB_NAME;
1092
    DB::$port = DB_PORT;
1093
    DB::$encoding = DB_ENCODING;
1094
    DB::$ssl = DB_SSL;
1095
    DB::$connect_options = DB_CONNECT_OPTIONS;
1096
    //Load Tree
1097
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1098
    $tree->register();
1099
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1100
    // get new value from db
1101
    $data = DB::queryFirstRow(
1102
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1103
        FROM ' . prefixTable('items') . ' as i
1104
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1105
        WHERE i.id = %i
1106
        AND l.action = %s',
1107
        $ident,
1108
        'at_creation'
1109
    );
1110
    // Get all TAGS
1111
    $tags = '';
1112
    $itemTags = DB::query(
1113
        'SELECT tag
1114
            FROM ' . prefixTable('tags') . '
1115
            WHERE item_id = %i AND tag != ""',
1116
        $ident
1117
    );
1118
    foreach ($itemTags as $itemTag) {
1119
        $tags .= $itemTag['tag'] . ' ';
1120
    }
1121
    // form id_tree to full foldername
1122
    $folder = [];
1123
    $arbo = $tree->getPath($data['id_tree'], true);
1124
    foreach ($arbo as $elem) {
1125
        // Check if title is the ID of a user
1126
        if (is_numeric($elem->title) === true) {
1127
            // Is this a User id?
1128
            $user = DB::queryfirstrow(
1129
                'SELECT id, login
1130
                FROM ' . prefixTable('users') . '
1131
                WHERE id = %i',
1132
                $elem->title
1133
            );
1134
            if (count($user) > 0) {
1135
                $elem->title = $user['login'];
1136
            }
1137
        }
1138
        // Build path
1139
        array_push($folder, stripslashes($elem->title));
1140
    }
1141
    // finaly update
1142
    DB::insert(
1143
        prefixTable('cache'),
1144
        [
1145
            'id' => $data['id'],
1146
            'label' => $data['label'],
1147
            'description' => $data['description'],
1148
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1149
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1150
            'id_tree' => $data['id_tree'],
1151
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1152
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1153
            'login' => $data['login'] ?? '',
1154
            'folder' => implode(' » ', $folder),
1155
            'author' => $globalsUserId,
1156
            'timestamp' => $data['date'],
1157
        ]
1158
    );
1159
}
1160
1161
/**
1162
 * Do statistics.
1163
 *
1164
 * @param array $SETTINGS Teampass settings
1165
 *
1166
 * @return array
1167
 */
1168
function getStatisticsData(array $SETTINGS): array
1169
{
1170
    DB::query(
1171
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1172
        0
1173
    );
1174
    $counter_folders = DB::count();
1175
    DB::query(
1176
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1177
        1
1178
    );
1179
    $counter_folders_perso = DB::count();
1180
    DB::query(
1181
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1182
        0
1183
    );
1184
    $counter_items = DB::count();
1185
        DB::query(
1186
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1187
        1
1188
    );
1189
    $counter_items_perso = DB::count();
1190
        DB::query(
1191
        'SELECT id FROM ' . prefixTable('users') . ''
1192
    );
1193
    $counter_users = DB::count();
1194
        DB::query(
1195
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1196
        1
1197
    );
1198
    $admins = DB::count();
1199
    DB::query(
1200
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1201
        1
1202
    );
1203
    $managers = DB::count();
1204
    DB::query(
1205
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1206
        1
1207
    );
1208
    $readOnly = DB::count();
1209
    // list the languages
1210
    $usedLang = [];
1211
    $tp_languages = DB::query(
1212
        'SELECT name FROM ' . prefixTable('languages')
1213
    );
1214
    foreach ($tp_languages as $tp_language) {
1215
        DB::query(
1216
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1217
            $tp_language['name']
1218
        );
1219
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1220
    }
1221
1222
    // get list of ips
1223
    $usedIp = [];
1224
    $tp_ips = DB::query(
1225
        'SELECT user_ip FROM ' . prefixTable('users')
1226
    );
1227
    foreach ($tp_ips as $ip) {
1228
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1229
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1230
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1231
            $usedIp[$ip['user_ip']] = 1;
1232
        }
1233
    }
1234
1235
    return [
1236
        'error' => '',
1237
        'stat_phpversion' => phpversion(),
1238
        'stat_folders' => $counter_folders,
1239
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1240
        'stat_items' => $counter_items,
1241
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1242
        'stat_users' => $counter_users,
1243
        'stat_admins' => $admins,
1244
        'stat_managers' => $managers,
1245
        'stat_ro' => $readOnly,
1246
        'stat_kb' => $SETTINGS['enable_kb'],
1247
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1248
        'stat_fav' => $SETTINGS['enable_favourites'],
1249
        'stat_teampassversion' => TP_VERSION_FULL,
1250
        'stat_ldap' => $SETTINGS['ldap_mode'],
1251
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1252
        'stat_duo' => $SETTINGS['duo'],
1253
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1254
        'stat_api' => $SETTINGS['api'],
1255
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1256
        'stat_syslog' => $SETTINGS['syslog_enable'],
1257
        'stat_2fa' => $SETTINGS['google_authentication'],
1258
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1259
        'stat_mysqlversion' => DB::serverVersion(),
1260
        'stat_languages' => $usedLang,
1261
        'stat_country' => $usedIp,
1262
    ];
1263
}
1264
1265
/**
1266
 * Permits to prepare the way to send the email
1267
 * 
1268
 * @param string $subject       email subject
1269
 * @param string $body          email message
1270
 * @param string $email         email
1271
 * @param string $receiverName  Receiver name
1272
 * @param array  $SETTINGS      settings
1273
 *
1274
 * @return void
1275
 */
1276
function prepareSendingEmail(
1277
    $subject,
1278
    $body,
1279
    $email,
1280
    $receiverName,
1281
    $SETTINGS
1282
): void 
1283
{
1284
    if (isKeyExistingAndEqual('enable_tasks_manager', 1, $SETTINGS) === true) {
1285
        DB::insert(
1286
            prefixTable('processes'),
1287
            array(
1288
                'created_at' => time(),
1289
                'process_type' => 'send_email',
1290
                'arguments' => json_encode([
1291
                    'subject' => $subject,
1292
                    'receivers' => $email,
1293
                    'body' => $body,
1294
                    'receiver_name' => $receiverName,
1295
                ]),
1296
                'updated_at' => '',
1297
                'finished_at' => '',
1298
                'output' => '',
1299
            )
1300
        );
1301
    } else {
1302
        sendEmail(
1303
            $subject,
1304
            $body,
1305
            $email,
1306
            $SETTINGS,
1307
            $body
1308
        );
1309
    }
1310
}
1311
1312
/**
1313
 * Permits to send an email.
1314
 *
1315
 * @param string $subject     email subject
1316
 * @param string $textMail    email message
1317
 * @param string $email       email
1318
 * @param array  $SETTINGS    settings
1319
 * @param string $textMailAlt email message alt
1320
 * @param bool   $silent      no errors
1321
 *
1322
 * @return string some json info
1323
 */
1324
function sendEmail(
1325
    $subject,
1326
    $textMail,
1327
    $email,
1328
    $SETTINGS,
1329
    $textMailAlt = null,
1330
    $silent = true
1331
) {
1332
    // CAse where email not defined
1333
    if ($email === 'none' || empty($email) === true) {
1334
        return json_encode(
1335
            [
1336
                'error' => true,
1337
                'message' => langHdl('forgot_my_pw_email_sent'),
1338
            ]
1339
        );
1340
    }
1341
1342
    // Load settings
1343
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1344
    // Load superglobal
1345
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1346
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1347
    // Get user language
1348
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . (null !== $superGlobal->get('user_language', 'SESSION', 'user') ? $superGlobal->get('user_language', 'SESSION', 'user') : 'english') . '.php';
1349
    // Load library
1350
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1351
    // load PHPMailer
1352
    $mail = new SplClassLoader('PHPMailer\PHPMailer', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1353
    $mail->register();
1354
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1355
1356
    // send to user
1357
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1358
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true ? $SETTINGS['email_debug_level'] : 0;
1359
    $mail->Port = (int) $SETTINGS['email_port'];
1360
    //COULD BE USED
1361
    $mail->CharSet = 'utf-8';
1362
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1363
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1364
    $mail->SMTPOptions = [
1365
        'ssl' => [
1366
            'verify_peer' => false,
1367
            'verify_peer_name' => false,
1368
            'allow_self_signed' => true,
1369
        ],
1370
    ];
1371
    $mail->isSmtp();
1372
    // send via SMTP
1373
    $mail->Host = $SETTINGS['email_smtp_server'];
1374
    // SMTP servers
1375
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1376
    // turn on SMTP authentication
1377
    $mail->Username = $SETTINGS['email_auth_username'];
1378
    // SMTP username
1379
    $mail->Password = $SETTINGS['email_auth_pwd'];
1380
    // SMTP password
1381
    $mail->From = $SETTINGS['email_from'];
1382
    $mail->FromName = $SETTINGS['email_from_name'];
1383
    // Prepare for each person
1384
    foreach (array_filter(explode(',', $email)) as $dest) {
1385
        $mail->addAddress($dest);
1386
    }
1387
1388
    // Prepare HTML
1389
    $text_html = emailBody($textMail);
1390
    $mail->WordWrap = 80;
1391
    // set word wrap
1392
    $mail->isHtml(true);
1393
    // send as HTML
1394
    $mail->Subject = $subject;
1395
    $mail->Body = $text_html;
1396
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1397
1398
    try {
1399
        // send email
1400
        $mail->send();
1401
    } catch (Exception $e) {
1402
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1403
            return json_encode(
1404
                [
1405
                    'error' => true,
1406
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1407
                ]
1408
            );
1409
        }
1410
        return '';
1411
    }
1412
    $mail->smtpClose();
1413
1414
    if ($silent === false) {
1415
        return json_encode(
1416
            [
1417
                'error' => false,
1418
                'message' => langHdl('forgot_my_pw_email_sent'),
1419
            ]
1420
        );
1421
    }
1422
    // Debug purpose
1423
    if ((int) $SETTINGS['email_debug_level'] !== 0) {
1424
        return json_encode(
1425
            [
1426
                'error' => true,
1427
                'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1428
            ]
1429
        );
1430
    }
1431
    return json_encode(
1432
        [
1433
            'error' => false,
1434
            'message' => langHdl('share_sent_ok'),
1435
        ]
1436
    );
1437
}
1438
1439
/**
1440
 * Returns the email body.
1441
 *
1442
 * @param string $textMail Text for the email
1443
 */
1444
function emailBody(string $textMail): string
1445
{
1446
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1447
    w3.org/TR/html4/loose.dtd"><html>
1448
    <head><title>Email Template</title>
1449
    <style type="text/css">
1450
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1451
    </style></head>
1452
    <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">
1453
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1454
    <tr><td style="border-collapse: collapse;"><br>
1455
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1456
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1457
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1458
        </td></tr></table></td>
1459
    </tr>
1460
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1461
        <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;">
1462
        <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;">
1463
        <br><div style="float:right;">' .
1464
        $textMail .
1465
        '<br><br></td></tr></table>
1466
    </td></tr></table>
1467
    <br></body></html>';
1468
}
1469
1470
/**
1471
 * Generate a Key.
1472
 * 
1473
 * @return string
1474
 */
1475
function generateKey(): string
1476
{
1477
    return substr(md5(rand() . rand()), 0, 15);
1478
}
1479
1480
/**
1481
 * Convert date to timestamp.
1482
 *
1483
 * @param string $date     The date
1484
 * @param array  $SETTINGS Teampass settings
1485
 *
1486
 * @return string
1487
 */
1488
function dateToStamp(string $date, array $SETTINGS): string
1489
{
1490
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1491
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1492
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1493
    }
1494
    return '';
1495
}
1496
1497
/**
1498
 * Is this a date.
1499
 *
1500
 * @param string $date Date
1501
 *
1502
 * @return bool
1503
 */
1504
function isDate(string $date): bool
1505
{
1506
    return strtotime($date) !== false;
1507
}
1508
1509
/**
1510
 * Check if isUTF8().
1511
 *
1512
 * @param string|array $string Is the string
1513
 *
1514
 * @return int is the string in UTF8 format
1515
 */
1516
function isUTF8($string): int
1517
{
1518
    if (is_array($string) === true) {
1519
        $string = $string['string'];
1520
    }
1521
1522
    return preg_match(
1523
        '%^(?:
1524
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1525
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1526
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1527
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1528
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1529
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1530
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1531
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1532
        )*$%xs',
1533
        $string
1534
    );
1535
}
1536
1537
/**
1538
 * Prepare an array to UTF8 format before JSON_encode.
1539
 *
1540
 * @param array $array Array of values
1541
 *
1542
 * @return array
1543
 */
1544
function utf8Converter(array $array): array
1545
{
1546
    array_walk_recursive(
1547
        $array,
1548
        static function (&$item): void {
1549
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1550
                $item = utf8_encode($item);
1551
            }
1552
        }
1553
    );
1554
    return $array;
1555
}
1556
1557
/**
1558
 * Permits to prepare data to be exchanged.
1559
 *
1560
 * @param string       $teampassDir
1561
 * @param array|string $data Text
1562
 * @param string       $type Parameter
1563
 * @param string       $key  Optional key
1564
 *
1565
 * @return string|array
1566
 */
1567
function prepareExchangedData($teampassDir, $data, string $type, ?string $key = null)
1568
{
1569
    $teampassDir = __DIR__ . '/..';
1570
    // Load superglobal
1571
    include_once $teampassDir . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1572
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1573
    // Get superglobals
1574
    if ($key !== null) {
1575
        $superGlobal->put('key', $key, 'SESSION');
1576
        $globalsKey = $key;
1577
    } else {
1578
        $globalsKey = $superGlobal->get('key', 'SESSION');
1579
    }
1580
1581
    //load Encoding
1582
    include_once $teampassDir . '/includes/libraries/ForceUTF8/Encoding.php';
1583
    
1584
    //Load CRYPTOJS
1585
    include_once $teampassDir . '/includes/libraries/Encryption/CryptoJs/Encryption.php';
1586
1587
    // Perform
1588
    if ($type === 'encode' && is_array($data) === true) {
1589
        // Now encode
1590
        return Encryption\CryptoJs\Encryption::encrypt(
1591
            json_encode(
1592
                $data,
1593
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1594
            ),
1595
            $globalsKey
1596
        );
1597
    }
1598
    if ($type === 'decode' && is_array($data) === false) {
1599
        return json_decode(
1600
            (string) Encryption\CryptoJs\Encryption::decrypt(
1601
                (string) $data,
1602
                $globalsKey
1603
            ),
1604
            true
1605
        );
1606
    }
1607
}
1608
1609
1610
/**
1611
 * Create a thumbnail.
1612
 *
1613
 * @param string  $src           Source
1614
 * @param string  $dest          Destination
1615
 * @param int $desired_width Size of width
1616
 * 
1617
 * @return void|string|bool
1618
 */
1619
function makeThumbnail(string $src, string $dest, int $desired_width)
1620
{
1621
    /* read the source image */
1622
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1623
        $source_image = imagecreatefrompng($src);
1624
        if ($source_image === false) {
1625
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1626
        }
1627
    } else {
1628
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1629
    }
1630
1631
    // Get height and width
1632
    $width = imagesx($source_image);
1633
    $height = imagesy($source_image);
1634
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1635
    $desired_height = (int) floor($height * $desired_width / $width);
1636
    /* create a new, "virtual" image */
1637
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1638
    if ($virtual_image === false) {
1639
        return false;
1640
    }
1641
    /* copy source image at a resized size */
1642
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1643
    /* create the physical thumbnail image to its destination */
1644
    imagejpeg($virtual_image, $dest);
1645
}
1646
1647
/**
1648
 * Check table prefix in SQL query.
1649
 *
1650
 * @param string $table Table name
1651
 * 
1652
 * @return string
1653
 */
1654
function prefixTable(string $table): string
1655
{
1656
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1657
    if (! empty($safeTable)) {
1658
        // sanitize string
1659
        return $safeTable;
1660
    }
1661
    // stop error no table
1662
    return 'table_not_exists';
1663
}
1664
1665
/**
1666
 * GenerateCryptKey
1667
 *
1668
 * @param int     $size      Length
1669
 * @param bool $secure Secure
1670
 * @param bool $numerals Numerics
1671
 * @param bool $uppercase Uppercase letters
1672
 * @param bool $symbols Symbols
1673
 * @param bool $lowercase Lowercase
1674
 * @param array   $SETTINGS  SETTINGS
1675
 * 
1676
 * @return string
1677
 */
1678
function GenerateCryptKey(
1679
    int $size = 10,
1680
    bool $secure = false,
1681
    bool $numerals = false,
1682
    bool $uppercase = false,
1683
    bool $symbols = false,
1684
    bool $lowercase = false,
1685
    array $SETTINGS = []
1686
): string {
1687
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1688
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1689
    $generator->register();
1690
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1691
    // Is PHP7 being used?
1692
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1693
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1694
        $php7generator->register();
1695
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1696
    }
1697
    
1698
    // Manage size
1699
    $generator->setLength((int) $size);
1700
    if ($secure === true) {
1701
        $generator->setSymbols(true);
1702
        $generator->setLowercase(true);
1703
        $generator->setUppercase(true);
1704
        $generator->setNumbers(true);
1705
    } else {
1706
        $generator->setLowercase($lowercase);
1707
        $generator->setUppercase($uppercase);
1708
        $generator->setNumbers($numerals);
1709
        $generator->setSymbols($symbols);
1710
    }
1711
1712
    return $generator->generatePasswords()[0];
1713
}
1714
1715
/**
1716
 * Send sysLOG message
1717
 *
1718
 * @param string    $message
1719
 * @param string    $host
1720
 * @param int       $port
1721
 * @param string    $component
1722
 * 
1723
 * @return void
1724
*/
1725
function send_syslog($message, $host, $port, $component = 'teampass'): void
1726
{
1727
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1728
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1729
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1730
    socket_close($sock);
1731
}
1732
1733
/**
1734
 * Permits to log events into DB
1735
 *
1736
 * @param array  $SETTINGS Teampass settings
1737
 * @param string $type     Type
1738
 * @param string $label    Label
1739
 * @param string $who      Who
1740
 * @param string $login    Login
1741
 * @param string $field_1  Field
1742
 * 
1743
 * @return void
1744
 */
1745
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1746
{
1747
    if (empty($who)) {
1748
        $who = getClientIpServer();
1749
    }
1750
1751
    // include librairies & connect to DB
1752
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1753
    if (defined('DB_PASSWD_CLEAR') === false) {
1754
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1755
    }
1756
    DB::$host = DB_HOST;
1757
    DB::$user = DB_USER;
1758
    DB::$password = DB_PASSWD_CLEAR;
1759
    DB::$dbName = DB_NAME;
1760
    DB::$port = DB_PORT;
1761
    DB::$encoding = DB_ENCODING;
1762
    DB::$ssl = DB_SSL;
1763
    DB::$connect_options = DB_CONNECT_OPTIONS;
1764
    DB::insert(
1765
        prefixTable('log_system'),
1766
        [
1767
            'type' => $type,
1768
            'date' => time(),
1769
            'label' => $label,
1770
            'qui' => $who,
1771
            'field_1' => $field_1 === null ? '' : $field_1,
1772
        ]
1773
    );
1774
    // If SYSLOG
1775
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1776
        if ($type === 'user_mngt') {
1777
            send_syslog(
1778
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1779
                $SETTINGS['syslog_host'],
1780
                $SETTINGS['syslog_port'],
1781
                'teampass'
1782
            );
1783
        } else {
1784
            send_syslog(
1785
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1786
                $SETTINGS['syslog_host'],
1787
                $SETTINGS['syslog_port'],
1788
                'teampass'
1789
            );
1790
        }
1791
    }
1792
}
1793
1794
/**
1795
 * Log events.
1796
 *
1797
 * @param array  $SETTINGS        Teampass settings
1798
 * @param int    $item_id         Item id
1799
 * @param string $item_label      Item label
1800
 * @param int    $id_user         User id
1801
 * @param string $action          Code for reason
1802
 * @param string $login           User login
1803
 * @param string $raison          Code for reason
1804
 * @param string $encryption_type Encryption on
1805
 * 
1806
 * @return void
1807
 */
1808
function logItems(
1809
    array $SETTINGS,
1810
    int $item_id,
1811
    string $item_label,
1812
    int $id_user,
1813
    string $action,
1814
    ?string $login = null,
1815
    ?string $raison = null,
1816
    ?string $encryption_type = null
1817
): void {
1818
    // include librairies & connect to DB
1819
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1820
    if (defined('DB_PASSWD_CLEAR') === false) {
1821
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1822
    }
1823
    DB::$host = DB_HOST;
1824
    DB::$user = DB_USER;
1825
    DB::$password = DB_PASSWD_CLEAR;
1826
    DB::$dbName = DB_NAME;
1827
    DB::$port = DB_PORT;
1828
    DB::$encoding = DB_ENCODING;
1829
    DB::$ssl = DB_SSL;
1830
    DB::$connect_options = DB_CONNECT_OPTIONS;
1831
    // Insert log in DB
1832
    DB::insert(
1833
        prefixTable('log_items'),
1834
        [
1835
            'id_item' => $item_id,
1836
            'date' => time(),
1837
            'id_user' => $id_user,
1838
            'action' => $action,
1839
            'raison' => $raison,
1840
            'raison_iv' => '',
1841
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1842
        ]
1843
    );
1844
    // Timestamp the last change
1845
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1846
        DB::update(
1847
            prefixTable('misc'),
1848
            [
1849
                'valeur' => time(),
1850
            ],
1851
            'type = %s AND intitule = %s',
1852
            'timestamp',
1853
            'last_item_change'
1854
        );
1855
    }
1856
1857
    // SYSLOG
1858
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1859
        // Extract reason
1860
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1861
        // Get item info if not known
1862
        if (empty($item_label) === true) {
1863
            $dataItem = DB::queryfirstrow(
1864
                'SELECT id, id_tree, label
1865
                FROM ' . prefixTable('items') . '
1866
                WHERE id = %i',
1867
                $item_id
1868
            );
1869
            $item_label = $dataItem['label'];
1870
        }
1871
1872
        send_syslog(
1873
            'action=' . str_replace('at_', '', $action) .
1874
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1875
                ' itemno=' . $item_id .
1876
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1877
                ' itemname="' . addslashes($item_label) . '"',
1878
            $SETTINGS['syslog_host'],
1879
            $SETTINGS['syslog_port'],
1880
            'teampass'
1881
        );
1882
    }
1883
1884
    // send notification if enabled
1885
    //notifyOnChange($item_id, $action, $SETTINGS);
1886
}
1887
1888
/**
1889
 * If enabled, then notify admin/manager.
1890
 *
1891
 * @param int    $item_id  Item id
1892
 * @param string $action   Action to do
1893
 * @param array  $SETTINGS Teampass settings
1894
 * 
1895
 * @return void
1896
 */
1897
/*
1898
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1899
{
1900
    if (
1901
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1902
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1903
        && $action === 'at_shown'
1904
    ) {
1905
        // Load superglobal
1906
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1907
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1908
        // Get superglobals
1909
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1910
        $globalsName = $superGlobal->get('name', 'SESSION');
1911
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1912
        // Get info about item
1913
        $dataItem = DB::queryfirstrow(
1914
            'SELECT id, id_tree, label
1915
            FROM ' . prefixTable('items') . '
1916
            WHERE id = %i',
1917
            $item_id
1918
        );
1919
        $item_label = $dataItem['label'];
1920
        // send back infos
1921
        DB::insert(
1922
            prefixTable('emails'),
1923
            [
1924
                'timestamp' => time(),
1925
                'subject' => langHdl('email_on_open_notification_subject'),
1926
                'body' => str_replace(
1927
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1928
                    [
1929
                        addslashes($globalsName . ' ' . $globalsLastname),
1930
                        addslashes($item_label),
1931
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1932
                    ],
1933
                    langHdl('email_on_open_notification_mail')
1934
                ),
1935
                'receivers' => $globalsNotifiedEmails,
1936
                'status' => '',
1937
            ]
1938
        );
1939
    }
1940
}
1941
*/
1942
1943
/**
1944
 * Prepare notification email to subscribers.
1945
 *
1946
 * @param int    $item_id  Item id
1947
 * @param string $label    Item label
1948
 * @param array  $changes  List of changes
1949
 * @param array  $SETTINGS Teampass settings
1950
 * 
1951
 * @return void
1952
 */
1953
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1954
{
1955
    // Load superglobal
1956
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1957
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1958
    // Get superglobals
1959
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1960
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1961
    $globalsName = $superGlobal->get('name', 'SESSION');
1962
    // send email to user that what to be notified
1963
    $notification = DB::queryOneColumn(
1964
        'email',
1965
        'SELECT *
1966
        FROM ' . prefixTable('notification') . ' AS n
1967
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1968
        WHERE n.item_id = %i AND n.user_id != %i',
1969
        $item_id,
1970
        $globalsUserId
1971
    );
1972
    if (DB::count() > 0) {
1973
        // Prepare path
1974
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1975
        // Get list of changes
1976
        $htmlChanges = '<ul>';
1977
        foreach ($changes as $change) {
1978
            $htmlChanges .= '<li>' . $change . '</li>';
1979
        }
1980
        $htmlChanges .= '</ul>';
1981
        // send email
1982
        DB::insert(
1983
            prefixTable('emails'),
1984
            [
1985
                'timestamp' => time(),
1986
                'subject' => langHdl('email_subject_item_updated'),
1987
                'body' => str_replace(
1988
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1989
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1990
                    langHdl('email_body_item_updated')
1991
                ),
1992
                'receivers' => implode(',', $notification),
1993
                'status' => '',
1994
            ]
1995
        );
1996
    }
1997
}
1998
1999
/**
2000
 * Returns the Item + path.
2001
 *
2002
 * @param int    $id_tree  Node id
2003
 * @param string $label    Label
2004
 * @param array  $SETTINGS TP settings
2005
 * 
2006
 * @return string
2007
 */
2008
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
2009
{
2010
    // Class loader
2011
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
2012
    //Load Tree
2013
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
2014
    $tree->register();
2015
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
2016
    $arbo = $tree->getPath($id_tree, true);
2017
    $path = '';
2018
    foreach ($arbo as $elem) {
2019
        if (empty($path) === true) {
2020
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
2021
        } else {
2022
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
2023
        }
2024
    }
2025
2026
    // Build text to show user
2027
    if (empty($label) === false) {
2028
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
2029
    }
2030
    return empty($path) === true ? '' : $path;
2031
}
2032
2033
/**
2034
 * Get the client ip address.
2035
 *
2036
 * @return string IP address
2037
 */
2038
function getClientIpServer(): string
2039
{
2040
    if (getenv('HTTP_CLIENT_IP')) {
2041
        $ipaddress = getenv('HTTP_CLIENT_IP');
2042
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
2043
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
2044
    } elseif (getenv('HTTP_X_FORWARDED')) {
2045
        $ipaddress = getenv('HTTP_X_FORWARDED');
2046
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
2047
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
2048
    } elseif (getenv('HTTP_FORWARDED')) {
2049
        $ipaddress = getenv('HTTP_FORWARDED');
2050
    } elseif (getenv('REMOTE_ADDR')) {
2051
        $ipaddress = getenv('REMOTE_ADDR');
2052
    } else {
2053
        $ipaddress = 'UNKNOWN';
2054
    }
2055
2056
    return $ipaddress;
2057
}
2058
2059
/**
2060
 * Escape all HTML, JavaScript, and CSS.
2061
 *
2062
 * @param string $input    The input string
2063
 * @param string $encoding Which character encoding are we using?
2064
 * 
2065
 * @return string
2066
 */
2067
function noHTML(string $input, string $encoding = 'UTF-8'): string
2068
{
2069
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2070
}
2071
2072
/**
2073
 * Permits to handle the Teampass config file
2074
 * $action accepts "rebuild" and "update"
2075
 *
2076
 * @param string $action   Action to perform
2077
 * @param array  $SETTINGS Teampass settings
2078
 * @param string $field    Field to refresh
2079
 * @param string $value    Value to set
2080
 *
2081
 * @return string|bool
2082
 */
2083
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2084
{
2085
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2086
    // include librairies & connect to DB
2087
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2088
    if (defined('DB_PASSWD_CLEAR') === false) {
2089
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2090
    }
2091
    DB::$host = DB_HOST;
2092
    DB::$user = DB_USER;
2093
    DB::$password = DB_PASSWD_CLEAR;
2094
    DB::$dbName = DB_NAME;
2095
    DB::$port = DB_PORT;
2096
    DB::$encoding = DB_ENCODING;
2097
    DB::$ssl = DB_SSL;
2098
    DB::$connect_options = DB_CONNECT_OPTIONS;
2099
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2100
        // perform a copy
2101
        if (file_exists($tp_config_file)) {
2102
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2103
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2104
            }
2105
        }
2106
2107
        // regenerate
2108
        $data = [];
2109
        $data[0] = "<?php\n";
2110
        $data[1] = "global \$SETTINGS;\n";
2111
        $data[2] = "\$SETTINGS = array (\n";
2112
        $rows = DB::query(
2113
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2114
            'admin'
2115
        );
2116
        foreach ($rows as $record) {
2117
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
2118
        }
2119
        array_push($data, ");\n");
2120
        $data = array_unique($data);
2121
    // ---
2122
    } elseif ($action === 'update' && empty($field) === false) {
2123
        $data = file($tp_config_file);
2124
        $inc = 0;
2125
        $bFound = false;
2126
        foreach ($data as $line) {
2127
            if (stristr($line, ');')) {
2128
                break;
2129
            }
2130
2131
            if (stristr($line, "'" . $field . "' => '")) {
2132
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
2133
                $bFound = true;
2134
                break;
2135
            }
2136
            ++$inc;
2137
        }
2138
        if ($bFound === false) {
2139
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
2140
        }
2141
    }
2142
2143
    // update file
2144
    file_put_contents($tp_config_file, implode('', $data ?? []));
2145
    return true;
2146
}
2147
2148
/**
2149
 * Permits to replace &#92; to permit correct display
2150
 *
2151
 * @param string $input Some text
2152
 * 
2153
 * @return string
2154
 */
2155
function handleBackslash(string $input): string
2156
{
2157
    return str_replace('&amp;#92;', '&#92;', $input);
2158
}
2159
2160
/**
2161
 * Permits to load settings
2162
 * 
2163
 * @return void
2164
*/
2165
function loadSettings(): void
2166
{
2167
    global $SETTINGS;
2168
    /* LOAD CPASSMAN SETTINGS */
2169
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2170
        $SETTINGS = [];
2171
        $SETTINGS['duplicate_folder'] = 0;
2172
        //by default, this is set to 0;
2173
        $SETTINGS['duplicate_item'] = 0;
2174
        //by default, this is set to 0;
2175
        $SETTINGS['number_of_used_pw'] = 5;
2176
        //by default, this value is set to 5;
2177
        $settings = [];
2178
        $rows = DB::query(
2179
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2180
            [
2181
                'type' => 'admin',
2182
                'type2' => 'settings',
2183
            ]
2184
        );
2185
        foreach ($rows as $record) {
2186
            if ($record['type'] === 'admin') {
2187
                $SETTINGS[$record['intitule']] = $record['valeur'];
2188
            } else {
2189
                $settings[$record['intitule']] = $record['valeur'];
2190
            }
2191
        }
2192
        $SETTINGS['loaded'] = 1;
2193
        $SETTINGS['default_session_expiration_time'] = 5;
2194
    }
2195
}
2196
2197
/**
2198
 * check if folder has custom fields.
2199
 * Ensure that target one also has same custom fields
2200
 * 
2201
 * @param int $source_id
2202
 * @param int $target_id 
2203
 * 
2204
 * @return bool
2205
*/
2206
function checkCFconsistency(int $source_id, int $target_id): bool
2207
{
2208
    $source_cf = [];
2209
    $rows = DB::QUERY(
2210
        'SELECT id_category
2211
            FROM ' . prefixTable('categories_folders') . '
2212
            WHERE id_folder = %i',
2213
        $source_id
2214
    );
2215
    foreach ($rows as $record) {
2216
        array_push($source_cf, $record['id_category']);
2217
    }
2218
2219
    $target_cf = [];
2220
    $rows = DB::QUERY(
2221
        'SELECT id_category
2222
            FROM ' . prefixTable('categories_folders') . '
2223
            WHERE id_folder = %i',
2224
        $target_id
2225
    );
2226
    foreach ($rows as $record) {
2227
        array_push($target_cf, $record['id_category']);
2228
    }
2229
2230
    $cf_diff = array_diff($source_cf, $target_cf);
2231
    if (count($cf_diff) > 0) {
2232
        return false;
2233
    }
2234
2235
    return true;
2236
}
2237
2238
/**
2239
 * Will encrypte/decrypt a fil eusing Defuse.
2240
 *
2241
 * @param string $type        can be either encrypt or decrypt
2242
 * @param string $source_file path to source file
2243
 * @param string $target_file path to target file
2244
 * @param array  $SETTINGS    Settings
2245
 * @param string $password    A password
2246
 *
2247
 * @return string|bool
2248
 */
2249
function prepareFileWithDefuse(
2250
    string $type,
2251
    string $source_file,
2252
    string $target_file,
2253
    array $SETTINGS,
2254
    string $password = null
2255
) {
2256
    // Load AntiXSS
2257
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2258
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2259
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2260
    $antiXss = new voku\helper\AntiXSS();
2261
    // Protect against bad inputs
2262
    if (is_array($source_file) === true || is_array($target_file) === true) {
2263
        return 'error_cannot_be_array';
2264
    }
2265
2266
    // Sanitize
2267
    $source_file = $antiXss->xss_clean($source_file);
2268
    $target_file = $antiXss->xss_clean($target_file);
2269
    if (empty($password) === true || is_null($password) === true) {
2270
        // get KEY to define password
2271
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2272
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2273
    }
2274
2275
    $err = '';
2276
    if ($type === 'decrypt') {
2277
        // Decrypt file
2278
        $err = defuseFileDecrypt(
2279
            $source_file,
2280
            $target_file,
2281
            $SETTINGS, /** @scrutinizer ignore-type */
2282
            $password
2283
        );
2284
    } elseif ($type === 'encrypt') {
2285
        // Encrypt file
2286
        $err = defuseFileEncrypt(
2287
            $source_file,
2288
            $target_file,
2289
            $SETTINGS, /** @scrutinizer ignore-type */
2290
            $password
2291
        );
2292
    }
2293
2294
    // return error
2295
    return $err === true ? '' : $err;
2296
}
2297
2298
/**
2299
 * Encrypt a file with Defuse.
2300
 *
2301
 * @param string $source_file path to source file
2302
 * @param string $target_file path to target file
2303
 * @param array  $SETTINGS    Settings
2304
 * @param string $password    A password
2305
 *
2306
 * @return string|bool
2307
 */
2308
function defuseFileEncrypt(
2309
    string $source_file,
2310
    string $target_file,
2311
    array $SETTINGS,
2312
    string $password = null
2313
) {
2314
    // load PhpEncryption library
2315
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2316
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2317
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2318
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2319
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2320
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2321
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2322
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2323
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2324
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2325
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2326
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2327
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2328
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2329
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2330
    try {
2331
        \Defuse\Crypto\File::encryptFileWithPassword(
2332
            $source_file,
2333
            $target_file,
2334
            $password
2335
        );
2336
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2337
        $err = 'wrong_key';
2338
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2339
        $err = $ex;
2340
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2341
        $err = $ex;
2342
    }
2343
2344
    // return error
2345
    return empty($err) === false ? $err : true;
2346
}
2347
2348
/**
2349
 * Decrypt a file with Defuse.
2350
 *
2351
 * @param string $source_file path to source file
2352
 * @param string $target_file path to target file
2353
 * @param array  $SETTINGS    Settings
2354
 * @param string $password    A password
2355
 *
2356
 * @return string|bool
2357
 */
2358
function defuseFileDecrypt(
2359
    string $source_file,
2360
    string $target_file,
2361
    array $SETTINGS,
2362
    string $password = null
2363
) {
2364
    // load PhpEncryption library
2365
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2366
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2367
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2368
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2369
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2370
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2371
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2372
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2373
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2374
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2375
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2376
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2377
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2378
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2379
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2380
    try {
2381
        \Defuse\Crypto\File::decryptFileWithPassword(
2382
            $source_file,
2383
            $target_file,
2384
            $password
2385
        );
2386
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2387
        $err = 'wrong_key';
2388
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2389
        $err = $ex;
2390
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2391
        $err = $ex;
2392
    }
2393
2394
    // return error
2395
    return empty($err) === false ? $err : true;
2396
}
2397
2398
/*
2399
* NOT TO BE USED
2400
*/
2401
/**
2402
 * Undocumented function.
2403
 *
2404
 * @param string $text Text to debug
2405
 */
2406
function debugTeampass(string $text): void
2407
{
2408
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2409
    if ($debugFile !== false) {
2410
        fputs($debugFile, $text);
2411
        fclose($debugFile);
2412
    }
2413
}
2414
2415
/**
2416
 * DELETE the file with expected command depending on server type.
2417
 *
2418
 * @param string $file     Path to file
2419
 * @param array  $SETTINGS Teampass settings
2420
 *
2421
 * @return void
2422
 */
2423
function fileDelete(string $file, array $SETTINGS): void
2424
{
2425
    // Load AntiXSS
2426
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2427
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2428
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2429
    $antiXss = new voku\helper\AntiXSS();
2430
    $file = $antiXss->xss_clean($file);
2431
    if (is_file($file)) {
2432
        unlink($file);
2433
    }
2434
}
2435
2436
/**
2437
 * Permits to extract the file extension.
2438
 *
2439
 * @param string $file File name
2440
 *
2441
 * @return string
2442
 */
2443
function getFileExtension(string $file): string
2444
{
2445
    if (strpos($file, '.') === false) {
2446
        return $file;
2447
    }
2448
2449
    return substr($file, strrpos($file, '.') + 1);
2450
}
2451
2452
/**
2453
 * Chmods files and folders with different permissions.
2454
 *
2455
 * This is an all-PHP alternative to using: \n
2456
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2457
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2458
 *
2459
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2460
  *
2461
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2462
 * @param int    $filePerm The permissions any found files should get.
2463
 * @param int    $dirPerm  The permissions any found folder should get.
2464
 *
2465
 * @return bool Returns TRUE if the path if found and FALSE if not.
2466
 *
2467
 * @warning The permission levels has to be entered in octal format, which
2468
 * normally means adding a zero ("0") in front of the permission level. \n
2469
 * More info at: http://php.net/chmod.
2470
*/
2471
2472
function recursiveChmod(
2473
    string $path,
2474
    int $filePerm = 0644,
2475
    int  $dirPerm = 0755
2476
) {
2477
    // Check if the path exists
2478
    if (! file_exists($path)) {
2479
        return false;
2480
    }
2481
2482
    // See whether this is a file
2483
    if (is_file($path)) {
2484
        // Chmod the file with our given filepermissions
2485
        chmod($path, $filePerm);
2486
    // If this is a directory...
2487
    } elseif (is_dir($path)) {
2488
        // Then get an array of the contents
2489
        $foldersAndFiles = scandir($path);
2490
        // Remove "." and ".." from the list
2491
        $entries = array_slice($foldersAndFiles, 2);
2492
        // Parse every result...
2493
        foreach ($entries as $entry) {
2494
            // And call this function again recursively, with the same permissions
2495
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2496
        }
2497
2498
        // When we are done with the contents of the directory, we chmod the directory itself
2499
        chmod($path, $dirPerm);
2500
    }
2501
2502
    // Everything seemed to work out well, return true
2503
    return true;
2504
}
2505
2506
/**
2507
 * Check if user can access to this item.
2508
 *
2509
 * @param int   $item_id ID of item
2510
 * @param array $SETTINGS
2511
 *
2512
 * @return bool|string
2513
 */
2514
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2515
{
2516
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2517
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2518
    // Prepare superGlobal variables
2519
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2520
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2521
    // Load item data
2522
    $data = DB::queryFirstRow(
2523
        'SELECT id_tree
2524
        FROM ' . prefixTable('items') . '
2525
        WHERE id = %i',
2526
        $item_id
2527
    );
2528
    // Check if user can access this folder
2529
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2530
        // Now check if this folder is restricted to user
2531
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2532
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2533
        ) {
2534
            return 'ERR_FOLDER_NOT_ALLOWED';
2535
        }
2536
    }
2537
2538
    return true;
2539
}
2540
2541
/**
2542
 * Creates a unique key.
2543
 *
2544
 * @param int $lenght Key lenght
2545
 *
2546
 * @return string
2547
 */
2548
function uniqidReal(int $lenght = 13): string
2549
{
2550
    if (function_exists('random_bytes')) {
2551
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2552
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2553
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2554
    } else {
2555
        throw new Exception('no cryptographically secure random function available');
2556
    }
2557
2558
    return substr(bin2hex($bytes), 0, $lenght);
2559
}
2560
2561
/**
2562
 * Obfuscate an email.
2563
 *
2564
 * @param string $email Email address
2565
 *
2566
 * @return string
2567
 */
2568
function obfuscateEmail(string $email): string
2569
{
2570
    $email = explode("@", $email);
2571
    $name = $email[0];
2572
    if (strlen($name) > 3) {
2573
        $name = substr($name, 0, 2);
2574
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2575
            $name .= "*";
2576
        }
2577
        $name .= substr($email[0], -1, 1);
2578
    }
2579
    $host = explode(".", $email[1])[0];
2580
    if (strlen($host) > 3) {
2581
        $host = substr($host, 0, 1);
2582
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2583
            $host .= "*";
2584
        }
2585
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2586
    }
2587
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2588
    return $email;
2589
}
2590
2591
/**
2592
 * Perform a Query.
2593
 *
2594
 * @param array  $SETTINGS Teamapss settings
2595
 * @param string $fields   Fields to use
2596
 * @param string $table    Table to use
2597
 *
2598
 * @return array
2599
 */
2600
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2601
{
2602
    // include librairies & connect to DB
2603
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2604
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2605
    if (defined('DB_PASSWD_CLEAR') === false) {
2606
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2607
    }
2608
    DB::$host = DB_HOST;
2609
    DB::$user = DB_USER;
2610
    DB::$password = DB_PASSWD_CLEAR;
2611
    DB::$dbName = DB_NAME;
2612
    DB::$port = DB_PORT;
2613
    DB::$encoding = DB_ENCODING;
2614
    DB::$ssl = DB_SSL;
2615
    DB::$connect_options = DB_CONNECT_OPTIONS;
2616
    // Insert log in DB
2617
    return DB::query(
2618
        'SELECT ' . $fields . '
2619
        FROM ' . prefixTable($table)
2620
    );
2621
}
2622
2623
/**
2624
 * Undocumented function.
2625
 *
2626
 * @param int $bytes Size of file
2627
 *
2628
 * @return string
2629
 */
2630
function formatSizeUnits(int $bytes): string
2631
{
2632
    if ($bytes >= 1073741824) {
2633
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2634
    } elseif ($bytes >= 1048576) {
2635
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2636
    } elseif ($bytes >= 1024) {
2637
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2638
    } elseif ($bytes > 1) {
2639
        $bytes .= ' bytes';
2640
    } elseif ($bytes === 1) {
2641
        $bytes .= ' byte';
2642
    } else {
2643
        $bytes = '0 bytes';
2644
    }
2645
2646
    return $bytes;
2647
}
2648
2649
/**
2650
 * Generate user pair of keys.
2651
 *
2652
 * @param string $userPwd User password
2653
 *
2654
 * @return array
2655
 */
2656
function generateUserKeys(string $userPwd): array
2657
{
2658
    // include library
2659
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2660
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2661
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2662
    // Load classes
2663
    $rsa = new Crypt_RSA();
2664
    $cipher = new Crypt_AES();
2665
    // Create the private and public key
2666
    $res = $rsa->createKey(4096);
2667
    // Encrypt the privatekey
2668
    $cipher->setPassword($userPwd);
2669
    $privatekey = $cipher->encrypt($res['privatekey']);
2670
    return [
2671
        'private_key' => base64_encode($privatekey),
2672
        'public_key' => base64_encode($res['publickey']),
2673
        'private_key_clear' => base64_encode($res['privatekey']),
2674
    ];
2675
}
2676
2677
/**
2678
 * Permits to decrypt the user's privatekey.
2679
 *
2680
 * @param string $userPwd        User password
2681
 * @param string $userPrivateKey User private key
2682
 *
2683
 * @return string
2684
 */
2685
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2686
{
2687
    if (empty($userPwd) === false) {
2688
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2689
        // Load classes
2690
        $cipher = new Crypt_AES();
2691
        // Encrypt the privatekey
2692
        $cipher->setPassword($userPwd);
2693
        try {
2694
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2695
        } catch (Exception $e) {
2696
            return $e;
2697
        }
2698
    }
2699
    return '';
2700
}
2701
2702
/**
2703
 * Permits to encrypt the user's privatekey.
2704
 *
2705
 * @param string $userPwd        User password
2706
 * @param string $userPrivateKey User private key
2707
 *
2708
 * @return string
2709
 */
2710
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2711
{
2712
    if (empty($userPwd) === false) {
2713
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2714
        // Load classes
2715
        $cipher = new Crypt_AES();
2716
        // Encrypt the privatekey
2717
        $cipher->setPassword($userPwd);        
2718
        try {
2719
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2720
        } catch (Exception $e) {
2721
            return $e;
2722
        }
2723
    }
2724
    return '';
2725
}
2726
2727
/**
2728
 * Encrypts a string using AES.
2729
 *
2730
 * @param string $data String to encrypt
2731
 *
2732
 * @return array
2733
 */
2734
function doDataEncryption(string $data): array
2735
{
2736
    // Includes
2737
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2738
    // Load classes
2739
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2740
    // Generate an object key
2741
    $objectKey = uniqidReal(32);
2742
    // Set it as password
2743
    $cipher->setPassword($objectKey);
2744
    return [
2745
        'encrypted' => base64_encode($cipher->encrypt($data)),
2746
        'objectKey' => base64_encode($objectKey),
2747
    ];
2748
}
2749
2750
/**
2751
 * Decrypts a string using AES.
2752
 *
2753
 * @param string $data Encrypted data
2754
 * @param string $key  Key to uncrypt
2755
 *
2756
 * @return string
2757
 */
2758
function doDataDecryption(string $data, string $key): string
2759
{
2760
    // Includes
2761
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2762
    // Load classes
2763
    $cipher = new Crypt_AES();
2764
    // Set the object key
2765
    $cipher->setPassword(base64_decode($key));
2766
    return base64_encode($cipher->decrypt(base64_decode($data)));
2767
}
2768
2769
/**
2770
 * Encrypts using RSA a string using a public key.
2771
 *
2772
 * @param string $key       Key to be encrypted
2773
 * @param string $publicKey User public key
2774
 *
2775
 * @return string
2776
 */
2777
function encryptUserObjectKey(string $key, string $publicKey): string
2778
{
2779
    // Includes
2780
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2781
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2782
    // Load classes
2783
    $rsa = new Crypt_RSA();
2784
    $rsa->loadKey(base64_decode($publicKey));
2785
    // Encrypt
2786
    return base64_encode($rsa->encrypt(base64_decode($key)));
2787
}
2788
2789
/**
2790
 * Decrypts using RSA an encrypted string using a private key.
2791
 *
2792
 * @param string $key        Encrypted key
2793
 * @param string $privateKey User private key
2794
 *
2795
 * @return string
2796
 */
2797
function decryptUserObjectKey(string $key, string $privateKey): string
2798
{
2799
    // Includes
2800
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2801
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2802
    // Load classes
2803
    $rsa = new Crypt_RSA();
2804
    $rsa->loadKey(base64_decode($privateKey));
2805
    // Decrypt
2806
    try {
2807
        $tmpValue = $rsa->decrypt(base64_decode($key));
2808
        if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2809
            $ret = base64_encode($tmpValue);
0 ignored issues
show
Bug introduced by
$tmpValue of type boolean is incompatible with the type string expected by parameter $string of base64_encode(). ( Ignorable by Annotation )

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

2809
            $ret = base64_encode(/** @scrutinizer ignore-type */ $tmpValue);
Loading history...
2810
        } else {
2811
            $ret = '';
2812
        }
2813
    } catch (Exception $e) {
2814
        return $e;
2815
    }
2816
2817
    return $ret;
2818
}
2819
2820
/**
2821
 * Encrypts a file.
2822
 *
2823
 * @param string $fileInName File name
2824
 * @param string $fileInPath Path to file
2825
 *
2826
 * @return array
2827
 */
2828
function encryptFile(string $fileInName, string $fileInPath): array
2829
{
2830
    if (defined('FILE_BUFFER_SIZE') === false) {
2831
        define('FILE_BUFFER_SIZE', 128 * 1024);
2832
    }
2833
2834
    // Includes
2835
    include_once __DIR__.'/../includes/config/include.php';
2836
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2837
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2838
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2839
    // Load classes
2840
    $cipher = new Crypt_AES();
2841
    // Generate an object key
2842
    $objectKey = uniqidReal(32);
2843
    // Set it as password
2844
    $cipher->setPassword($objectKey);
2845
    // Prevent against out of memory
2846
    $cipher->enableContinuousBuffer();
2847
    //$cipher->disablePadding();
2848
2849
    // Encrypt the file content
2850
    $plaintext = file_get_contents(
2851
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2852
    );
2853
    $ciphertext = $cipher->encrypt($plaintext);
2854
    // Save new file
2855
    $hash = md5($plaintext);
2856
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2857
    file_put_contents($fileOut, $ciphertext);
2858
    unlink($fileInPath . '/' . $fileInName);
2859
    return [
2860
        'fileHash' => base64_encode($hash),
2861
        'objectKey' => base64_encode($objectKey),
2862
    ];
2863
}
2864
2865
/**
2866
 * Decrypt a file.
2867
 *
2868
 * @param string $fileName File name
2869
 * @param string $filePath Path to file
2870
 * @param string $key      Key to use
2871
 *
2872
 * @return string
2873
 */
2874
function decryptFile(string $fileName, string $filePath, string $key): string
2875
{
2876
    if (! defined('FILE_BUFFER_SIZE')) {
2877
        define('FILE_BUFFER_SIZE', 128 * 1024);
2878
    }
2879
2880
    // Includes
2881
    include_once __DIR__.'/../includes/config/include.php';
2882
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2883
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2884
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2885
    // Get file name
2886
    $fileName = base64_decode($fileName);
2887
    // Load classes
2888
    $cipher = new Crypt_AES();
2889
    // Set the object key
2890
    $cipher->setPassword(base64_decode($key));
2891
    // Prevent against out of memory
2892
    $cipher->enableContinuousBuffer();
2893
    $cipher->disablePadding();
2894
    // Get file content
2895
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2896
    // Decrypt file content and return
2897
    return base64_encode($cipher->decrypt($ciphertext));
2898
}
2899
2900
/**
2901
 * Generate a simple password
2902
 *
2903
 * @param int $length Length of string
2904
 * @param bool $symbolsincluded Allow symbols
2905
 *
2906
 * @return string
2907
 */
2908
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2909
{
2910
    // Generate new user password
2911
    $small_letters = range('a', 'z');
2912
    $big_letters = range('A', 'Z');
2913
    $digits = range(0, 9);
2914
    $symbols = $symbolsincluded === true ?
2915
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2916
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2917
    $count = count($res);
2918
    // first variant
2919
2920
    $random_string = '';
2921
    for ($i = 0; $i < $length; ++$i) {
2922
        $random_string .= $res[random_int(0, $count - 1)];
2923
    }
2924
2925
    return $random_string;
2926
}
2927
2928
/**
2929
 * Permit to store the sharekey of an object for users.
2930
 *
2931
 * @param string $object_name             Type for table selection
2932
 * @param int    $post_folder_is_personal Personal
2933
 * @param int    $post_folder_id          Folder
2934
 * @param int    $post_object_id          Object
2935
 * @param string $objectKey               Object key
2936
 * @param array  $SETTINGS                Teampass settings
2937
 *
2938
 * @return void
2939
 */
2940
function storeUsersShareKey(
2941
    string $object_name,
2942
    int $post_folder_is_personal,
2943
    int $post_folder_id,
2944
    int $post_object_id,
2945
    string $objectKey,
2946
    array $SETTINGS
2947
): void {
2948
    // include librairies & connect to DB
2949
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2950
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2951
    if (defined('DB_PASSWD_CLEAR') === false) {
2952
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2953
    }
2954
    DB::$host = DB_HOST;
2955
    DB::$user = DB_USER;
2956
    DB::$password = DB_PASSWD_CLEAR;
2957
    DB::$dbName = DB_NAME;
2958
    DB::$port = DB_PORT;
2959
    DB::$encoding = DB_ENCODING;
2960
    DB::$ssl = DB_SSL;
2961
    DB::$connect_options = DB_CONNECT_OPTIONS;
2962
    // Delete existing entries for this object
2963
    DB::delete(
2964
        $object_name,
2965
        'object_id = %i',
2966
        $post_object_id
2967
    );
2968
    // Superglobals
2969
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2970
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2971
    // Prepare superGlobal variables
2972
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2973
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2974
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2975
    if (
2976
        (int) $post_folder_is_personal === 1
2977
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2978
    ) {
2979
        // If this is a personal object
2980
        // Only create the sharekey for user
2981
        DB::insert(
2982
            $object_name,
2983
            [
2984
                'object_id' => (int) $post_object_id,
2985
                'user_id' => (int) $sessionUserId,
2986
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2987
            ]
2988
        );
2989
    } else {
2990
        // This is a public object
2991
        // Create sharekey for each user
2992
        $users = DB::query(
2993
            'SELECT id, public_key
2994
            FROM ' . prefixTable('users') . '
2995
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2996
            AND public_key != ""'
2997
        );
2998
        foreach ($users as $user) {
2999
            // Insert in DB the new object key for this item by user
3000
            DB::insert(
3001
                $object_name,
3002
                [
3003
                    'object_id' => $post_object_id,
3004
                    'user_id' => (int) $user['id'],
3005
                    'share_key' => encryptUserObjectKey(
3006
                        $objectKey,
3007
                        $user['public_key']
3008
                    ),
3009
                ]
3010
            );
3011
        }
3012
    }
3013
}
3014
3015
/**
3016
 * Is this string base64 encoded?
3017
 *
3018
 * @param string $str Encoded string?
3019
 *
3020
 * @return bool
3021
 */
3022
function isBase64(string $str): bool
3023
{
3024
    $str = (string) trim($str);
3025
    if (! isset($str[0])) {
3026
        return false;
3027
    }
3028
3029
    $base64String = (string) base64_decode($str, true);
3030
    if ($base64String && base64_encode($base64String) === $str) {
3031
        return true;
3032
    }
3033
3034
    return false;
3035
}
3036
3037
/**
3038
 * Undocumented function
3039
 *
3040
 * @param string $field Parameter
3041
 *
3042
 * @return array|bool|resource|string
3043
 */
3044
function filterString(string $field)
3045
{
3046
    // Sanitize string
3047
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
3048
    if (empty($field) === false) {
3049
        // Load AntiXSS
3050
        include_once __DIR__.'/../includes/libraries/voku/helper/AntiXSS.php';
3051
        $antiXss = new voku\helper\AntiXSS();
3052
        // Return
3053
        return $antiXss->xss_clean($field);
3054
    }
3055
3056
    return false;
3057
}
3058
3059
/**
3060
 * CHeck if provided credentials are allowed on server
3061
 *
3062
 * @param string $login    User Login
3063
 * @param string $password User Pwd
3064
 * @param array  $SETTINGS Teampass settings
3065
 *
3066
 * @return bool
3067
 */
3068
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3069
{
3070
    // Build ldap configuration array
3071
    $config = [
3072
        // Mandatory Configuration Options
3073
        'hosts' => [$SETTINGS['ldap_hosts']],
3074
        'base_dn' => $SETTINGS['ldap_bdn'],
3075
        'username' => $SETTINGS['ldap_username'],
3076
        'password' => $SETTINGS['ldap_password'],
3077
3078
        // Optional Configuration Options
3079
        'port' => $SETTINGS['ldap_port'],
3080
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3081
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3082
        'version' => 3,
3083
        'timeout' => 5,
3084
        'follow_referrals' => false,
3085
3086
        // Custom LDAP Options
3087
        'options' => [
3088
            // See: http://php.net/ldap_set_option
3089
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
3090
        ],
3091
    ];
3092
    // Load expected libraries
3093
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3094
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3095
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3096
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3097
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3098
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3099
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3100
    $ad->register();
3101
    $connection = new Connection($config);
3102
    // Connect to LDAP
3103
    try {
3104
        $connection->connect();
3105
    } catch (\LdapRecord\Auth\BindException $e) {
3106
        $error = $e->getDetailedError();
3107
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3108
        return false;
3109
    }
3110
3111
    // Authenticate user
3112
    try {
3113
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3114
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3115
        } else {
3116
            $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);
3117
        }
3118
    } catch (\LdapRecord\Auth\BindException $e) {
3119
        $error = $e->getDetailedError();
3120
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3121
        return false;
3122
    }
3123
3124
    return true;
3125
}
3126
3127
/**
3128
 * Removes from DB all sharekeys of this user
3129
 *
3130
 * @param int $userId User's id
3131
 * @param array   $SETTINGS Teampass settings
3132
 *
3133
 * @return bool
3134
 */
3135
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
3136
{
3137
    // include librairies & connect to DB
3138
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
3139
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3140
    if (defined('DB_PASSWD_CLEAR') === false) {
3141
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3142
    }
3143
    DB::$host = DB_HOST;
3144
    DB::$user = DB_USER;
3145
    DB::$password = DB_PASSWD_CLEAR;
3146
    DB::$dbName = DB_NAME;
3147
    DB::$port = DB_PORT;
3148
    DB::$encoding = DB_ENCODING;
3149
    DB::$ssl = DB_SSL;
3150
    DB::$connect_options = DB_CONNECT_OPTIONS;
3151
    // Remove all item sharekeys items
3152
    DB::delete(
3153
        prefixTable('sharekeys_items'),
3154
        'user_id = %i',
3155
        $userId
3156
    );
3157
    // Remove all item sharekeys files
3158
    DB::delete(
3159
        prefixTable('sharekeys_files'),
3160
        'user_id = %i',
3161
        $userId
3162
    );
3163
    // Remove all item sharekeys fields
3164
    DB::delete(
3165
        prefixTable('sharekeys_fields'),
3166
        'user_id = %i',
3167
        $userId
3168
    );
3169
    // Remove all item sharekeys logs
3170
    DB::delete(
3171
        prefixTable('sharekeys_logs'),
3172
        'user_id = %i',
3173
        $userId
3174
    );
3175
    // Remove all item sharekeys suggestions
3176
    DB::delete(
3177
        prefixTable('sharekeys_suggestions'),
3178
        'user_id = %i',
3179
        $userId
3180
    );
3181
    return false;
3182
}
3183
3184
/**
3185
 * Manage list of timezones   $SETTINGS Teampass settings
3186
 *
3187
 * @return array
3188
 */
3189
function timezone_list()
3190
{
3191
    static $timezones = null;
3192
    if ($timezones === null) {
3193
        $timezones = [];
3194
        $offsets = [];
3195
        $now = new DateTime('now', new DateTimeZone('UTC'));
3196
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3197
            $now->setTimezone(new DateTimeZone($timezone));
3198
            $offsets[] = $offset = $now->getOffset();
3199
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3200
        }
3201
3202
        array_multisort($offsets, $timezones);
3203
    }
3204
3205
    return $timezones;
3206
}
3207
3208
/**
3209
 * Provide timezone offset
3210
 *
3211
 * @param int $offset Timezone offset
3212
 *
3213
 * @return string
3214
 */
3215
function format_GMT_offset($offset): string
3216
{
3217
    $hours = intval($offset / 3600);
3218
    $minutes = abs(intval($offset % 3600 / 60));
3219
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3220
}
3221
3222
/**
3223
 * Provides timezone name
3224
 *
3225
 * @param string $name Timezone name
3226
 *
3227
 * @return string
3228
 */
3229
function format_timezone_name($name): string
3230
{
3231
    $name = str_replace('/', ', ', $name);
3232
    $name = str_replace('_', ' ', $name);
3233
3234
    return str_replace('St ', 'St. ', $name);
3235
}
3236
3237
/**
3238
 * Provides info if user should use MFA based on roles
3239
 *
3240
 * @param string $userRolesIds  User roles ids
3241
 * @param string $mfaRoles      Roles for which MFA is requested
3242
 *
3243
 * @return bool
3244
 */
3245
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3246
{
3247
    if (empty($mfaRoles) === true) {
3248
        return true;
3249
    }
3250
3251
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3252
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3253
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3254
        return true;
3255
    }
3256
3257
    return false;
3258
}
3259
3260
/**
3261
 * Permits to clean a string for export purpose
3262
 *
3263
 * @param string $text
3264
 * @param bool $emptyCheckOnly
3265
 * 
3266
 * @return string
3267
 */
3268
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3269
{
3270
    if (is_null($text) === true || empty($text) === true) {
3271
        return '';
3272
    }
3273
    // only expected to check if $text was empty
3274
    elseif ($emptyCheckOnly === true) {
3275
        return $text;
3276
    }
3277
3278
    return strip_tags(
3279
        cleanString(
3280
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3281
            true)
3282
        );
3283
}
3284
3285
/**
3286
 * Permits to check if user ID is valid
3287
 *
3288
 * @param integer $post_user_id
3289
 * @return bool
3290
 */
3291
function isUserIdValid($userId): bool
3292
{
3293
    if (is_null($userId) === false
3294
        && isset($userId) === true
3295
        && empty($userId) === false
3296
    ) {
3297
        return true;
3298
    }
3299
    return false;
3300
}
3301
3302
/**
3303
 * Check if a key exists and if its value equal the one expected
3304
 *
3305
 * @param string $key
3306
 * @param integer|string $value
3307
 * @param array $array
3308
 * 
3309
 * @return boolean
3310
 */
3311
function isKeyExistingAndEqual(
3312
    string $key,
3313
    /*PHP8 - integer|string*/$value,
3314
    array $array
3315
): bool
3316
{
3317
    if (isset($array[$key]) === true
3318
        && (is_int($value) === true ?
3319
            (int) $array[$key] === $value :
3320
            (string) $array[$key] === $value)
3321
    ) {
3322
        return true;
3323
    }
3324
    return false;
3325
}
3326
3327
/**
3328
 * Check if a variable is not set or equal to a value
3329
 *
3330
 * @param string|null $var
3331
 * @param integer|string $value
3332
 * 
3333
 * @return boolean
3334
 */
3335
function isKeyNotSetOrEqual(
3336
    /*PHP8 - string|null*/$var,
3337
    /*PHP8 - integer|string*/$value
3338
): bool
3339
{
3340
    if (isset($var) === false
3341
        || (is_int($value) === true ?
3342
            (int) $var === $value :
3343
            (string) $var === $value)
3344
    ) {
3345
        return true;
3346
    }
3347
    return false;
3348
}
3349
3350
/**
3351
 * Check if a key exists and if its value < to the one expected
3352
 *
3353
 * @param string $key
3354
 * @param integer $value
3355
 * @param array $array
3356
 * 
3357
 * @return boolean
3358
 */
3359
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3360
{
3361
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3362
        return true;
3363
    }
3364
    return false;
3365
}
3366
3367
/**
3368
 * Check if a key exists and if its value > to the one expected
3369
 *
3370
 * @param string $key
3371
 * @param integer $value
3372
 * @param array $array
3373
 * 
3374
 * @return boolean
3375
 */
3376
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3377
{
3378
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3379
        return true;
3380
    }
3381
    return false;
3382
}
3383
3384
/**
3385
 * Check if values in array are set
3386
 * Return true if all set
3387
 * Return false if one of them is not set
3388
 *
3389
 * @param array $arrayOfValues
3390
 * @return boolean
3391
 */
3392
function isSetArrayOfValues(array $arrayOfValues): bool
3393
{
3394
    foreach($arrayOfValues as $value) {
3395
        if (isset($value) === false) {
3396
            return false;
3397
        }
3398
    }
3399
    return true;
3400
}
3401
3402
/**
3403
 * Check if values in array are set
3404
 * Return true if all set
3405
 * Return false if one of them is not set
3406
 *
3407
 * @param array $arrayOfValues
3408
 * @param integer|string $value
3409
 * @return boolean
3410
 */
3411
function isArrayOfVarsEqualToValue(
3412
    array $arrayOfVars,
3413
    /*PHP8 - integer|string*/$value
3414
) : bool
3415
{
3416
    foreach($arrayOfVars as $variable) {
3417
        if ($variable !== $value) {
3418
            return false;
3419
        }
3420
    }
3421
    return true;
3422
}
3423
3424
/**
3425
 * Checks if at least one variable in array is equal to value
3426
 *
3427
 * @param array $arrayOfValues
3428
 * @param integer|string $value
3429
 * @return boolean
3430
 */
3431
function isOneVarOfArrayEqualToValue(
3432
    array $arrayOfVars,
3433
    /*PHP8 - integer|string*/$value
3434
) : bool
3435
{
3436
    foreach($arrayOfVars as $variable) {
3437
        if ($variable === $value) {
3438
            return true;
3439
        }
3440
    }
3441
    return false;
3442
}
3443
3444
/**
3445
 * Checks is value is null, not set OR empty
3446
 *
3447
 * @param string|int|null $value
3448
 * @return boolean
3449
 */
3450
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3451
{
3452
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3453
        return true;
3454
    }
3455
    return false;
3456
}
3457
3458
/**
3459
 * Checks if value is set and if empty is equal to passed boolean
3460
 *
3461
 * @param string|int $value
3462
 * @param boolean $boolean
3463
 * @return boolean
3464
 */
3465
function isValueSetEmpty($value, $boolean = true) : bool
3466
{
3467
    if (isset($value) === true && empty($value) === $boolean) {
3468
        return true;
3469
    }
3470
    return false;
3471
}
3472
3473
/**
3474
 * Ensure Complexity is translated
3475
 *
3476
 * @return void
3477
 */
3478
function defineComplexity() : void
3479
{
3480
    if (defined('TP_PW_COMPLEXITY') === false) {
3481
        define(
3482
            'TP_PW_COMPLEXITY',
3483
            [
3484
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3485
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3486
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3487
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3488
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3489
            ]
3490
        );
3491
    }
3492
}
3493
3494
/**
3495
 * Uses Sanitizer to perform data sanitization
3496
 *
3497
 * @param array     $data
3498
 * @param array     $filters
3499
 * @param string    $path
3500
 * @return array
3501
 */
3502
function dataSanitizer(
3503
    array $data,
3504
    array $filters,
3505
    string $path
3506
): array
3507
{
3508
    // Load Sanitizer library
3509
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3510
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3511
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3512
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3513
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3514
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3515
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3516
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3517
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3518
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3519
3520
    // Sanitize post and get variables
3521
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3522
    return $sanitizer->sanitize();
3523
}
3524
3525
/**
3526
 * Permits to manage the cache tree for a user
3527
 *
3528
 * @param integer $user_id
3529
 * @param string $data
3530
 * @param array $SETTINGS
3531
 * @param string $field_update
3532
 * @return void
3533
 */
3534
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3535
{
3536
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3537
    //Connect to DB
3538
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3539
    if (defined('DB_PASSWD_CLEAR') === false) {
3540
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3541
    }
3542
    DB::$host = DB_HOST;
3543
    DB::$user = DB_USER;
3544
    DB::$password = DB_PASSWD_CLEAR;
3545
    DB::$dbName = DB_NAME;
3546
    DB::$port = DB_PORT;
3547
    DB::$encoding = DB_ENCODING;
3548
    DB::$ssl = DB_SSL;
3549
    DB::$connect_options = DB_CONNECT_OPTIONS;
3550
3551
    // Exists ?
3552
    $userCacheId = DB::queryfirstrow(
3553
        'SELECT increment_id
3554
        FROM ' . prefixTable('cache_tree') . '
3555
        WHERE user_id = %i',
3556
        $user_id
3557
    );
3558
    
3559
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3560
        DB::insert(
3561
            prefixTable('cache_tree'),
3562
            array(
3563
                'data' => $data,
3564
                'timestamp' => time(),
3565
                'user_id' => $user_id,
3566
                'visible_folders' => '',
3567
            )
3568
        );
3569
    } else {
3570
        if (empty($field_update) === true) {
3571
            DB::update(
3572
                prefixTable('cache_tree'),
3573
                [
3574
                    'timestamp' => time(),
3575
                    'data' => $data,
3576
                ],
3577
                'increment_id = %i',
3578
                $userCacheId['increment_id']
3579
            );
3580
        } else {
3581
            DB::update(
3582
                prefixTable('cache_tree'),
3583
                [
3584
                    $field_update => $data,
3585
                ],
3586
                'increment_id = %i',
3587
                $userCacheId['increment_id']
3588
            );
3589
        }
3590
    }
3591
}
3592
3593
/**
3594
 * Permits to calculate a %
3595
 *
3596
 * @param float $nombre
3597
 * @param float $total
3598
 * @param float $pourcentage
3599
 * @return float
3600
 */
3601
function pourcentage(float $nombre, float $total, float $pourcentage): float
3602
{ 
3603
    $resultat = ($nombre/$total) * $pourcentage;
3604
    return round($resultat);
3605
}
3606
3607
/**
3608
 * Load the folders list from the cache
3609
 *
3610
 * @param string $fieldName
3611
 * @param string $sessionName
3612
 * @param boolean $forceRefresh
3613
 * @return array
3614
 */
3615
function loadFoldersListByCache(
3616
    string $fieldName,
3617
    string $sessionName,
3618
    bool $forceRefresh = false
3619
): array
3620
{
3621
    // Case when refresh is EXPECTED / MANDATORY
3622
    if ($forceRefresh === true) {
3623
        return [
3624
            'state' => false,
3625
            'data' => [],
3626
        ];
3627
    }
3628
3629
    // Get last folder update
3630
    $lastFolderChange = DB::queryfirstrow(
3631
        'SELECT valeur FROM ' . prefixTable('misc') . '
3632
        WHERE type = %s AND intitule = %s',
3633
        'timestamp',
3634
        'last_folder_change'
3635
    );
3636
    if (DB::count() === 0) {
3637
        $lastFolderChange['valeur'] = 0;
3638
    }
3639
3640
    // Case when an update in the tree has been done
3641
    // Refresh is then mandatory
3642
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3643
        return [
3644
            'state' => false,
3645
            'data' => [],
3646
        ];
3647
    }
3648
3649
    // Does this user has the tree structure in session?
3650
    // If yes then use it
3651
    if (count(isset($_SESSION['teampassUser'][$sessionName]) === true ? $_SESSION['teampassUser'][$sessionName] : []) > 0) {
3652
        return [
3653
            'state' => true,
3654
            'data' => json_encode($_SESSION['teampassUser'][$sessionName]),
3655
        ];
3656
    }
3657
3658
    // Does this user has a tree cache
3659
    $userCacheTree = DB::queryfirstrow(
3660
        'SELECT '.$fieldName.'
3661
        FROM ' . prefixTable('cache_tree') . '
3662
        WHERE user_id = %i',
3663
        $_SESSION['user_id']
3664
    );
3665
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3666
        return [
3667
            'state' => true,
3668
            'data' => $userCacheTree[$fieldName],
3669
        ];
3670
    }
3671
3672
    return [
3673
        'state' => false,
3674
        'data' => [],
3675
    ];
3676
}
3677
3678
3679
/**
3680
 * Permits to refresh the categories of folders
3681
 *
3682
 * @param array $folderIds
3683
 * @return void
3684
 */
3685
function handleFoldersCategories(
3686
    array $folderIds
3687
)
3688
{
3689
    //load ClassLoader
3690
    include_once __DIR__. '/../sources/SplClassLoader.php';
3691
    
3692
    //Connect to DB
3693
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3694
    if (defined('DB_PASSWD_CLEAR') === false) {
3695
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
3696
    }
3697
    DB::$host = DB_HOST;
3698
    DB::$user = DB_USER;
3699
    DB::$password = DB_PASSWD_CLEAR;
3700
    DB::$dbName = DB_NAME;
3701
    DB::$port = DB_PORT;
3702
    DB::$encoding = DB_ENCODING;
3703
    DB::$ssl = DB_SSL;
3704
    DB::$connect_options = DB_CONNECT_OPTIONS;
3705
3706
    $arr_data = array();
3707
3708
    // force full list of folders
3709
    if (count($folderIds) === 0) {
3710
        $folderIds = DB::queryFirstColumn(
3711
            'SELECT id
3712
            FROM ' . prefixTable('nested_tree') . '
3713
            WHERE personal_folder=%i',
3714
            0
3715
        );
3716
    }
3717
3718
    // Get complexity
3719
    defineComplexity();
3720
3721
    // update
3722
    foreach ($folderIds as $folder) {
3723
        // Do we have Categories
3724
        // get list of associated Categories
3725
        $arrCatList = array();
3726
        $rows_tmp = DB::query(
3727
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3728
            f.id_category AS category_id
3729
            FROM ' . prefixTable('categories_folders') . ' AS f
3730
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3731
            WHERE id_folder=%i',
3732
            $folder
3733
        );
3734
        if (DB::count() > 0) {
3735
            foreach ($rows_tmp as $row) {
3736
                $arrCatList[$row['id']] = array(
3737
                    'id' => $row['id'],
3738
                    'title' => $row['title'],
3739
                    'level' => $row['level'],
3740
                    'type' => $row['type'],
3741
                    'masked' => $row['masked'],
3742
                    'order' => $row['order'],
3743
                    'encrypted_data' => $row['encrypted_data'],
3744
                    'role_visibility' => $row['role_visibility'],
3745
                    'is_mandatory' => $row['is_mandatory'],
3746
                    'category_id' => $row['category_id'],
3747
                );
3748
            }
3749
        }
3750
        $arr_data['categories'] = $arrCatList;
3751
3752
        // Now get complexity
3753
        $valTemp = '';
3754
        $data = DB::queryFirstRow(
3755
            'SELECT valeur
3756
            FROM ' . prefixTable('misc') . '
3757
            WHERE type = %s AND intitule=%i',
3758
            'complex',
3759
            $folder
3760
        );
3761
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3762
            $valTemp = array(
3763
                'value' => $data['valeur'],
3764
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3765
            );
3766
        }
3767
        $arr_data['complexity'] = $valTemp;
3768
3769
        // Now get Roles
3770
        $valTemp = '';
3771
        $rows_tmp = DB::query(
3772
            'SELECT t.title
3773
            FROM ' . prefixTable('roles_values') . ' as v
3774
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3775
            WHERE v.folder_id = %i
3776
            GROUP BY title',
3777
            $folder
3778
        );
3779
        foreach ($rows_tmp as $record) {
3780
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3781
        }
3782
        $arr_data['visibilityRoles'] = $valTemp;
3783
3784
        // now save in DB
3785
        DB::update(
3786
            prefixTable('nested_tree'),
3787
            array(
3788
                'categories' => json_encode($arr_data),
3789
            ),
3790
            'id = %i',
3791
            $folder
3792
        );
3793
    }
3794
}
3795
3796
/**
3797
 * List all users that have specific roles
3798
 *
3799
 * @param array $roles
3800
 * @return array
3801
 */
3802
function getUsersWithRoles(
3803
    array $roles
3804
): array
3805
{
3806
    $arrUsers = array();
3807
3808
    foreach ($roles as $role) {
3809
        // loop on users and check if user has this role
3810
        $rows = DB::query(
3811
            'SELECT id, fonction_id
3812
            FROM ' . prefixTable('users') . '
3813
            WHERE id != %i',
3814
            $_SESSION['user_id']
3815
        );
3816
        foreach ($rows as $user) {
3817
            $userRoles = explode(';', $user['fonction_id']);
3818
            if (in_array($role, $userRoles, true) === true) {
3819
                array_push($arrUsers, $user['id']);
3820
            }
3821
        }
3822
    }
3823
3824
    return $arrUsers;
3825
}
3826