Passed
Push — master ( 31a4a5...10e222 )
by Nils
04:13
created

buildEmail()   D

Complexity

Conditions 11
Paths 384

Size

Total Lines 84
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 45
c 1
b 0
f 0
nc 384
nop 6
dl 0
loc 84
rs 4.1833

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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