Passed
Push — master ( 8df915...875b7f )
by Nils
10:39
created

identUserGetFoldersFromRoles()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 20
c 0
b 0
f 0
nc 12
nop 4
dl 0
loc 29
rs 8.6666
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
 * @file      main.functions.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use LdapRecord\Connection;
28
use ForceUTF8\Encoding;
29
30
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
31
    //die('Hacking attempt...');
32
}
33
34
// Load config if $SETTINGS not defined
35
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
36
    include_once __DIR__ . '/../includes/config/tp.config.php';
37
}
38
39
header('Content-type: text/html; charset=utf-8');
40
header('Cache-Control: no-cache, must-revalidate');
41
/**
42
 * Convert language code to string.
43
 *
44
 * @param string $string String to get
45
 */
46
function langHdl(string $string): string
47
{
48
    if (empty($string) === true) {
49
        // Manage error
50
        return 'ERROR in language strings!';
51
    }
52
53
    // Load superglobal
54
    include_once __DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
55
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
56
    // Get language string
57
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
58
    if (is_null($session_language) === true) {
59
        /* 
60
            Load the English version to $_SESSION so we don't 
61
            return bad JSON (multiple includes add BOM characters to the json returned 
62
            which makes jquery unhappy on the UI, especially on the log page)
63
            and improve performance by avoiding to include the file for every missing strings.
64
        */
65
        if (isset($_SESSION['teampass']) === false || isset($_SESSION['teampass']['en_lang'][trim($string)]) === false) {
66
            $_SESSION['teampass']['en_lang'] = include_once __DIR__. '/../includes/language/english.php';
67
            $session_language = isset($_SESSION['teampass']['en_lang'][trim($string)]) === false ? '' : $_SESSION['teampass']['en_lang'][trim($string)];
68
        } else {
69
            $session_language = $_SESSION['teampass']['en_lang'][trim($string)];
70
        }
71
    }
72
    // If after all this, we still don't have the string even in english (especially with old logs), return the language code
73
    if (empty($session_language) === true) {
74
        return trim($string);
75
    }
76
    //return (string) str_replace("'",  "&apos;", $session_language);
77
    return (string) $session_language;
78
}
79
80
/**
81
 * genHash().
82
 *
83
 * Generate a hash for user login
84
 *
85
 * @param string $password What password
86
 * @param string $cost     What cost
87
 *
88
 * @return string|void
89
 */
90
function bCrypt(
91
    string $password,
92
    string $cost
93
): ?string
94
{
95
    $salt = sprintf('$2y$%02d$', $cost);
96
    if (function_exists('openssl_random_pseudo_bytes')) {
97
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
98
    } else {
99
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
100
        for ($i = 0; $i < 22; ++$i) {
101
            $salt .= $chars[mt_rand(0, 63)];
102
        }
103
    }
104
105
    return crypt($password, $salt);
106
}
107
108
/**
109
 * Defuse cryption function.
110
 *
111
 * @param string $message   what to de/crypt
112
 * @param string $ascii_key key to use
113
 * @param string $type      operation to perform
114
 * @param array  $SETTINGS  Teampass settings
115
 *
116
 * @return array
117
 */
118
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
119
{
120
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
121
    $err = false;
122
    
123
    $path = __DIR__.'/../includes/libraries/Encryption/Encryption/';
124
125
    include_once $path . 'Exception/CryptoException.php';
126
    include_once $path . 'Exception/BadFormatException.php';
127
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
128
    include_once $path . 'Exception/IOException.php';
129
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
130
    include_once $path . 'Crypto.php';
131
    include_once $path . 'Encoding.php';
132
    include_once $path . 'DerivedKeys.php';
133
    include_once $path . 'Key.php';
134
    include_once $path . 'KeyOrPassword.php';
135
    include_once $path . 'File.php';
136
    include_once $path . 'RuntimeTests.php';
137
    include_once $path . 'KeyProtectedByPassword.php';
138
    include_once $path . 'Core.php';
139
    
140
    // convert KEY
141
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
142
    try {
143
        if ($type === 'encrypt') {
144
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
145
        } elseif ($type === 'decrypt') {
146
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
147
        }
148
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
149
        $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.';
150
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
151
        $err = $ex;
152
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
153
        $err = $ex;
154
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
155
        $err = $ex;
156
    } catch (Defuse\Crypto\Exception\IOException $ex) {
157
        $err = $ex;
158
    }
159
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
160
161
    return [
162
        'string' => $text ?? '',
163
        'error' => $err,
164
    ];
165
}
166
167
/**
168
 * Generating a defuse key.
169
 *
170
 * @return string
171
 */
172
function defuse_generate_key()
173
{
174
    // load PhpEncryption library
175
    if (file_exists('../includes/config/tp.config.php') === true) {
176
        $path = '../includes/libraries/Encryption/Encryption/';
177
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
178
        $path = './includes/libraries/Encryption/Encryption/';
179
    } else {
180
        $path = '../includes/libraries/Encryption/Encryption/';
181
    }
182
183
    include_once $path . 'Exception/CryptoException.php';
184
    include_once $path . 'Exception/BadFormatException.php';
185
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
186
    include_once $path . 'Exception/IOException.php';
187
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
188
    include_once $path . 'Crypto.php';
189
    include_once $path . 'Encoding.php';
190
    include_once $path . 'DerivedKeys.php';
191
    include_once $path . 'Key.php';
192
    include_once $path . 'KeyOrPassword.php';
193
    include_once $path . 'File.php';
194
    include_once $path . 'RuntimeTests.php';
195
    include_once $path . 'KeyProtectedByPassword.php';
196
    include_once $path . 'Core.php';
197
198
    $key = \Defuse\Crypto\Key::createNewRandomKey();
199
    $key = $key->saveToAsciiSafeString();
200
    return $key;
201
}
202
203
/**
204
 * Generate a Defuse personal key.
205
 *
206
 * @param string $psk psk used
207
 *
208
 * @return string
209
 */
210
function defuse_generate_personal_key(string $psk): string
211
{
212
    // load PhpEncryption library
213
    if (file_exists('../includes/config/tp.config.php') === true) {
214
        $path = '../includes/libraries/Encryption/Encryption/';
215
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
216
        $path = './includes/libraries/Encryption/Encryption/';
217
    } else {
218
        $path = '../includes/libraries/Encryption/Encryption/';
219
    }
220
221
    include_once $path . 'Exception/CryptoException.php';
222
    include_once $path . 'Exception/BadFormatException.php';
223
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
224
    include_once $path . 'Exception/IOException.php';
225
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
226
    include_once $path . 'Crypto.php';
227
    include_once $path . 'Encoding.php';
228
    include_once $path . 'DerivedKeys.php';
229
    include_once $path . 'Key.php';
230
    include_once $path . 'KeyOrPassword.php';
231
    include_once $path . 'File.php';
232
    include_once $path . 'RuntimeTests.php';
233
    include_once $path . 'KeyProtectedByPassword.php';
234
    include_once $path . 'Core.php';
235
    
236
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
237
    return $protected_key->saveToAsciiSafeString(); // save this in user table
238
}
239
240
/**
241
 * Validate persoanl key with defuse.
242
 *
243
 * @param string $psk                   the user's psk
244
 * @param string $protected_key_encoded special key
245
 *
246
 * @return string
247
 */
248
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
249
{
250
    // load PhpEncryption library
251
    if (file_exists('../includes/config/tp.config.php') === true) {
252
        $path = '../includes/libraries/Encryption/Encryption/';
253
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
254
        $path = './includes/libraries/Encryption/Encryption/';
255
    } else {
256
        $path = '../includes/libraries/Encryption/Encryption/';
257
    }
258
259
    include_once $path . 'Exception/CryptoException.php';
260
    include_once $path . 'Exception/BadFormatException.php';
261
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
262
    include_once $path . 'Exception/IOException.php';
263
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
264
    include_once $path . 'Crypto.php';
265
    include_once $path . 'Encoding.php';
266
    include_once $path . 'DerivedKeys.php';
267
    include_once $path . 'Key.php';
268
    include_once $path . 'KeyOrPassword.php';
269
    include_once $path . 'File.php';
270
    include_once $path . 'RuntimeTests.php';
271
    include_once $path . 'KeyProtectedByPassword.php';
272
    include_once $path . 'Core.php';
273
274
    try {
275
        $protected_key_encoded = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
276
        $user_key = $protected_key_encoded->unlockKey($psk);
277
        $user_key_encoded = $user_key->saveToAsciiSafeString();
278
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
279
        return 'Error - Major issue as the encryption is broken.';
280
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
281
        return 'Error - The saltkey is not the correct one.';
282
    }
283
284
    return $user_key_encoded;
285
    // store it in session once user has entered his psk
286
}
287
288
/**
289
 * Decrypt a defuse string if encrypted.
290
 *
291
 * @param string $value Encrypted string
292
 *
293
 * @return string Decrypted string
294
 */
295
function defuseReturnDecrypted(string $value, $SETTINGS): string
296
{
297
    if (substr($value, 0, 3) === 'def') {
298
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
299
    }
300
301
    return $value;
302
}
303
304
/**
305
 * Trims a string depending on a specific string.
306
 *
307
 * @param string|array $chaine  what to trim
308
 * @param string       $element trim on what
309
 *
310
 * @return string
311
 */
312
function trimElement($chaine, string $element): string
313
{
314
    if (! empty($chaine)) {
315
        if (is_array($chaine) === true) {
316
            $chaine = implode(';', $chaine);
317
        }
318
        $chaine = trim($chaine);
319
        if (substr($chaine, 0, 1) === $element) {
320
            $chaine = substr($chaine, 1);
321
        }
322
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
323
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
324
        }
325
    }
326
327
    return $chaine;
328
}
329
330
/**
331
 * Permits to suppress all "special" characters from string.
332
 *
333
 * @param string $string  what to clean
334
 * @param bool   $special use of special chars?
335
 *
336
 * @return string
337
 */
338
function cleanString(string $string, bool $special = false): string
339
{
340
    // Create temporary table for special characters escape
341
    $tabSpecialChar = [];
342
    for ($i = 0; $i <= 31; ++$i) {
343
        $tabSpecialChar[] = chr($i);
344
    }
345
    array_push($tabSpecialChar, '<br />');
346
    if ((int) $special === 1) {
347
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
348
    }
349
350
    return str_replace($tabSpecialChar, "\n", $string);
351
}
352
353
/**
354
 * Erro manager for DB.
355
 *
356
 * @param array $params output from query
357
 *
358
 * @return void
359
 */
360
function db_error_handler(array $params): void
361
{
362
    echo 'Error: ' . $params['error'] . "<br>\n";
363
    echo 'Query: ' . $params['query'] . "<br>\n";
364
    throw new Exception('Error - Query', 1);
365
}
366
367
/**
368
 * Identify user's rights
369
 *
370
 * @param string|array $groupesVisiblesUser  [description]
371
 * @param string|array $groupesInterditsUser [description]
372
 * @param string       $isAdmin              [description]
373
 * @param string       $idFonctions          [description]
374
 *
375
 * @return bool
376
 */
377
function identifyUserRights(
378
    $groupesVisiblesUser,
379
    $groupesInterditsUser,
380
    $isAdmin,
381
    $idFonctions,
382
    $SETTINGS
383
) {
384
    //load ClassLoader
385
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
386
    // Load superglobal
387
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
388
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
389
    //Connect to DB
390
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
391
    if (defined('DB_PASSWD_CLEAR') === false) {
392
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
393
    }
394
    DB::$host = DB_HOST;
395
    DB::$user = DB_USER;
396
    DB::$password = DB_PASSWD_CLEAR;
397
    DB::$dbName = DB_NAME;
398
    DB::$port = DB_PORT;
399
    DB::$encoding = DB_ENCODING;
400
    DB::$ssl = DB_SSL;
401
    DB::$connect_options = DB_CONNECT_OPTIONS;
402
    //Build tree
403
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
404
    $tree->register();
405
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
406
407
    // Check if user is ADMINISTRATOR    
408
    (int) $isAdmin === 1 ?
409
        identAdmin(
410
            $idFonctions,
411
            $SETTINGS, /** @scrutinizer ignore-type */
412
            $tree
413
        )
414
        :
415
        identUser(
416
            $groupesVisiblesUser,
417
            $groupesInterditsUser,
418
            $idFonctions,
419
            $SETTINGS, /** @scrutinizer ignore-type */
420
            $tree
421
        );
422
423
    // update user's timestamp
424
    DB::update(
425
        prefixTable('users'),
426
        [
427
            'timestamp' => time(),
428
        ],
429
        'id=%i',
430
        $superGlobal->get('user_id', 'SESSION')
431
    );
432
433
    return true;
434
}
435
436
/**
437
 * Identify administrator.
438
 *
439
 * @param string $idFonctions Roles of user
440
 * @param array  $SETTINGS    Teampass settings
441
 * @param array  $tree        Tree of folders
442
 *
443
 * @return bool
444
 */
445
function identAdmin($idFonctions, $SETTINGS, $tree)
446
{
447
    // Load superglobal
448
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
449
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
450
    // Init
451
    $groupesVisibles = [];
452
    $superGlobal->put('personal_folders', [], 'SESSION');
453
    $superGlobal->put('groupes_visibles', [], 'SESSION');
454
    $superGlobal->put('no_access_folders', [], 'SESSION');
455
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
456
    $superGlobal->put('read_only_folders', [], 'SESSION');
457
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
458
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
459
    $superGlobal->put('list_folders_limited', [], 'SESSION');
460
    $superGlobal->put('no_access_folders', [], 'SESSION');
461
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
462
    // Get superglobals
463
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
464
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
465
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
466
    // Get list of Folders
467
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
468
    foreach ($rows as $record) {
469
        array_push($groupesVisibles, $record['id']);
470
    }
471
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
472
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
473
    // Exclude all PF
474
    $where = new WhereClause('and');
475
    // create a WHERE statement of pieces joined by ANDs
476
    $where->add('personal_folder=%i', 1);
477
    if (
478
        isset($SETTINGS['enable_pf_feature']) === true
479
        && (int) $SETTINGS['enable_pf_feature'] === 1
480
    ) {
481
        $where->add('title=%s', $globalsUserId);
482
        $where->negateLast();
483
    }
484
    // Get ID of personal folder
485
    $persfld = DB::queryfirstrow(
486
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
487
        $globalsUserId
488
    );
489
    if (empty($persfld['id']) === false) {
490
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
491
            array_push($globalsVisibleFolders, $persfld['id']);
492
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
493
            // get all descendants
494
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
495
            $tree->rebuild();
496
            $tst = $tree->getDescendants($persfld['id']);
497
            foreach ($tst as $t) {
498
                array_push($globalsVisibleFolders, $t->id);
499
                array_push($globalsPersonalVisibleFolders, $t->id);
500
            }
501
        }
502
    }
503
504
    // get complete list of ROLES
505
    $tmp = explode(';', $idFonctions);
506
    $rows = DB::query(
507
        'SELECT * FROM ' . prefixTable('roles_title') . '
508
        ORDER BY title ASC'
509
    );
510
    foreach ($rows as $record) {
511
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
512
            array_push($tmp, $record['id']);
513
        }
514
    }
515
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
516
    $superGlobal->put('is_admin', 1, 'SESSION');
517
    // Check if admin has created Folders and Roles
518
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
519
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
520
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
521
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
522
523
    return true;
524
}
525
526
/**
527
 * Permits to convert an element to array.
528
 *
529
 * @param string|array $element Any value to be returned as array
530
 *
531
 * @return array
532
 */
533
function convertToArray($element): array
534
{
535
    if (is_string($element) === true) {
536
        if (empty($element) === true) {
537
            return [];
538
        }
539
        return explode(
540
            ';',
541
            trimElement($element, ';')
542
        );
543
    }
544
    return $element;
545
}
546
547
/**
548
 * Defines the rights the user has.
549
 *
550
 * @param string|array $allowedFolders  Allowed folders
551
 * @param string|array $noAccessFolders Not allowed folders
552
 * @param string|array $userRoles       Roles of user
553
 * @param array        $SETTINGS        Teampass settings
554
 * @param object       $tree            Tree of folders
555
 * 
556
 * @return bool
557
 */
558
function identUser(
559
    $allowedFolders,
560
    $noAccessFolders,
561
    $userRoles,
562
    array $SETTINGS,
563
    object $tree
564
) {
565
    // Load superglobal
566
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
567
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
568
    // Init
569
    $superGlobal->put('groupes_visibles', [], 'SESSION');
570
    $superGlobal->put('personal_folders', [], 'SESSION');
571
    $superGlobal->put('no_access_folders', [], 'SESSION');
572
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
573
    $superGlobal->put('read_only_folders', [], 'SESSION');
574
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
575
    $superGlobal->put('is_admin', 0, 'SESSION');
576
    // init
577
    $personalFolders = [];
578
    $readOnlyFolders = [];
579
    $noAccessPersonalFolders = [];
580
    $restrictedFoldersForItems = [];
581
    $foldersLimited = [];
582
    $foldersLimitedFull = [];
583
    $allowedFoldersByRoles = [];
584
    // Get superglobals
585
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
586
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
587
    // Ensure consistency in array format
588
    $noAccessFolders = convertToArray($noAccessFolders);
589
    $userRoles = convertToArray($userRoles);
590
    $allowedFolders = convertToArray($allowedFolders);
591
    
592
    // Get list of folders depending on Roles
593
    $arrays = identUserGetFoldersFromRoles(
594
        $userRoles,
595
        $allowedFoldersByRoles,
596
        $readOnlyFolders,
597
        $allowedFolders
598
    );
599
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
600
    $readOnlyFolders = $arrays['readOnlyFolders'];
601
602
    // Does this user is allowed to see other items
603
    $inc = 0;
604
    $rows = DB::query(
605
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
606
            WHERE restricted_to LIKE %ss AND inactif = %s'.
607
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
608
        $globalsUserId,
609
        '0'
610
    );
611
    foreach ($rows as $record) {
612
        // Exclude restriction on item if folder is fully accessible
613
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
614
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
615
            ++$inc;
616
        //}
617
    }
618
619
    // Check for the users roles if some specific rights exist on items
620
    $rows = DB::query(
621
        'SELECT i.id_tree, r.item_id
622
        FROM ' . prefixTable('items') . ' as i
623
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
624
        WHERE i.id_tree <> "" '.
625
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
626
        'ORDER BY i.id_tree ASC',
627
        $userRoles
628
    );
629
    $inc = 0;
630
    foreach ($rows as $record) {
631
        //if (isset($record['id_tree'])) {
632
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
633
            array_push($foldersLimitedFull, $record['id_tree']);
634
            ++$inc;
635
        //}
636
    }
637
638
    // Get list of Personal Folders
639
    $arrays = identUserGetPFList(
640
        $globalsPersonalFolders,
641
        $allowedFolders,
642
        $globalsUserId,
643
        $personalFolders,
644
        $noAccessPersonalFolders,
645
        $foldersLimitedFull,
646
        $allowedFoldersByRoles,
647
        array_keys($restrictedFoldersForItems),
648
        $readOnlyFolders,
649
        $noAccessFolders,
650
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
651
        $tree
652
    );
653
    $allowedFolders = $arrays['allowedFolders'];
654
    $personalFolders = $arrays['personalFolders'];
655
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
656
657
    // Return data
658
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
659
    $superGlobal->put('groupes_visibles', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC), 'SESSION');
660
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
661
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
662
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
663
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
664
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
665
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
666
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
667
    $superGlobal->put(
668
        'all_folders_including_no_access',
669
        array_unique(array_merge(
670
            $allowedFolders,
671
            $personalFolders,
672
            $noAccessFolders,
673
            $readOnlyFolders
674
        ), SORT_NUMERIC),
675
        'SESSION'
676
    );
677
    // Folders and Roles numbers
678
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
679
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
680
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
681
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
682
    // check if change proposals on User's items
683
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
684
        $countNewItems = DB::query(
685
            'SELECT COUNT(*)
686
            FROM ' . prefixTable('items_change') . ' AS c
687
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
688
            WHERE i.action = %s AND i.id_user = %i',
689
            'at_creation',
690
            $globalsUserId
691
        );
692
        $superGlobal->put('nb_item_change_proposals', $countNewItems, 'SESSION');
693
    } else {
694
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
695
    }
696
697
    return true;
698
}
699
700
/**
701
 * Get list of folders depending on Roles
702
 * 
703
 * @param array $userRoles
704
 * @param array $allowedFoldersByRoles
705
 * @param array $readOnlyFolders
706
 * @param array $allowedFolders
707
 * 
708
 * @return array
709
 */
710
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
711
{
712
    $rows = DB::query(
713
        'SELECT *
714
        FROM ' . prefixTable('roles_values') . '
715
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
716
        ['W', 'ND', 'NE', 'NDNE', 'R'],
717
        $userRoles,
718
    );
719
    foreach ($rows as $record) {
720
        if ($record['type'] === 'R') {
721
            array_push($readOnlyFolders, $record['folder_id']);
722
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
723
            array_push($allowedFoldersByRoles, $record['folder_id']);
724
        }
725
    }
726
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
727
    $readOnlyFolders = array_unique($readOnlyFolders);
728
    // Clean arrays
729
    foreach ($allowedFoldersByRoles as $value) {
730
        $key = array_search($value, $readOnlyFolders);
731
        if ($key !== false) {
732
            unset($readOnlyFolders[$key]);
733
        }
734
    }
735
736
    return [
737
        'readOnlyFolders' => $readOnlyFolders,
738
        'allowedFoldersByRoles' => $allowedFoldersByRoles
739
    ];
740
}
741
742
/**
743
 * Get list of Personal Folders
744
 * 
745
 * @param int $globalsPersonalFolders
746
 * @param array $allowedFolders
747
 * @param int $globalsUserId
748
 * @param array $personalFolders
749
 * @param array $noAccessPersonalFolders
750
 * @param array $foldersLimitedFull
751
 * @param array $allowedFoldersByRoles
752
 * @param array $restrictedFoldersForItems
753
 * @param array $readOnlyFolders
754
 * @param array $noAccessFolders
755
 * @param int $enablePfFeature
756
 * @param object $tree
757
 * 
758
 * @return array
759
 */
760
function identUserGetPFList(
761
    $globalsPersonalFolders,
762
    $allowedFolders,
763
    $globalsUserId,
764
    $personalFolders,
765
    $noAccessPersonalFolders,
766
    $foldersLimitedFull,
767
    $allowedFoldersByRoles,
768
    $restrictedFoldersForItems,
769
    $readOnlyFolders,
770
    $noAccessFolders,
771
    $enablePfFeature,
772
    $tree
773
)
774
{
775
    if (
776
        (int) $enablePfFeature === 1
777
        && (int) $globalsPersonalFolders === 1
778
    ) {
779
        $persoFld = DB::queryfirstrow(
780
            'SELECT id
781
            FROM ' . prefixTable('nested_tree') . '
782
            WHERE title = %s AND personal_folder = %i'.
783
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
784
            $globalsUserId,
785
            1
786
        );
787
        if (empty($persoFld['id']) === false) {
788
            array_push($personalFolders, $persoFld['id']);
789
            array_push($allowedFolders, $persoFld['id']);
790
            // get all descendants
791
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
792
            foreach ($ids as $id) {
793
                //array_push($allowedFolders, $id);
794
                array_push($personalFolders, $id);
795
            }
796
        }
797
    }
798
    
799
    // Exclude all other PF
800
    $where = new WhereClause('and');
801
    $where->add('personal_folder=%i', 1);
802
    if (count($personalFolders) > 0) {
803
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
804
    }
805
    if (
806
        (int) $enablePfFeature === 1
807
        && (int) $globalsPersonalFolders === 1
808
    ) {
809
        $where->add('title=%s', $globalsUserId);
810
        $where->negateLast();
811
    }
812
    $persoFlds = DB::query(
813
        'SELECT id
814
        FROM ' . prefixTable('nested_tree') . '
815
        WHERE %l',
816
        $where
817
    );
818
    foreach ($persoFlds as $persoFldId) {
819
        array_push($noAccessPersonalFolders, $persoFldId['id']);
820
    }
821
822
    // All folders visibles
823
    $allowedFolders = array_unique(array_merge(
824
        $allowedFolders,
825
        $foldersLimitedFull,
826
        $allowedFoldersByRoles,
827
        $restrictedFoldersForItems,
828
        $readOnlyFolders
829
    ), SORT_NUMERIC);
830
    // Exclude from allowed folders all the specific user forbidden folders
831
    if (count($noAccessFolders) > 0) {
832
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
833
    }
834
835
    return [
836
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
837
        'personalFolders' => $personalFolders,
838
        'noAccessPersonalFolders' => $noAccessPersonalFolders
839
    ];
840
}
841
842
843
/**
844
 * Update the CACHE table.
845
 *
846
 * @param string $action   What to do
847
 * @param array  $SETTINGS Teampass settings
848
 * @param int    $ident    Ident format
849
 * 
850
 * @return void
851
 */
852
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
853
{
854
    if ($action === 'reload') {
855
        // Rebuild full cache table
856
        cacheTableRefresh($SETTINGS);
857
    } elseif ($action === 'update_value' && is_null($ident) === false) {
858
        // UPDATE an item
859
        cacheTableUpdate($SETTINGS, $ident);
860
    } elseif ($action === 'add_value' && is_null($ident) === false) {
861
        // ADD an item
862
        cacheTableAdd($SETTINGS, $ident);
863
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
864
        // DELETE an item
865
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
866
    }
867
}
868
869
/**
870
 * Cache table - refresh.
871
 *
872
 * @param array $SETTINGS Teampass settings
873
 * 
874
 * @return void
875
 */
876
function cacheTableRefresh(array $SETTINGS): void
877
{
878
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
879
    //Connect to DB
880
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
881
    if (defined('DB_PASSWD_CLEAR') === false) {
882
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
883
    }
884
    DB::$host = DB_HOST;
885
    DB::$user = DB_USER;
886
    DB::$password = DB_PASSWD_CLEAR;
887
    DB::$dbName = DB_NAME;
888
    DB::$port = DB_PORT;
889
    DB::$encoding = DB_ENCODING;
890
    DB::$ssl = DB_SSL;
891
    DB::$connect_options = DB_CONNECT_OPTIONS;
892
    //Load Tree
893
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] .'/includes/libraries');
894
    $tree->register();
895
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
896
    // truncate table
897
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
898
    // reload date
899
    $rows = DB::query(
900
        'SELECT *
901
        FROM ' . prefixTable('items') . ' as i
902
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
903
        AND l.action = %s
904
        AND i.inactif = %i',
905
        'at_creation',
906
        0
907
    );
908
    foreach ($rows as $record) {
909
        if (empty($record['id_tree']) === false) {
910
            // Get all TAGS
911
            $tags = '';
912
            $itemTags = DB::query(
913
                'SELECT tag
914
                FROM ' . prefixTable('tags') . '
915
                WHERE item_id = %i AND tag != ""',
916
                $record['id']
917
            );
918
            foreach ($itemTags as $itemTag) {
919
                $tags .= $itemTag['tag'] . ' ';
920
            }
921
922
            // Get renewal period
923
            $resNT = DB::queryfirstrow(
924
                'SELECT renewal_period
925
                FROM ' . prefixTable('nested_tree') . '
926
                WHERE id = %i',
927
                $record['id_tree']
928
            );
929
            // form id_tree to full foldername
930
            $folder = [];
931
            $arbo = $tree->getPath($record['id_tree'], true);
932
            foreach ($arbo as $elem) {
933
                // Check if title is the ID of a user
934
                if (is_numeric($elem->title) === true) {
935
                    // Is this a User id?
936
                    $user = DB::queryfirstrow(
937
                        'SELECT id, login
938
                        FROM ' . prefixTable('users') . '
939
                        WHERE id = %i',
940
                        $elem->title
941
                    );
942
                    if (count($user) > 0) {
943
                        $elem->title = $user['login'];
944
                    }
945
                }
946
                // Build path
947
                array_push($folder, stripslashes($elem->title));
948
            }
949
            // store data
950
            DB::insert(
951
                prefixTable('cache'),
952
                [
953
                    'id' => $record['id'],
954
                    'label' => $record['label'],
955
                    'description' => $record['description'] ?? '',
956
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
957
                    'tags' => $tags,
958
                    'id_tree' => $record['id_tree'],
959
                    'perso' => $record['perso'],
960
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
961
                    'login' => $record['login'] ?? '',
962
                    'folder' => implode(' > ', $folder),
963
                    'author' => $record['id_user'],
964
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
965
                    'timestamp' => $record['date'],
966
                ]
967
            );
968
        }
969
    }
970
}
971
972
/**
973
 * Cache table - update existing value.
974
 *
975
 * @param array  $SETTINGS Teampass settings
976
 * @param int    $ident    Ident format
977
 * 
978
 * @return void
979
 */
980
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
981
{
982
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
983
    // Load superglobal
984
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
985
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
986
    //Connect to DB
987
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
988
    if (defined('DB_PASSWD_CLEAR') === false) {
989
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
990
    }
991
    DB::$host = DB_HOST;
992
    DB::$user = DB_USER;
993
    DB::$password = DB_PASSWD_CLEAR;
994
    DB::$dbName = DB_NAME;
995
    DB::$port = DB_PORT;
996
    DB::$encoding = DB_ENCODING;
997
    DB::$ssl = DB_SSL;
998
    DB::$connect_options = DB_CONNECT_OPTIONS;
999
    //Load Tree
1000
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1001
    $tree->register();
1002
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1003
    // get new value from db
1004
    $data = DB::queryfirstrow(
1005
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1006
        FROM ' . prefixTable('items') . '
1007
        WHERE id=%i',
1008
        $ident
1009
    );
1010
    // Get all TAGS
1011
    $tags = '';
1012
    $itemTags = DB::query(
1013
        'SELECT tag
1014
            FROM ' . prefixTable('tags') . '
1015
            WHERE item_id = %i AND tag != ""',
1016
        $ident
1017
    );
1018
    foreach ($itemTags as $itemTag) {
1019
        $tags .= $itemTag['tag'] . ' ';
1020
    }
1021
    // form id_tree to full foldername
1022
    $folder = [];
1023
    $arbo = $tree->getPath($data['id_tree'], true);
1024
    foreach ($arbo as $elem) {
1025
        // Check if title is the ID of a user
1026
        if (is_numeric($elem->title) === true) {
1027
            // Is this a User id?
1028
            $user = DB::queryfirstrow(
1029
                'SELECT id, login
1030
                FROM ' . prefixTable('users') . '
1031
                WHERE id = %i',
1032
                $elem->title
1033
            );
1034
            if (count($user) > 0) {
1035
                $elem->title = $user['login'];
1036
            }
1037
        }
1038
        // Build path
1039
        array_push($folder, stripslashes($elem->title));
1040
    }
1041
    // finaly update
1042
    DB::update(
1043
        prefixTable('cache'),
1044
        [
1045
            'label' => $data['label'],
1046
            'description' => $data['description'],
1047
            'tags' => $tags,
1048
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1049
            'id_tree' => $data['id_tree'],
1050
            'perso' => $data['perso'],
1051
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1052
            'login' => $data['login'] ?? '',
1053
            'folder' => implode(' » ', $folder),
1054
            'author' => $superGlobal->get('user_id', 'SESSION'),
1055
        ],
1056
        'id = %i',
1057
        $ident
1058
    );
1059
}
1060
1061
/**
1062
 * Cache table - add new value.
1063
 *
1064
 * @param array  $SETTINGS Teampass settings
1065
 * @param int    $ident    Ident format
1066
 * 
1067
 * @return void
1068
 */
1069
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1070
{
1071
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1072
    // Load superglobal
1073
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1074
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1075
    // Get superglobals
1076
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1077
    //Connect to DB
1078
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1079
    if (defined('DB_PASSWD_CLEAR') === false) {
1080
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1081
    }
1082
    DB::$host = DB_HOST;
1083
    DB::$user = DB_USER;
1084
    DB::$password = DB_PASSWD_CLEAR;
1085
    DB::$dbName = DB_NAME;
1086
    DB::$port = DB_PORT;
1087
    DB::$encoding = DB_ENCODING;
1088
    DB::$ssl = DB_SSL;
1089
    DB::$connect_options = DB_CONNECT_OPTIONS;
1090
    //Load Tree
1091
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1092
    $tree->register();
1093
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1094
    // get new value from db
1095
    $data = DB::queryFirstRow(
1096
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1097
        FROM ' . prefixTable('items') . ' as i
1098
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1099
        WHERE i.id = %i
1100
        AND l.action = %s',
1101
        $ident,
1102
        'at_creation'
1103
    );
1104
    // Get all TAGS
1105
    $tags = '';
1106
    $itemTags = DB::query(
1107
        'SELECT tag
1108
            FROM ' . prefixTable('tags') . '
1109
            WHERE item_id = %i AND tag != ""',
1110
        $ident
1111
    );
1112
    foreach ($itemTags as $itemTag) {
1113
        $tags .= $itemTag['tag'] . ' ';
1114
    }
1115
    // form id_tree to full foldername
1116
    $folder = [];
1117
    $arbo = $tree->getPath($data['id_tree'], true);
1118
    foreach ($arbo as $elem) {
1119
        // Check if title is the ID of a user
1120
        if (is_numeric($elem->title) === true) {
1121
            // Is this a User id?
1122
            $user = DB::queryfirstrow(
1123
                'SELECT id, login
1124
                FROM ' . prefixTable('users') . '
1125
                WHERE id = %i',
1126
                $elem->title
1127
            );
1128
            if (count($user) > 0) {
1129
                $elem->title = $user['login'];
1130
            }
1131
        }
1132
        // Build path
1133
        array_push($folder, stripslashes($elem->title));
1134
    }
1135
    // finaly update
1136
    DB::insert(
1137
        prefixTable('cache'),
1138
        [
1139
            'id' => $data['id'],
1140
            'label' => $data['label'],
1141
            'description' => $data['description'],
1142
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1143
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1144
            'id_tree' => $data['id_tree'],
1145
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1146
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1147
            'login' => $data['login'] ?? '',
1148
            'folder' => implode(' » ', $folder),
1149
            'author' => $globalsUserId,
1150
            'timestamp' => $data['date'],
1151
        ]
1152
    );
1153
}
1154
1155
/**
1156
 * Do statistics.
1157
 *
1158
 * @param array $SETTINGS Teampass settings
1159
 *
1160
 * @return array
1161
 */
1162
function getStatisticsData(array $SETTINGS): array
1163
{
1164
    DB::query(
1165
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1166
        0
1167
    );
1168
    $counter_folders = DB::count();
1169
    DB::query(
1170
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1171
        1
1172
    );
1173
    $counter_folders_perso = DB::count();
1174
    DB::query(
1175
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1176
        0
1177
    );
1178
    $counter_items = DB::count();
1179
        DB::query(
1180
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1181
        1
1182
    );
1183
    $counter_items_perso = DB::count();
1184
        DB::query(
1185
        'SELECT id FROM ' . prefixTable('users') . ''
1186
    );
1187
    $counter_users = DB::count();
1188
        DB::query(
1189
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1190
        1
1191
    );
1192
    $admins = DB::count();
1193
    DB::query(
1194
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1195
        1
1196
    );
1197
    $managers = DB::count();
1198
    DB::query(
1199
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1200
        1
1201
    );
1202
    $readOnly = DB::count();
1203
    // list the languages
1204
    $usedLang = [];
1205
    $tp_languages = DB::query(
1206
        'SELECT name FROM ' . prefixTable('languages')
1207
    );
1208
    foreach ($tp_languages as $tp_language) {
1209
        DB::query(
1210
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1211
            $tp_language['name']
1212
        );
1213
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1214
    }
1215
1216
    // get list of ips
1217
    $usedIp = [];
1218
    $tp_ips = DB::query(
1219
        'SELECT user_ip FROM ' . prefixTable('users')
1220
    );
1221
    foreach ($tp_ips as $ip) {
1222
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1223
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1224
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1225
            $usedIp[$ip['user_ip']] = 1;
1226
        }
1227
    }
1228
1229
    return [
1230
        'error' => '',
1231
        'stat_phpversion' => phpversion(),
1232
        'stat_folders' => $counter_folders,
1233
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1234
        'stat_items' => $counter_items,
1235
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1236
        'stat_users' => $counter_users,
1237
        'stat_admins' => $admins,
1238
        'stat_managers' => $managers,
1239
        'stat_ro' => $readOnly,
1240
        'stat_kb' => $SETTINGS['enable_kb'],
1241
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1242
        'stat_fav' => $SETTINGS['enable_favourites'],
1243
        'stat_teampassversion' => TP_VERSION,
1244
        'stat_ldap' => $SETTINGS['ldap_mode'],
1245
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1246
        'stat_duo' => $SETTINGS['duo'],
1247
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1248
        'stat_api' => $SETTINGS['api'],
1249
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1250
        'stat_syslog' => $SETTINGS['syslog_enable'],
1251
        'stat_2fa' => $SETTINGS['google_authentication'],
1252
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1253
        'stat_mysqlversion' => DB::serverVersion(),
1254
        'stat_languages' => $usedLang,
1255
        'stat_country' => $usedIp,
1256
    ];
1257
}
1258
1259
/**
1260
 * Permits to prepare the way to send the email
1261
 * 
1262
 * @param string $subject       email subject
1263
 * @param string $body          email message
1264
 * @param string $email         email
1265
 * @param string $receiverName  Receiver name
1266
 * @param array  $SETTINGS      settings
1267
 *
1268
 * @return void
1269
 */
1270
function prepareSendingEmail(
1271
    $subject,
1272
    $body,
1273
    $email,
1274
    $receiverName,
1275
    $SETTINGS
1276
): void 
1277
{
1278
    DB::insert(
1279
        prefixTable('processes'),
1280
        array(
1281
            'created_at' => time(),
1282
            'process_type' => 'send_email',
1283
            'arguments' => json_encode([
1284
                'subject' => $subject,
1285
                'receivers' => $email,
1286
                'body' => $body,
1287
                'receiver_name' => $receiverName,
1288
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1289
            'updated_at' => '',
1290
            'finished_at' => '',
1291
            'output' => '',
1292
        )
1293
    );
1294
}
1295
1296
/**
1297
 * Permits to send an email.
1298
 *
1299
 * @param string $subject     email subject
1300
 * @param string $textMail    email message
1301
 * @param string $email       email
1302
 * @param array  $SETTINGS    settings
1303
 * @param string $textMailAlt email message alt
1304
 * @param bool   $silent      no errors
1305
 *
1306
 * @return string some json info
1307
 */
1308
function sendEmail(
1309
    $subject,
1310
    $textMail,
1311
    $email,
1312
    $SETTINGS,
1313
    $textMailAlt = null,
1314
    $silent = true,
1315
    $cron = false
1316
) {
1317
    // CAse where email not defined
1318
    if ($email === 'none' || empty($email) === true) {
1319
        return json_encode(
1320
            [
1321
                'error' => true,
1322
                'message' => langHdl('forgot_my_pw_email_sent'),
1323
            ]
1324
        );
1325
    }
1326
1327
    // Build and send email
1328
    $email = buildEmail(
1329
        $subject,
1330
        $textMail,
1331
        $email,
1332
        $SETTINGS,
1333
        $textMailAlt = null,
1334
        $silent = true,
1335
        $cron
1336
    );
1337
1338
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1339
        return json_encode(
1340
            [
1341
                'error' => false,
1342
                'message' => langHdl('forgot_my_pw_email_sent'),
1343
            ]
1344
        );
1345
    }
1346
    // Debug purpose
1347
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1348
        return json_encode(
1349
            [
1350
                'error' => true,
1351
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1352
            ]
1353
        );
1354
    }
1355
    return json_encode(
1356
        [
1357
            'error' => false,
1358
            'message' => langHdl('share_sent_ok'),
1359
        ]
1360
    );
1361
}
1362
1363
1364
function buildEmail(
1365
    $subject,
1366
    $textMail,
1367
    $email,
1368
    $SETTINGS,
1369
    $textMailAlt = null,
1370
    $silent = true,
1371
    $cron = false
1372
)
1373
{
1374
    // Load settings
1375
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1376
    // Load superglobal
1377
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1378
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1379
    // Get user language
1380
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . (null !== $superGlobal->get('user_language', 'SESSION', 'user') ? $superGlobal->get('user_language', 'SESSION', 'user') : 'english') . '.php';
1381
    // Load library
1382
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1383
    // load PHPMailer
1384
    $mail = new SplClassLoader('PHPMailer\PHPMailer', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1385
    $mail->register();
1386
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1387
1388
    // send to user
1389
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1390
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false && $silent === false ? $SETTINGS['email_debug_level'] : 0;
1391
    $mail->Port = (int) $SETTINGS['email_port'];
1392
    //COULD BE USED
1393
    $mail->CharSet = 'utf-8';
1394
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1395
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1396
    $mail->SMTPOptions = [
1397
        'ssl' => [
1398
            'verify_peer' => false,
1399
            'verify_peer_name' => false,
1400
            'allow_self_signed' => true,
1401
        ],
1402
    ];
1403
    $mail->isSmtp();
1404
    // send via SMTP
1405
    $mail->Host = $SETTINGS['email_smtp_server'];
1406
    // SMTP servers
1407
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1408
    // turn on SMTP authentication
1409
    $mail->Username = $SETTINGS['email_auth_username'];
1410
    // SMTP username
1411
    $mail->Password = $SETTINGS['email_auth_pwd'];
1412
    // SMTP password
1413
    $mail->From = $SETTINGS['email_from'];
1414
    $mail->FromName = $SETTINGS['email_from_name'];
1415
    // Prepare for each person
1416
    foreach (array_filter(explode(',', $email)) as $dest) {
1417
        $mail->addAddress($dest);
1418
    }
1419
    
1420
    // Prepare HTML
1421
    $text_html = emailBody($textMail);
1422
    $mail->WordWrap = 80;
1423
    // set word wrap
1424
    $mail->isHtml(true);
1425
    // send as HTML
1426
    $mail->Subject = $subject;
1427
    $mail->Body = $text_html;
1428
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1429
1430
    try {
1431
        // send email
1432
        $mail->send();
1433
    } catch (Exception $e) {
1434
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1435
            return json_encode(
1436
                [
1437
                    'error' => true,
1438
                    'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1439
                ]
1440
            );
1441
        }
1442
        return '';
1443
    }
1444
    $mail->smtpClose();
1445
1446
    return json_encode(
1447
        [
1448
            'error' => true,
1449
            'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1450
        ]
1451
    );
1452
}
1453
1454
/**
1455
 * Returns the email body.
1456
 *
1457
 * @param string $textMail Text for the email
1458
 */
1459
function emailBody(string $textMail): string
1460
{
1461
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1462
    w3.org/TR/html4/loose.dtd"><html>
1463
    <head><title>Email Template</title>
1464
    <style type="text/css">
1465
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1466
    </style></head>
1467
    <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">
1468
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1469
    <tr><td style="border-collapse: collapse;"><br>
1470
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1471
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1472
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1473
        </td></tr></table></td>
1474
    </tr>
1475
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1476
        <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;">
1477
        <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;">
1478
        <br><div style="float:right;">' .
1479
        $textMail .
1480
        '<br><br></td></tr></table>
1481
    </td></tr></table>
1482
    <br></body></html>';
1483
}
1484
1485
/**
1486
 * Generate a Key.
1487
 * 
1488
 * @return string
1489
 */
1490
function generateKey(): string
1491
{
1492
    return substr(md5(rand() . rand()), 0, 15);
1493
}
1494
1495
/**
1496
 * Convert date to timestamp.
1497
 *
1498
 * @param string $date        The date
1499
 * @param string $date_format Date format
1500
 *
1501
 * @return int
1502
 */
1503
function dateToStamp(string $date, string $date_format): int
1504
{
1505
    $date = date_parse_from_format($date_format, $date);
1506
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1507
        return mktime(
1508
            empty($date['hour']) === false ? $date['hour'] : 23,
1509
            empty($date['minute']) === false ? $date['minute'] : 59,
1510
            empty($date['second']) === false ? $date['second'] : 59,
1511
            $date['month'],
1512
            $date['day'],
1513
            $date['year']
1514
        );
1515
    }
1516
    return 0;
1517
}
1518
1519
/**
1520
 * Is this a date.
1521
 *
1522
 * @param string $date Date
1523
 *
1524
 * @return bool
1525
 */
1526
function isDate(string $date): bool
1527
{
1528
    return strtotime($date) !== false;
1529
}
1530
1531
/**
1532
 * Check if isUTF8().
1533
 *
1534
 * @param string|array $string Is the string
1535
 *
1536
 * @return int is the string in UTF8 format
1537
 */
1538
function isUTF8($string): int
1539
{
1540
    if (is_array($string) === true) {
1541
        $string = $string['string'];
1542
    }
1543
1544
    return preg_match(
1545
        '%^(?:
1546
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1547
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1548
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1549
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1550
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1551
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1552
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1553
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1554
        )*$%xs',
1555
        $string
1556
    );
1557
}
1558
1559
/**
1560
 * Prepare an array to UTF8 format before JSON_encode.
1561
 *
1562
 * @param array $array Array of values
1563
 *
1564
 * @return array
1565
 */
1566
function utf8Converter(array $array): array
1567
{
1568
    array_walk_recursive(
1569
        $array,
1570
        static function (&$item): void {
1571
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1572
                $item = utf8_encode($item);
1573
            }
1574
        }
1575
    );
1576
    return $array;
1577
}
1578
1579
/**
1580
 * Permits to prepare data to be exchanged.
1581
 *
1582
 * @param string       $teampassDir
1583
 * @param array|string $data Text
1584
 * @param string       $type Parameter
1585
 * @param string       $key  Optional key
1586
 *
1587
 * @return string|array
1588
 */
1589
function prepareExchangedData($teampassDir, $data, string $type, ?string $key = null)
1590
{
1591
    $teampassDir = __DIR__ . '/..';
1592
    // Load superglobal
1593
    include_once $teampassDir . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1594
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1595
    // Get superglobals
1596
    if ($key !== null) {
1597
        $superGlobal->put('key', $key, 'SESSION');
1598
        $globalsKey = $key;
1599
    } else {
1600
        $globalsKey = $superGlobal->get('key', 'SESSION');
1601
    }
1602
1603
    //load Encoding
1604
    include_once $teampassDir . '/includes/libraries/ForceUTF8/Encoding.php';
1605
    
1606
    //Load CRYPTOJS
1607
    include_once $teampassDir . '/includes/libraries/Encryption/CryptoJs/Encryption.php';
1608
1609
    // Perform
1610
    if ($type === 'encode' && is_array($data) === true) {
1611
        // Now encode
1612
        return Encryption\CryptoJs\Encryption::encrypt(
1613
            json_encode(
1614
                $data,
1615
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1616
            ),
1617
            $globalsKey
1618
        );
1619
    }
1620
    if ($type === 'decode' && is_array($data) === false) {
1621
        // check if key exists
1622
        return json_decode(
1623
            (string) Encryption\CryptoJs\Encryption::decrypt(
1624
                (string) $data,
1625
                $globalsKey
1626
            ),
1627
            true
1628
        );
1629
    }
1630
}
1631
1632
1633
/**
1634
 * Create a thumbnail.
1635
 *
1636
 * @param string  $src           Source
1637
 * @param string  $dest          Destination
1638
 * @param int $desired_width Size of width
1639
 * 
1640
 * @return void|string|bool
1641
 */
1642
function makeThumbnail(string $src, string $dest, int $desired_width)
1643
{
1644
    /* read the source image */
1645
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1646
        $source_image = imagecreatefrompng($src);
1647
        if ($source_image === false) {
1648
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1649
        }
1650
    } else {
1651
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1652
    }
1653
1654
    // Get height and width
1655
    $width = imagesx($source_image);
1656
    $height = imagesy($source_image);
1657
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1658
    $desired_height = (int) floor($height * $desired_width / $width);
1659
    /* create a new, "virtual" image */
1660
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1661
    if ($virtual_image === false) {
1662
        return false;
1663
    }
1664
    /* copy source image at a resized size */
1665
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1666
    /* create the physical thumbnail image to its destination */
1667
    imagejpeg($virtual_image, $dest);
1668
}
1669
1670
/**
1671
 * Check table prefix in SQL query.
1672
 *
1673
 * @param string $table Table name
1674
 * 
1675
 * @return string
1676
 */
1677
function prefixTable(string $table): string
1678
{
1679
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1680
    if (! empty($safeTable)) {
1681
        // sanitize string
1682
        return $safeTable;
1683
    }
1684
    // stop error no table
1685
    return 'table_not_exists';
1686
}
1687
1688
/**
1689
 * GenerateCryptKey
1690
 *
1691
 * @param int     $size      Length
1692
 * @param bool $secure Secure
1693
 * @param bool $numerals Numerics
1694
 * @param bool $uppercase Uppercase letters
1695
 * @param bool $symbols Symbols
1696
 * @param bool $lowercase Lowercase
1697
 * @param array   $SETTINGS  SETTINGS
1698
 * 
1699
 * @return string
1700
 */
1701
function GenerateCryptKey(
1702
    int $size = 20,
1703
    bool $secure = false,
1704
    bool $numerals = false,
1705
    bool $uppercase = false,
1706
    bool $symbols = false,
1707
    bool $lowercase = false,
1708
    array $SETTINGS = []
1709
): string {
1710
    include_once __DIR__ . '/../sources/SplClassLoader.php';
1711
    $generator = new SplClassLoader('PasswordGenerator\Generator', __DIR__. '/../includes/libraries');
1712
    $generator->register();
1713
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1714
    // Is PHP7 being used?
1715
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1716
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', __DIR__ . '/../includes/libraries');
1717
        $php7generator->register();
1718
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1719
    }
1720
    
1721
    // Manage size
1722
    $generator->setLength((int) $size);
1723
    if ($secure === true) {
1724
        $generator->setSymbols(true);
1725
        $generator->setLowercase(true);
1726
        $generator->setUppercase(true);
1727
        $generator->setNumbers(true);
1728
    } else {
1729
        $generator->setLowercase($lowercase);
1730
        $generator->setUppercase($uppercase);
1731
        $generator->setNumbers($numerals);
1732
        $generator->setSymbols($symbols);
1733
    }
1734
1735
    return $generator->generatePasswords()[0];
1736
}
1737
1738
/**
1739
 * Send sysLOG message
1740
 *
1741
 * @param string    $message
1742
 * @param string    $host
1743
 * @param int       $port
1744
 * @param string    $component
1745
 * 
1746
 * @return void
1747
*/
1748
function send_syslog($message, $host, $port, $component = 'teampass'): void
1749
{
1750
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1751
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1752
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1753
    socket_close($sock);
1754
}
1755
1756
/**
1757
 * Permits to log events into DB
1758
 *
1759
 * @param array  $SETTINGS Teampass settings
1760
 * @param string $type     Type
1761
 * @param string $label    Label
1762
 * @param string $who      Who
1763
 * @param string $login    Login
1764
 * @param string $field_1  Field
1765
 * 
1766
 * @return void
1767
 */
1768
function logEvents(
1769
    array $SETTINGS, 
1770
    string $type, 
1771
    string $label, 
1772
    string $who, 
1773
    ?string $login = null, 
1774
    ?string $field_1 = null
1775
): void
1776
{
1777
    if (empty($who)) {
1778
        $who = getClientIpServer();
1779
    }
1780
1781
    // include librairies & connect to DB
1782
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1783
    if (defined('DB_PASSWD_CLEAR') === false) {
1784
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1785
    }
1786
    DB::$host = DB_HOST;
1787
    DB::$user = DB_USER;
1788
    DB::$password = DB_PASSWD_CLEAR;
1789
    DB::$dbName = DB_NAME;
1790
    DB::$port = DB_PORT;
1791
    DB::$encoding = DB_ENCODING;
1792
    DB::$ssl = DB_SSL;
1793
    DB::$connect_options = DB_CONNECT_OPTIONS;
1794
    DB::insert(
1795
        prefixTable('log_system'),
1796
        [
1797
            'type' => $type,
1798
            'date' => time(),
1799
            'label' => $label,
1800
            'qui' => $who,
1801
            'field_1' => $field_1 === null ? '' : $field_1,
1802
        ]
1803
    );
1804
    // If SYSLOG
1805
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1806
        if ($type === 'user_mngt') {
1807
            send_syslog(
1808
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1809
                $SETTINGS['syslog_host'],
1810
                $SETTINGS['syslog_port'],
1811
                'teampass'
1812
            );
1813
        } else {
1814
            send_syslog(
1815
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1816
                $SETTINGS['syslog_host'],
1817
                $SETTINGS['syslog_port'],
1818
                'teampass'
1819
            );
1820
        }
1821
    }
1822
}
1823
1824
/**
1825
 * Log events.
1826
 *
1827
 * @param array  $SETTINGS        Teampass settings
1828
 * @param int    $item_id         Item id
1829
 * @param string $item_label      Item label
1830
 * @param int    $id_user         User id
1831
 * @param string $action          Code for reason
1832
 * @param string $login           User login
1833
 * @param string $raison          Code for reason
1834
 * @param string $encryption_type Encryption on
1835
 * 
1836
 * @return void
1837
 */
1838
function logItems(
1839
    array $SETTINGS,
1840
    int $item_id,
1841
    string $item_label,
1842
    int $id_user,
1843
    string $action,
1844
    ?string $login = null,
1845
    ?string $raison = null,
1846
    ?string $encryption_type = null,
1847
    ?string $time = null
1848
): void {
1849
    // include librairies & connect to DB
1850
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1851
    if (defined('DB_PASSWD_CLEAR') === false) {
1852
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1853
    }
1854
    DB::$host = DB_HOST;
1855
    DB::$user = DB_USER;
1856
    DB::$password = DB_PASSWD_CLEAR;
1857
    DB::$dbName = DB_NAME;
1858
    DB::$port = DB_PORT;
1859
    DB::$encoding = DB_ENCODING;
1860
    DB::$ssl = DB_SSL;
1861
    DB::$connect_options = DB_CONNECT_OPTIONS;
1862
    // Insert log in DB
1863
    DB::insert(
1864
        prefixTable('log_items'),
1865
        [
1866
            'id_item' => $item_id,
1867
            'date' => is_null($time) === true ? time() : $time,
1868
            'id_user' => $id_user,
1869
            'action' => $action,
1870
            'raison' => $raison,
1871
            'raison_iv' => '',
1872
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1873
        ]
1874
    );
1875
    // Timestamp the last change
1876
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1877
        DB::update(
1878
            prefixTable('misc'),
1879
            [
1880
                'valeur' => time(),
1881
            ],
1882
            'type = %s AND intitule = %s',
1883
            'timestamp',
1884
            'last_item_change'
1885
        );
1886
    }
1887
1888
    // SYSLOG
1889
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1890
        // Extract reason
1891
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1892
        // Get item info if not known
1893
        if (empty($item_label) === true) {
1894
            $dataItem = DB::queryfirstrow(
1895
                'SELECT id, id_tree, label
1896
                FROM ' . prefixTable('items') . '
1897
                WHERE id = %i',
1898
                $item_id
1899
            );
1900
            $item_label = $dataItem['label'];
1901
        }
1902
1903
        send_syslog(
1904
            'action=' . str_replace('at_', '', $action) .
1905
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1906
                ' itemno=' . $item_id .
1907
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1908
                ' itemname="' . addslashes($item_label) . '"',
1909
            $SETTINGS['syslog_host'],
1910
            $SETTINGS['syslog_port'],
1911
            'teampass'
1912
        );
1913
    }
1914
1915
    // send notification if enabled
1916
    //notifyOnChange($item_id, $action, $SETTINGS);
1917
}
1918
1919
/**
1920
 * If enabled, then notify admin/manager.
1921
 *
1922
 * @param int    $item_id  Item id
1923
 * @param string $action   Action to do
1924
 * @param array  $SETTINGS Teampass settings
1925
 * 
1926
 * @return void
1927
 */
1928
/*
1929
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1930
{
1931
    if (
1932
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1933
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1934
        && $action === 'at_shown'
1935
    ) {
1936
        // Load superglobal
1937
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1938
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1939
        // Get superglobals
1940
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1941
        $globalsName = $superGlobal->get('name', 'SESSION');
1942
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1943
        // Get info about item
1944
        $dataItem = DB::queryfirstrow(
1945
            'SELECT id, id_tree, label
1946
            FROM ' . prefixTable('items') . '
1947
            WHERE id = %i',
1948
            $item_id
1949
        );
1950
        $item_label = $dataItem['label'];
1951
        // send back infos
1952
        DB::insert(
1953
            prefixTable('emails'),
1954
            [
1955
                'timestamp' => time(),
1956
                'subject' => langHdl('email_on_open_notification_subject'),
1957
                'body' => str_replace(
1958
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1959
                    [
1960
                        addslashes($globalsName . ' ' . $globalsLastname),
1961
                        addslashes($item_label),
1962
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1963
                    ],
1964
                    langHdl('email_on_open_notification_mail')
1965
                ),
1966
                'receivers' => $globalsNotifiedEmails,
1967
                'status' => '',
1968
            ]
1969
        );
1970
    }
1971
}
1972
*/
1973
1974
/**
1975
 * Prepare notification email to subscribers.
1976
 *
1977
 * @param int    $item_id  Item id
1978
 * @param string $label    Item label
1979
 * @param array  $changes  List of changes
1980
 * @param array  $SETTINGS Teampass settings
1981
 * 
1982
 * @return void
1983
 */
1984
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1985
{
1986
    // Load superglobal
1987
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1988
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1989
    // Get superglobals
1990
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1991
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1992
    $globalsName = $superGlobal->get('name', 'SESSION');
1993
    // send email to user that what to be notified
1994
    $notification = DB::queryOneColumn(
1995
        'email',
1996
        'SELECT *
1997
        FROM ' . prefixTable('notification') . ' AS n
1998
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1999
        WHERE n.item_id = %i AND n.user_id != %i',
2000
        $item_id,
2001
        $globalsUserId
2002
    );
2003
    if (DB::count() > 0) {
2004
        // Prepare path
2005
        $path = geItemReadablePath($item_id, '', $SETTINGS);
2006
        // Get list of changes
2007
        $htmlChanges = '<ul>';
2008
        foreach ($changes as $change) {
2009
            $htmlChanges .= '<li>' . $change . '</li>';
2010
        }
2011
        $htmlChanges .= '</ul>';
2012
        // send email
2013
        DB::insert(
2014
            prefixTable('emails'),
2015
            [
2016
                'timestamp' => time(),
2017
                'subject' => langHdl('email_subject_item_updated'),
2018
                'body' => str_replace(
2019
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
2020
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
2021
                    langHdl('email_body_item_updated')
2022
                ),
2023
                'receivers' => implode(',', $notification),
2024
                'status' => '',
2025
            ]
2026
        );
2027
    }
2028
}
2029
2030
/**
2031
 * Returns the Item + path.
2032
 *
2033
 * @param int    $id_tree  Node id
2034
 * @param string $label    Label
2035
 * @param array  $SETTINGS TP settings
2036
 * 
2037
 * @return string
2038
 */
2039
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
2040
{
2041
    // Class loader
2042
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
2043
    //Load Tree
2044
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
2045
    $tree->register();
2046
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
2047
    $arbo = $tree->getPath($id_tree, true);
2048
    $path = '';
2049
    foreach ($arbo as $elem) {
2050
        if (empty($path) === true) {
2051
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
2052
        } else {
2053
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
2054
        }
2055
    }
2056
2057
    // Build text to show user
2058
    if (empty($label) === false) {
2059
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
2060
    }
2061
    return empty($path) === true ? '' : $path;
2062
}
2063
2064
/**
2065
 * Get the client ip address.
2066
 *
2067
 * @return string IP address
2068
 */
2069
function getClientIpServer(): string
2070
{
2071
    if (getenv('HTTP_CLIENT_IP')) {
2072
        $ipaddress = getenv('HTTP_CLIENT_IP');
2073
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
2074
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
2075
    } elseif (getenv('HTTP_X_FORWARDED')) {
2076
        $ipaddress = getenv('HTTP_X_FORWARDED');
2077
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
2078
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
2079
    } elseif (getenv('HTTP_FORWARDED')) {
2080
        $ipaddress = getenv('HTTP_FORWARDED');
2081
    } elseif (getenv('REMOTE_ADDR')) {
2082
        $ipaddress = getenv('REMOTE_ADDR');
2083
    } else {
2084
        $ipaddress = 'UNKNOWN';
2085
    }
2086
2087
    return $ipaddress;
2088
}
2089
2090
/**
2091
 * Escape all HTML, JavaScript, and CSS.
2092
 *
2093
 * @param string $input    The input string
2094
 * @param string $encoding Which character encoding are we using?
2095
 * 
2096
 * @return string
2097
 */
2098
function noHTML(string $input, string $encoding = 'UTF-8'): string
2099
{
2100
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2101
}
2102
2103
/**
2104
 * Permits to handle the Teampass config file
2105
 * $action accepts "rebuild" and "update"
2106
 *
2107
 * @param string $action   Action to perform
2108
 * @param array  $SETTINGS Teampass settings
2109
 * @param string $field    Field to refresh
2110
 * @param string $value    Value to set
2111
 *
2112
 * @return string|bool
2113
 */
2114
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2115
{
2116
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2117
    // include librairies & connect to DB
2118
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2119
    if (defined('DB_PASSWD_CLEAR') === false) {
2120
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2121
    }
2122
    DB::$host = DB_HOST;
2123
    DB::$user = DB_USER;
2124
    DB::$password = DB_PASSWD_CLEAR;
2125
    DB::$dbName = DB_NAME;
2126
    DB::$port = DB_PORT;
2127
    DB::$encoding = DB_ENCODING;
2128
    DB::$ssl = DB_SSL;
2129
    DB::$connect_options = DB_CONNECT_OPTIONS;
2130
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2131
        // perform a copy
2132
        if (file_exists($tp_config_file)) {
2133
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2134
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2135
            }
2136
        }
2137
2138
        // regenerate
2139
        $data = [];
2140
        $data[0] = "<?php\n";
2141
        $data[1] = "global \$SETTINGS;\n";
2142
        $data[2] = "\$SETTINGS = array (\n";
2143
        $rows = DB::query(
2144
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2145
            'admin'
2146
        );
2147
        foreach ($rows as $record) {
2148
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
2149
        }
2150
        array_push($data, ");\n");
2151
        $data = array_unique($data);
2152
    // ---
2153
    } elseif ($action === 'update' && empty($field) === false) {
2154
        $data = file($tp_config_file);
2155
        $inc = 0;
2156
        $bFound = false;
2157
        foreach ($data as $line) {
2158
            if (stristr($line, ');')) {
2159
                break;
2160
            }
2161
2162
            if (stristr($line, "'" . $field . "' => '")) {
2163
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value, ENT_COMPAT) . "',\n";
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type null; however, parameter $string of htmlspecialchars_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

2163
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode(/** @scrutinizer ignore-type */ $value, ENT_COMPAT) . "',\n";
Loading history...
2164
                $bFound = true;
2165
                break;
2166
            }
2167
            ++$inc;
2168
        }
2169
        if ($bFound === false) {
2170
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value, ENT_COMPAT). "',\n);\n";
2171
        }
2172
    }
2173
2174
    // update file
2175
    file_put_contents($tp_config_file, implode('', $data ?? []));
2176
    return true;
2177
}
2178
2179
/**
2180
 * Permits to replace &#92; to permit correct display
2181
 *
2182
 * @param string $input Some text
2183
 * 
2184
 * @return string
2185
 */
2186
function handleBackslash(string $input): string
2187
{
2188
    return str_replace('&amp;#92;', '&#92;', $input);
2189
}
2190
2191
/**
2192
 * Permits to load settings
2193
 * 
2194
 * @return void
2195
*/
2196
function loadSettings(): void
2197
{
2198
    global $SETTINGS;
2199
    /* LOAD CPASSMAN SETTINGS */
2200
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2201
        $SETTINGS = [];
2202
        $SETTINGS['duplicate_folder'] = 0;
2203
        //by default, this is set to 0;
2204
        $SETTINGS['duplicate_item'] = 0;
2205
        //by default, this is set to 0;
2206
        $SETTINGS['number_of_used_pw'] = 5;
2207
        //by default, this value is set to 5;
2208
        $settings = [];
2209
        $rows = DB::query(
2210
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2211
            [
2212
                'type' => 'admin',
2213
                'type2' => 'settings',
2214
            ]
2215
        );
2216
        foreach ($rows as $record) {
2217
            if ($record['type'] === 'admin') {
2218
                $SETTINGS[$record['intitule']] = $record['valeur'];
2219
            } else {
2220
                $settings[$record['intitule']] = $record['valeur'];
2221
            }
2222
        }
2223
        $SETTINGS['loaded'] = 1;
2224
        $SETTINGS['default_session_expiration_time'] = 5;
2225
    }
2226
}
2227
2228
/**
2229
 * check if folder has custom fields.
2230
 * Ensure that target one also has same custom fields
2231
 * 
2232
 * @param int $source_id
2233
 * @param int $target_id 
2234
 * 
2235
 * @return bool
2236
*/
2237
function checkCFconsistency(int $source_id, int $target_id): bool
2238
{
2239
    $source_cf = [];
2240
    $rows = DB::QUERY(
2241
        'SELECT id_category
2242
            FROM ' . prefixTable('categories_folders') . '
2243
            WHERE id_folder = %i',
2244
        $source_id
2245
    );
2246
    foreach ($rows as $record) {
2247
        array_push($source_cf, $record['id_category']);
2248
    }
2249
2250
    $target_cf = [];
2251
    $rows = DB::QUERY(
2252
        'SELECT id_category
2253
            FROM ' . prefixTable('categories_folders') . '
2254
            WHERE id_folder = %i',
2255
        $target_id
2256
    );
2257
    foreach ($rows as $record) {
2258
        array_push($target_cf, $record['id_category']);
2259
    }
2260
2261
    $cf_diff = array_diff($source_cf, $target_cf);
2262
    if (count($cf_diff) > 0) {
2263
        return false;
2264
    }
2265
2266
    return true;
2267
}
2268
2269
/**
2270
 * Will encrypte/decrypt a fil eusing Defuse.
2271
 *
2272
 * @param string $type        can be either encrypt or decrypt
2273
 * @param string $source_file path to source file
2274
 * @param string $target_file path to target file
2275
 * @param array  $SETTINGS    Settings
2276
 * @param string $password    A password
2277
 *
2278
 * @return string|bool
2279
 */
2280
function prepareFileWithDefuse(
2281
    string $type,
2282
    string $source_file,
2283
    string $target_file,
2284
    array $SETTINGS,
2285
    string $password = null
2286
) {
2287
    // Load AntiXSS
2288
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
2289
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2290
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
2291
    $antiXss = new voku\helper\AntiXSS();
2292
    // Protect against bad inputs
2293
    if (is_array($source_file) === true || is_array($target_file) === true) {
2294
        return 'error_cannot_be_array';
2295
    }
2296
2297
    // Sanitize
2298
    $source_file = $antiXss->xss_clean($source_file);
2299
    $target_file = $antiXss->xss_clean($target_file);
2300
    if (empty($password) === true || is_null($password) === true) {
2301
        // get KEY to define password
2302
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2303
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2304
    }
2305
2306
    $err = '';
2307
    if ($type === 'decrypt') {
2308
        // Decrypt file
2309
        $err = defuseFileDecrypt(
2310
            $source_file,
2311
            $target_file,
2312
            $SETTINGS, /** @scrutinizer ignore-type */
2313
            $password
2314
        );
2315
    } elseif ($type === 'encrypt') {
2316
        // Encrypt file
2317
        $err = defuseFileEncrypt(
2318
            $source_file,
2319
            $target_file,
2320
            $SETTINGS, /** @scrutinizer ignore-type */
2321
            $password
2322
        );
2323
    }
2324
2325
    // return error
2326
    return $err === true ? '' : $err;
2327
}
2328
2329
/**
2330
 * Encrypt a file with Defuse.
2331
 *
2332
 * @param string $source_file path to source file
2333
 * @param string $target_file path to target file
2334
 * @param array  $SETTINGS    Settings
2335
 * @param string $password    A password
2336
 *
2337
 * @return string|bool
2338
 */
2339
function defuseFileEncrypt(
2340
    string $source_file,
2341
    string $target_file,
2342
    array $SETTINGS,
2343
    string $password = null
2344
) {
2345
    // load PhpEncryption library
2346
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2347
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2348
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2349
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2350
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2351
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2352
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2353
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2354
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2355
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2356
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2357
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2358
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2359
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2360
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2361
    try {
2362
        \Defuse\Crypto\File::encryptFileWithPassword(
2363
            $source_file,
2364
            $target_file,
2365
            $password
2366
        );
2367
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2368
        $err = 'wrong_key';
2369
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2370
        $err = $ex;
2371
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2372
        $err = $ex;
2373
    }
2374
2375
    // return error
2376
    return empty($err) === false ? $err : true;
2377
}
2378
2379
/**
2380
 * Decrypt a file with Defuse.
2381
 *
2382
 * @param string $source_file path to source file
2383
 * @param string $target_file path to target file
2384
 * @param array  $SETTINGS    Settings
2385
 * @param string $password    A password
2386
 *
2387
 * @return string|bool
2388
 */
2389
function defuseFileDecrypt(
2390
    string $source_file,
2391
    string $target_file,
2392
    array $SETTINGS,
2393
    string $password = null
2394
) {
2395
    // load PhpEncryption library
2396
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2397
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2398
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2399
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2400
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2401
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2402
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2403
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2404
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2405
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2406
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2407
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2408
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2409
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2410
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2411
    try {
2412
        \Defuse\Crypto\File::decryptFileWithPassword(
2413
            $source_file,
2414
            $target_file,
2415
            $password
2416
        );
2417
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2418
        $err = 'wrong_key';
2419
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2420
        $err = $ex;
2421
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2422
        $err = $ex;
2423
    }
2424
2425
    // return error
2426
    return empty($err) === false ? $err : true;
2427
}
2428
2429
/*
2430
* NOT TO BE USED
2431
*/
2432
/**
2433
 * Undocumented function.
2434
 *
2435
 * @param string $text Text to debug
2436
 */
2437
function debugTeampass(string $text): void
2438
{
2439
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2440
    if ($debugFile !== false) {
2441
        fputs($debugFile, $text);
2442
        fclose($debugFile);
2443
    }
2444
}
2445
2446
/**
2447
 * DELETE the file with expected command depending on server type.
2448
 *
2449
 * @param string $file     Path to file
2450
 * @param array  $SETTINGS Teampass settings
2451
 *
2452
 * @return void
2453
 */
2454
function fileDelete(string $file, array $SETTINGS): void
2455
{
2456
    // Load AntiXSS
2457
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
2458
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2459
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
2460
    $antiXss = new voku\helper\AntiXSS();
2461
    $file = $antiXss->xss_clean($file);
2462
    if (is_file($file)) {
2463
        unlink($file);
2464
    }
2465
}
2466
2467
/**
2468
 * Permits to extract the file extension.
2469
 *
2470
 * @param string $file File name
2471
 *
2472
 * @return string
2473
 */
2474
function getFileExtension(string $file): string
2475
{
2476
    if (strpos($file, '.') === false) {
2477
        return $file;
2478
    }
2479
2480
    return substr($file, strrpos($file, '.') + 1);
2481
}
2482
2483
/**
2484
 * Chmods files and folders with different permissions.
2485
 *
2486
 * This is an all-PHP alternative to using: \n
2487
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2488
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2489
 *
2490
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2491
  *
2492
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2493
 * @param int    $filePerm The permissions any found files should get.
2494
 * @param int    $dirPerm  The permissions any found folder should get.
2495
 *
2496
 * @return bool Returns TRUE if the path if found and FALSE if not.
2497
 *
2498
 * @warning The permission levels has to be entered in octal format, which
2499
 * normally means adding a zero ("0") in front of the permission level. \n
2500
 * More info at: http://php.net/chmod.
2501
*/
2502
2503
function recursiveChmod(
2504
    string $path,
2505
    int $filePerm = 0644,
2506
    int  $dirPerm = 0755
2507
) {
2508
    // Check if the path exists
2509
    if (! file_exists($path)) {
2510
        return false;
2511
    }
2512
2513
    // See whether this is a file
2514
    if (is_file($path)) {
2515
        // Chmod the file with our given filepermissions
2516
        chmod($path, $filePerm);
2517
    // If this is a directory...
2518
    } elseif (is_dir($path)) {
2519
        // Then get an array of the contents
2520
        $foldersAndFiles = scandir($path);
2521
        // Remove "." and ".." from the list
2522
        $entries = array_slice($foldersAndFiles, 2);
2523
        // Parse every result...
2524
        foreach ($entries as $entry) {
2525
            // And call this function again recursively, with the same permissions
2526
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2527
        }
2528
2529
        // When we are done with the contents of the directory, we chmod the directory itself
2530
        chmod($path, $dirPerm);
2531
    }
2532
2533
    // Everything seemed to work out well, return true
2534
    return true;
2535
}
2536
2537
/**
2538
 * Check if user can access to this item.
2539
 *
2540
 * @param int   $item_id ID of item
2541
 * @param array $SETTINGS
2542
 *
2543
 * @return bool|string
2544
 */
2545
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2546
{
2547
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2548
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2549
    // Prepare superGlobal variables
2550
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2551
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2552
    // Load item data
2553
    $data = DB::queryFirstRow(
2554
        'SELECT id_tree
2555
        FROM ' . prefixTable('items') . '
2556
        WHERE id = %i',
2557
        $item_id
2558
    );
2559
    // Check if user can access this folder
2560
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2561
        // Now check if this folder is restricted to user
2562
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2563
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2564
        ) {
2565
            return 'ERR_FOLDER_NOT_ALLOWED';
2566
        }
2567
    }
2568
2569
    return true;
2570
}
2571
2572
/**
2573
 * Creates a unique key.
2574
 *
2575
 * @param int $lenght Key lenght
2576
 *
2577
 * @return string
2578
 */
2579
function uniqidReal(int $lenght = 13): string
2580
{
2581
    if (function_exists('random_bytes')) {
2582
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2583
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2584
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2585
    } else {
2586
        throw new Exception('no cryptographically secure random function available');
2587
    }
2588
2589
    return substr(bin2hex($bytes), 0, $lenght);
2590
}
2591
2592
/**
2593
 * Obfuscate an email.
2594
 *
2595
 * @param string $email Email address
2596
 *
2597
 * @return string
2598
 */
2599
function obfuscateEmail(string $email): string
2600
{
2601
    $email = explode("@", $email);
2602
    $name = $email[0];
2603
    if (strlen($name) > 3) {
2604
        $name = substr($name, 0, 2);
2605
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2606
            $name .= "*";
2607
        }
2608
        $name .= substr($email[0], -1, 1);
2609
    }
2610
    $host = explode(".", $email[1])[0];
2611
    if (strlen($host) > 3) {
2612
        $host = substr($host, 0, 1);
2613
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2614
            $host .= "*";
2615
        }
2616
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2617
    }
2618
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2619
    return $email;
2620
}
2621
2622
/**
2623
 * Perform a Query.
2624
 *
2625
 * @param array  $SETTINGS Teamapss settings
2626
 * @param string $fields   Fields to use
2627
 * @param string $table    Table to use
2628
 *
2629
 * @return array
2630
 */
2631
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2632
{
2633
    // include librairies & connect to DB
2634
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2635
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2636
    if (defined('DB_PASSWD_CLEAR') === false) {
2637
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2638
    }
2639
    DB::$host = DB_HOST;
2640
    DB::$user = DB_USER;
2641
    DB::$password = DB_PASSWD_CLEAR;
2642
    DB::$dbName = DB_NAME;
2643
    DB::$port = DB_PORT;
2644
    DB::$encoding = DB_ENCODING;
2645
    DB::$ssl = DB_SSL;
2646
    DB::$connect_options = DB_CONNECT_OPTIONS;
2647
    // Insert log in DB
2648
    return DB::query(
2649
        'SELECT ' . $fields . '
2650
        FROM ' . prefixTable($table)
2651
    );
2652
}
2653
2654
/**
2655
 * Undocumented function.
2656
 *
2657
 * @param int $bytes Size of file
2658
 *
2659
 * @return string
2660
 */
2661
function formatSizeUnits(int $bytes): string
2662
{
2663
    if ($bytes >= 1073741824) {
2664
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2665
    } elseif ($bytes >= 1048576) {
2666
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2667
    } elseif ($bytes >= 1024) {
2668
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2669
    } elseif ($bytes > 1) {
2670
        $bytes .= ' bytes';
2671
    } elseif ($bytes === 1) {
2672
        $bytes .= ' byte';
2673
    } else {
2674
        $bytes = '0 bytes';
2675
    }
2676
2677
    return $bytes;
2678
}
2679
2680
/**
2681
 * Generate user pair of keys.
2682
 *
2683
 * @param string $userPwd User password
2684
 *
2685
 * @return array
2686
 */
2687
function generateUserKeys(string $userPwd): array
2688
{
2689
    // include library
2690
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2691
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2692
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2693
    // Load classes
2694
    $rsa = new Crypt_RSA();
2695
    $cipher = new Crypt_AES();
2696
    // Create the private and public key
2697
    $res = $rsa->createKey(4096);
2698
    // Encrypt the privatekey
2699
    $cipher->setPassword($userPwd);
2700
    $privatekey = $cipher->encrypt($res['privatekey']);
2701
    return [
2702
        'private_key' => base64_encode($privatekey),
2703
        'public_key' => base64_encode($res['publickey']),
2704
        'private_key_clear' => base64_encode($res['privatekey']),
2705
    ];
2706
}
2707
2708
/**
2709
 * Permits to decrypt the user's privatekey.
2710
 *
2711
 * @param string $userPwd        User password
2712
 * @param string $userPrivateKey User private key
2713
 *
2714
 * @return string
2715
 */
2716
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2717
{
2718
    if (empty($userPwd) === false) {
2719
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2720
        // Load classes
2721
        $cipher = new Crypt_AES();
2722
        // Encrypt the privatekey
2723
        $cipher->setPassword($userPwd);
2724
        try {
2725
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2726
        } catch (Exception $e) {
2727
            return $e;
2728
        }
2729
    }
2730
    return '';
2731
}
2732
2733
/**
2734
 * Permits to encrypt the user's privatekey.
2735
 *
2736
 * @param string $userPwd        User password
2737
 * @param string $userPrivateKey User private key
2738
 *
2739
 * @return string
2740
 */
2741
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2742
{
2743
    if (empty($userPwd) === false) {
2744
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2745
        // Load classes
2746
        $cipher = new Crypt_AES();
2747
        // Encrypt the privatekey
2748
        $cipher->setPassword($userPwd);        
2749
        try {
2750
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2751
        } catch (Exception $e) {
2752
            return $e;
2753
        }
2754
    }
2755
    return '';
2756
}
2757
2758
/**
2759
 * Encrypts a string using AES.
2760
 *
2761
 * @param string $data String to encrypt
2762
 * @param string $key
2763
 *
2764
 * @return array
2765
 */
2766
function doDataEncryption(string $data, string $key = NULL): array
2767
{
2768
    // Includes
2769
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2770
    // Load classes
2771
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2772
    // Generate an object key
2773
    $objectKey = is_null($key) === true ? uniqidReal(32) : $key;
2774
    // Set it as password
2775
    $cipher->setPassword($objectKey);
2776
    return [
2777
        'encrypted' => base64_encode($cipher->encrypt($data)),
2778
        'objectKey' => base64_encode($objectKey),
2779
    ];
2780
}
2781
2782
/**
2783
 * Decrypts a string using AES.
2784
 *
2785
 * @param string $data Encrypted data
2786
 * @param string $key  Key to uncrypt
2787
 *
2788
 * @return string
2789
 */
2790
function doDataDecryption(string $data, string $key): string
2791
{
2792
    // Includes
2793
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2794
    // Load classes
2795
    $cipher = new Crypt_AES();
2796
    // Set the object key
2797
    $cipher->setPassword(base64_decode($key));
2798
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2799
}
2800
2801
/**
2802
 * Encrypts using RSA a string using a public key.
2803
 *
2804
 * @param string $key       Key to be encrypted
2805
 * @param string $publicKey User public key
2806
 *
2807
 * @return string
2808
 */
2809
function encryptUserObjectKey(string $key, string $publicKey): string
2810
{
2811
    // Includes
2812
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2813
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2814
    // Load classes
2815
    $rsa = new Crypt_RSA();
2816
    $rsa->loadKey(base64_decode($publicKey));
2817
    // Encrypt
2818
    return base64_encode($rsa->encrypt(base64_decode($key)));
2819
}
2820
2821
/**
2822
 * Decrypts using RSA an encrypted string using a private key.
2823
 *
2824
 * @param string $key        Encrypted key
2825
 * @param string $privateKey User private key
2826
 *
2827
 * @return string
2828
 */
2829
function decryptUserObjectKey(string $key, string $privateKey): string
2830
{
2831
    // Includes
2832
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2833
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2834
    // Load classes
2835
    $rsa = new Crypt_RSA();
2836
    $rsa->loadKey(base64_decode($privateKey));
2837
    // Decrypt
2838
    try {
2839
        $tmpValue = $rsa->decrypt(base64_decode($key));
2840
        if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2841
            $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2842
        } else {
2843
            $ret = '';
2844
        }
2845
    } catch (Exception $e) {
2846
        return $e;
2847
    }
2848
2849
    return $ret;
2850
}
2851
2852
/**
2853
 * Encrypts a file.
2854
 *
2855
 * @param string $fileInName File name
2856
 * @param string $fileInPath Path to file
2857
 *
2858
 * @return array
2859
 */
2860
function encryptFile(string $fileInName, string $fileInPath): array
2861
{
2862
    if (defined('FILE_BUFFER_SIZE') === false) {
2863
        define('FILE_BUFFER_SIZE', 128 * 1024);
2864
    }
2865
2866
    // Includes
2867
    include_once __DIR__.'/../includes/config/include.php';
2868
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2869
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2870
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2871
    // Load classes
2872
    $cipher = new Crypt_AES();
2873
    // Generate an object key
2874
    $objectKey = uniqidReal(32);
2875
    // Set it as password
2876
    $cipher->setPassword($objectKey);
2877
    // Prevent against out of memory
2878
    $cipher->enableContinuousBuffer();
2879
    //$cipher->disablePadding();
2880
2881
    // Encrypt the file content
2882
    $plaintext = file_get_contents(
2883
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2884
    );
2885
    $ciphertext = $cipher->encrypt($plaintext);
2886
    // Save new file
2887
    $hash = md5($plaintext);
2888
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2889
    file_put_contents($fileOut, $ciphertext);
2890
    unlink($fileInPath . '/' . $fileInName);
2891
    return [
2892
        'fileHash' => base64_encode($hash),
2893
        'objectKey' => base64_encode($objectKey),
2894
    ];
2895
}
2896
2897
/**
2898
 * Decrypt a file.
2899
 *
2900
 * @param string $fileName File name
2901
 * @param string $filePath Path to file
2902
 * @param string $key      Key to use
2903
 *
2904
 * @return string
2905
 */
2906
function decryptFile(string $fileName, string $filePath, string $key): string
2907
{
2908
    if (! defined('FILE_BUFFER_SIZE')) {
2909
        define('FILE_BUFFER_SIZE', 128 * 1024);
2910
    }
2911
2912
    // Includes
2913
    include_once __DIR__.'/../includes/config/include.php';
2914
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2915
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2916
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2917
    // Get file name
2918
    $fileName = base64_decode($fileName);
2919
    // Load classes
2920
    $cipher = new Crypt_AES();
2921
    // Set the object key
2922
    $cipher->setPassword(base64_decode($key));
2923
    // Prevent against out of memory
2924
    $cipher->enableContinuousBuffer();
2925
    $cipher->disablePadding();
2926
    // Get file content
2927
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2928
    // Decrypt file content and return
2929
    return base64_encode($cipher->decrypt($ciphertext));
2930
}
2931
2932
/**
2933
 * Generate a simple password
2934
 *
2935
 * @param int $length Length of string
2936
 * @param bool $symbolsincluded Allow symbols
2937
 *
2938
 * @return string
2939
 */
2940
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2941
{
2942
    // Generate new user password
2943
    $small_letters = range('a', 'z');
2944
    $big_letters = range('A', 'Z');
2945
    $digits = range(0, 9);
2946
    $symbols = $symbolsincluded === true ?
2947
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2948
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2949
    $count = count($res);
2950
    // first variant
2951
2952
    $random_string = '';
2953
    for ($i = 0; $i < $length; ++$i) {
2954
        $random_string .= $res[random_int(0, $count - 1)];
2955
    }
2956
2957
    return $random_string;
2958
}
2959
2960
/**
2961
 * Permit to store the sharekey of an object for users.
2962
 *
2963
 * @param string $object_name             Type for table selection
2964
 * @param int    $post_folder_is_personal Personal
2965
 * @param int    $post_folder_id          Folder
2966
 * @param int    $post_object_id          Object
2967
 * @param string $objectKey               Object key
2968
 * @param array  $SETTINGS                Teampass settings
2969
 *
2970
 * @return void
2971
 */
2972
function storeUsersShareKey(
2973
    string $object_name,
2974
    int $post_folder_is_personal,
2975
    int $post_folder_id,
2976
    int $post_object_id,
2977
    string $objectKey,
2978
    array $SETTINGS
2979
): void {
2980
    // include librairies & connect to DB
2981
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2982
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2983
    if (defined('DB_PASSWD_CLEAR') === false) {
2984
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2985
    }
2986
    DB::$host = DB_HOST;
2987
    DB::$user = DB_USER;
2988
    DB::$password = DB_PASSWD_CLEAR;
2989
    DB::$dbName = DB_NAME;
2990
    DB::$port = DB_PORT;
2991
    DB::$encoding = DB_ENCODING;
2992
    DB::$ssl = DB_SSL;
2993
    DB::$connect_options = DB_CONNECT_OPTIONS;
2994
    // Delete existing entries for this object
2995
    DB::delete(
2996
        $object_name,
2997
        'object_id = %i',
2998
        $post_object_id
2999
    );
3000
    // Superglobals
3001
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
3002
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
3003
    // Prepare superGlobal variables
3004
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
3005
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
3006
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
3007
    if (
3008
        (int) $post_folder_is_personal === 1
3009
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
3010
    ) {
3011
        // If this is a personal object
3012
        // Only create the sharekey for user
3013
        DB::insert(
3014
            $object_name,
3015
            [
3016
                'object_id' => (int) $post_object_id,
3017
                'user_id' => (int) $sessionUserId,
3018
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
3019
            ]
3020
        );
3021
    } else {
3022
        // This is a public object
3023
        // Create sharekey for each user
3024
        $users = DB::query(
3025
            'SELECT id, public_key
3026
            FROM ' . prefixTable('users') . '
3027
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
3028
            AND public_key != ""'
3029
        );
3030
        foreach ($users as $user) {
3031
            // Insert in DB the new object key for this item by user
3032
            DB::insert(
3033
                $object_name,
3034
                [
3035
                    'object_id' => $post_object_id,
3036
                    'user_id' => (int) $user['id'],
3037
                    'share_key' => encryptUserObjectKey(
3038
                        $objectKey,
3039
                        $user['public_key']
3040
                    ),
3041
                ]
3042
            );
3043
        }
3044
    }
3045
}
3046
3047
/**
3048
 * Is this string base64 encoded?
3049
 *
3050
 * @param string $str Encoded string?
3051
 *
3052
 * @return bool
3053
 */
3054
function isBase64(string $str): bool
3055
{
3056
    $str = (string) trim($str);
3057
    if (! isset($str[0])) {
3058
        return false;
3059
    }
3060
3061
    $base64String = (string) base64_decode($str, true);
3062
    if ($base64String && base64_encode($base64String) === $str) {
3063
        return true;
3064
    }
3065
3066
    return false;
3067
}
3068
3069
/**
3070
 * Undocumented function
3071
 *
3072
 * @param string $field Parameter
3073
 *
3074
 * @return array|bool|resource|string
3075
 */
3076
function filterString(string $field)
3077
{
3078
    // Sanitize string
3079
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
3080
    if (empty($field) === false) {
3081
        // Load AntiXSS
3082
        include_once __DIR__.'/../includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
3083
        $antiXss = new voku\helper\AntiXSS();
3084
        // Return
3085
        return $antiXss->xss_clean($field);
3086
    }
3087
3088
    return false;
3089
}
3090
3091
/**
3092
 * CHeck if provided credentials are allowed on server
3093
 *
3094
 * @param string $login    User Login
3095
 * @param string $password User Pwd
3096
 * @param array  $SETTINGS Teampass settings
3097
 *
3098
 * @return bool
3099
 */
3100
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3101
{
3102
    // Build ldap configuration array
3103
    $config = [
3104
        // Mandatory Configuration Options
3105
        'hosts' => [$SETTINGS['ldap_hosts']],
3106
        'base_dn' => $SETTINGS['ldap_bdn'],
3107
        'username' => $SETTINGS['ldap_username'],
3108
        'password' => $SETTINGS['ldap_password'],
3109
3110
        // Optional Configuration Options
3111
        'port' => $SETTINGS['ldap_port'],
3112
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3113
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3114
        'version' => 3,
3115
        'timeout' => 5,
3116
        'follow_referrals' => false,
3117
3118
        // Custom LDAP Options
3119
        'options' => [
3120
            // See: http://php.net/ldap_set_option
3121
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
3122
        ],
3123
    ];
3124
    // Load expected libraries
3125
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3126
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3127
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3128
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3129
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3130
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3131
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3132
    $ad->register();
3133
    $connection = new Connection($config);
3134
    // Connect to LDAP
3135
    try {
3136
        $connection->connect();
3137
    } catch (\LdapRecord\Auth\BindException $e) {
3138
        $error = $e->getDetailedError();
3139
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3140
        return false;
3141
    }
3142
3143
    // Authenticate user
3144
    try {
3145
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3146
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3147
        } else {
3148
            $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);
3149
        }
3150
    } catch (\LdapRecord\Auth\BindException $e) {
3151
        $error = $e->getDetailedError();
3152
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3153
        return false;
3154
    }
3155
3156
    return true;
3157
}
3158
3159
/**
3160
 * Removes from DB all sharekeys of this user
3161
 *
3162
 * @param int $userId User's id
3163
 * @param array   $SETTINGS Teampass settings
3164
 *
3165
 * @return bool
3166
 */
3167
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
3168
{
3169
    // include librairies & connect to DB
3170
    include_once __DIR__. '/../includes/config/settings.php';
3171
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3172
    if (defined('DB_PASSWD_CLEAR') === false) {
3173
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3174
    }
3175
    DB::$host = DB_HOST;
3176
    DB::$user = DB_USER;
3177
    DB::$password = DB_PASSWD_CLEAR;
3178
    DB::$dbName = DB_NAME;
3179
    DB::$port = DB_PORT;
3180
    DB::$encoding = DB_ENCODING;
3181
    DB::$ssl = DB_SSL;
3182
    DB::$connect_options = DB_CONNECT_OPTIONS;
3183
    // Remove all item sharekeys items
3184
    DB::delete(
3185
        prefixTable('sharekeys_items'),
3186
        'user_id = %i',
3187
        $userId
3188
    );
3189
    // Remove all item sharekeys files
3190
    DB::delete(
3191
        prefixTable('sharekeys_files'),
3192
        'user_id = %i',
3193
        $userId
3194
    );
3195
    // Remove all item sharekeys fields
3196
    DB::delete(
3197
        prefixTable('sharekeys_fields'),
3198
        'user_id = %i',
3199
        $userId
3200
    );
3201
    // Remove all item sharekeys logs
3202
    DB::delete(
3203
        prefixTable('sharekeys_logs'),
3204
        'user_id = %i',
3205
        $userId
3206
    );
3207
    // Remove all item sharekeys suggestions
3208
    DB::delete(
3209
        prefixTable('sharekeys_suggestions'),
3210
        'user_id = %i',
3211
        $userId
3212
    );
3213
    return false;
3214
}
3215
3216
/**
3217
 * Manage list of timezones   $SETTINGS Teampass settings
3218
 *
3219
 * @return array
3220
 */
3221
function timezone_list()
3222
{
3223
    static $timezones = null;
3224
    if ($timezones === null) {
3225
        $timezones = [];
3226
        $offsets = [];
3227
        $now = new DateTime('now', new DateTimeZone('UTC'));
3228
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3229
            $now->setTimezone(new DateTimeZone($timezone));
3230
            $offsets[] = $offset = $now->getOffset();
3231
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3232
        }
3233
3234
        array_multisort($offsets, $timezones);
3235
    }
3236
3237
    return $timezones;
3238
}
3239
3240
/**
3241
 * Provide timezone offset
3242
 *
3243
 * @param int $offset Timezone offset
3244
 *
3245
 * @return string
3246
 */
3247
function format_GMT_offset($offset): string
3248
{
3249
    $hours = intval($offset / 3600);
3250
    $minutes = abs(intval($offset % 3600 / 60));
3251
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3252
}
3253
3254
/**
3255
 * Provides timezone name
3256
 *
3257
 * @param string $name Timezone name
3258
 *
3259
 * @return string
3260
 */
3261
function format_timezone_name($name): string
3262
{
3263
    $name = str_replace('/', ', ', $name);
3264
    $name = str_replace('_', ' ', $name);
3265
3266
    return str_replace('St ', 'St. ', $name);
3267
}
3268
3269
/**
3270
 * Provides info if user should use MFA based on roles
3271
 *
3272
 * @param string $userRolesIds  User roles ids
3273
 * @param string $mfaRoles      Roles for which MFA is requested
3274
 *
3275
 * @return bool
3276
 */
3277
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3278
{
3279
    if (empty($mfaRoles) === true) {
3280
        return true;
3281
    }
3282
3283
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3284
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3285
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3286
        return true;
3287
    }
3288
3289
    return false;
3290
}
3291
3292
/**
3293
 * Permits to clean a string for export purpose
3294
 *
3295
 * @param string $text
3296
 * @param bool $emptyCheckOnly
3297
 * 
3298
 * @return string
3299
 */
3300
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3301
{
3302
    if (is_null($text) === true || empty($text) === true) {
3303
        return '';
3304
    }
3305
    // only expected to check if $text was empty
3306
    elseif ($emptyCheckOnly === true) {
3307
        return $text;
3308
    }
3309
3310
    return strip_tags(
3311
        cleanString(
3312
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3313
            true)
3314
        );
3315
}
3316
3317
/**
3318
 * Permits to check if user ID is valid
3319
 *
3320
 * @param integer $post_user_id
3321
 * @return bool
3322
 */
3323
function isUserIdValid($userId): bool
3324
{
3325
    if (is_null($userId) === false
3326
        && isset($userId) === true
3327
        && empty($userId) === false
3328
    ) {
3329
        return true;
3330
    }
3331
    return false;
3332
}
3333
3334
/**
3335
 * Check if a key exists and if its value equal the one expected
3336
 *
3337
 * @param string $key
3338
 * @param integer|string $value
3339
 * @param array $array
3340
 * 
3341
 * @return boolean
3342
 */
3343
function isKeyExistingAndEqual(
3344
    string $key,
3345
    /*PHP8 - integer|string*/$value,
3346
    array $array
3347
): bool
3348
{
3349
    if (isset($array[$key]) === true
3350
        && (is_int($value) === true ?
3351
            (int) $array[$key] === $value :
3352
            (string) $array[$key] === $value)
3353
    ) {
3354
        return true;
3355
    }
3356
    return false;
3357
}
3358
3359
/**
3360
 * Check if a variable is not set or equal to a value
3361
 *
3362
 * @param string|null $var
3363
 * @param integer|string $value
3364
 * 
3365
 * @return boolean
3366
 */
3367
function isKeyNotSetOrEqual(
3368
    /*PHP8 - string|null*/$var,
3369
    /*PHP8 - integer|string*/$value
3370
): bool
3371
{
3372
    if (isset($var) === false
3373
        || (is_int($value) === true ?
3374
            (int) $var === $value :
3375
            (string) $var === $value)
3376
    ) {
3377
        return true;
3378
    }
3379
    return false;
3380
}
3381
3382
/**
3383
 * Check if a key exists and if its value < to the one expected
3384
 *
3385
 * @param string $key
3386
 * @param integer $value
3387
 * @param array $array
3388
 * 
3389
 * @return boolean
3390
 */
3391
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3392
{
3393
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3394
        return true;
3395
    }
3396
    return false;
3397
}
3398
3399
/**
3400
 * Check if a key exists and if its value > to the one expected
3401
 *
3402
 * @param string $key
3403
 * @param integer $value
3404
 * @param array $array
3405
 * 
3406
 * @return boolean
3407
 */
3408
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3409
{
3410
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3411
        return true;
3412
    }
3413
    return false;
3414
}
3415
3416
/**
3417
 * Check if values in array are set
3418
 * Return true if all set
3419
 * Return false if one of them is not set
3420
 *
3421
 * @param array $arrayOfValues
3422
 * @return boolean
3423
 */
3424
function isSetArrayOfValues(array $arrayOfValues): bool
3425
{
3426
    foreach($arrayOfValues as $value) {
3427
        if (isset($value) === false) {
3428
            return false;
3429
        }
3430
    }
3431
    return true;
3432
}
3433
3434
/**
3435
 * Check if values in array are set
3436
 * Return true if all set
3437
 * Return false if one of them is not set
3438
 *
3439
 * @param array $arrayOfValues
3440
 * @param integer|string $value
3441
 * @return boolean
3442
 */
3443
function isArrayOfVarsEqualToValue(
3444
    array $arrayOfVars,
3445
    /*PHP8 - integer|string*/$value
3446
) : bool
3447
{
3448
    foreach($arrayOfVars as $variable) {
3449
        if ($variable !== $value) {
3450
            return false;
3451
        }
3452
    }
3453
    return true;
3454
}
3455
3456
/**
3457
 * Checks if at least one variable in array is equal to value
3458
 *
3459
 * @param array $arrayOfValues
3460
 * @param integer|string $value
3461
 * @return boolean
3462
 */
3463
function isOneVarOfArrayEqualToValue(
3464
    array $arrayOfVars,
3465
    /*PHP8 - integer|string*/$value
3466
) : bool
3467
{
3468
    foreach($arrayOfVars as $variable) {
3469
        if ($variable === $value) {
3470
            return true;
3471
        }
3472
    }
3473
    return false;
3474
}
3475
3476
/**
3477
 * Checks is value is null, not set OR empty
3478
 *
3479
 * @param string|int|null $value
3480
 * @return boolean
3481
 */
3482
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3483
{
3484
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3485
        return true;
3486
    }
3487
    return false;
3488
}
3489
3490
/**
3491
 * Checks if value is set and if empty is equal to passed boolean
3492
 *
3493
 * @param string|int $value
3494
 * @param boolean $boolean
3495
 * @return boolean
3496
 */
3497
function isValueSetEmpty($value, $boolean = true) : bool
3498
{
3499
    if (isset($value) === true && empty($value) === $boolean) {
3500
        return true;
3501
    }
3502
    return false;
3503
}
3504
3505
/**
3506
 * Ensure Complexity is translated
3507
 *
3508
 * @return void
3509
 */
3510
function defineComplexity() : void
3511
{
3512
    if (defined('TP_PW_COMPLEXITY') === false) {
3513
        define(
3514
            'TP_PW_COMPLEXITY',
3515
            [
3516
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3517
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3518
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3519
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3520
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3521
            ]
3522
        );
3523
    }
3524
}
3525
3526
/**
3527
 * Uses Sanitizer to perform data sanitization
3528
 *
3529
 * @param array     $data
3530
 * @param array     $filters
3531
 * @param string    $path
3532
 * @return array|string
3533
 */
3534
function dataSanitizer(
3535
    array $data,
3536
    array $filters,
3537
    string $path = __DIR__. '/..' // Path to Teampass root
3538
)
3539
{
3540
    // Load Sanitizer library
3541
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3542
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3543
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3544
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3545
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3546
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3547
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3548
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3549
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3550
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3551
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3552
3553
    // Load AntiXSS
3554
    include_once $path. '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
3555
    include_once $path . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
3556
    include_once $path . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
3557
    $antiXss = new voku\helper\AntiXSS();
3558
3559
    // Sanitize post and get variables
3560
    return $antiXss->xss_clean($sanitizer->sanitize());
3561
}
3562
3563
/**
3564
 * Permits to manage the cache tree for a user
3565
 *
3566
 * @param integer $user_id
3567
 * @param string $data
3568
 * @param array $SETTINGS
3569
 * @param string $field_update
3570
 * @return void
3571
 */
3572
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3573
{
3574
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3575
    //Connect to DB
3576
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3577
    if (defined('DB_PASSWD_CLEAR') === false) {
3578
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3579
    }
3580
    DB::$host = DB_HOST;
3581
    DB::$user = DB_USER;
3582
    DB::$password = DB_PASSWD_CLEAR;
3583
    DB::$dbName = DB_NAME;
3584
    DB::$port = DB_PORT;
3585
    DB::$encoding = DB_ENCODING;
3586
    DB::$ssl = DB_SSL;
3587
    DB::$connect_options = DB_CONNECT_OPTIONS;
3588
3589
    // Exists ?
3590
    $userCacheId = DB::queryfirstrow(
3591
        'SELECT increment_id
3592
        FROM ' . prefixTable('cache_tree') . '
3593
        WHERE user_id = %i',
3594
        $user_id
3595
    );
3596
    
3597
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3598
        DB::insert(
3599
            prefixTable('cache_tree'),
3600
            array(
3601
                'data' => $data,
3602
                'timestamp' => time(),
3603
                'user_id' => $user_id,
3604
                'visible_folders' => '',
3605
            )
3606
        );
3607
    } else {
3608
        if (empty($field_update) === true) {
3609
            DB::update(
3610
                prefixTable('cache_tree'),
3611
                [
3612
                    'timestamp' => time(),
3613
                    'data' => $data,
3614
                ],
3615
                'increment_id = %i',
3616
                $userCacheId['increment_id']
3617
            );
3618
        } else {
3619
            DB::update(
3620
                prefixTable('cache_tree'),
3621
                [
3622
                    $field_update => $data,
3623
                ],
3624
                'increment_id = %i',
3625
                $userCacheId['increment_id']
3626
            );
3627
        }
3628
    }
3629
}
3630
3631
/**
3632
 * Permits to calculate a %
3633
 *
3634
 * @param float $nombre
3635
 * @param float $total
3636
 * @param float $pourcentage
3637
 * @return float
3638
 */
3639
function pourcentage(float $nombre, float $total, float $pourcentage): float
3640
{ 
3641
    $resultat = ($nombre/$total) * $pourcentage;
3642
    return round($resultat);
3643
}
3644
3645
/**
3646
 * Load the folders list from the cache
3647
 *
3648
 * @param string $fieldName
3649
 * @param string $sessionName
3650
 * @param boolean $forceRefresh
3651
 * @return array
3652
 */
3653
function loadFoldersListByCache(
3654
    string $fieldName,
3655
    string $sessionName,
3656
    bool $forceRefresh = false
3657
): array
3658
{
3659
    // Case when refresh is EXPECTED / MANDATORY
3660
    if ($forceRefresh === true) {
3661
        return [
3662
            'state' => false,
3663
            'data' => [],
3664
        ];
3665
    }
3666
3667
    // Get last folder update
3668
    $lastFolderChange = DB::queryfirstrow(
3669
        'SELECT valeur FROM ' . prefixTable('misc') . '
3670
        WHERE type = %s AND intitule = %s',
3671
        'timestamp',
3672
        'last_folder_change'
3673
    );
3674
    if (DB::count() === 0) {
3675
        $lastFolderChange['valeur'] = 0;
3676
    }
3677
3678
    // Case when an update in the tree has been done
3679
    // Refresh is then mandatory
3680
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3681
        return [
3682
            'state' => false,
3683
            'data' => [],
3684
        ];
3685
    }
3686
3687
    // Does this user has the tree structure in session?
3688
    // If yes then use it
3689
    if (count(isset($_SESSION['teampassUser'][$sessionName]) === true ? $_SESSION['teampassUser'][$sessionName] : []) > 0) {
3690
        return [
3691
            'state' => true,
3692
            'data' => json_encode($_SESSION['teampassUser'][$sessionName]),
3693
        ];
3694
    }
3695
3696
    // Does this user has a tree cache
3697
    $userCacheTree = DB::queryfirstrow(
3698
        'SELECT '.$fieldName.'
3699
        FROM ' . prefixTable('cache_tree') . '
3700
        WHERE user_id = %i',
3701
        $_SESSION['user_id']
3702
    );
3703
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3704
        return [
3705
            'state' => true,
3706
            'data' => $userCacheTree[$fieldName],
3707
        ];
3708
    }
3709
3710
    return [
3711
        'state' => false,
3712
        'data' => [],
3713
    ];
3714
}
3715
3716
3717
/**
3718
 * Permits to refresh the categories of folders
3719
 *
3720
 * @param array $folderIds
3721
 * @return void
3722
 */
3723
function handleFoldersCategories(
3724
    array $folderIds
3725
)
3726
{
3727
    //load ClassLoader
3728
    include_once __DIR__. '/../sources/SplClassLoader.php';
3729
    
3730
    //Connect to DB
3731
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3732
    if (defined('DB_PASSWD_CLEAR') === false) {
3733
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
3734
    }
3735
    DB::$host = DB_HOST;
3736
    DB::$user = DB_USER;
3737
    DB::$password = DB_PASSWD_CLEAR;
3738
    DB::$dbName = DB_NAME;
3739
    DB::$port = DB_PORT;
3740
    DB::$encoding = DB_ENCODING;
3741
    DB::$ssl = DB_SSL;
3742
    DB::$connect_options = DB_CONNECT_OPTIONS;
3743
3744
    $arr_data = array();
3745
3746
    // force full list of folders
3747
    if (count($folderIds) === 0) {
3748
        $folderIds = DB::queryFirstColumn(
3749
            'SELECT id
3750
            FROM ' . prefixTable('nested_tree') . '
3751
            WHERE personal_folder=%i',
3752
            0
3753
        );
3754
    }
3755
3756
    // Get complexity
3757
    defineComplexity();
3758
3759
    // update
3760
    foreach ($folderIds as $folder) {
3761
        // Do we have Categories
3762
        // get list of associated Categories
3763
        $arrCatList = array();
3764
        $rows_tmp = DB::query(
3765
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3766
            f.id_category AS category_id
3767
            FROM ' . prefixTable('categories_folders') . ' AS f
3768
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3769
            WHERE id_folder=%i',
3770
            $folder
3771
        );
3772
        if (DB::count() > 0) {
3773
            foreach ($rows_tmp as $row) {
3774
                $arrCatList[$row['id']] = array(
3775
                    'id' => $row['id'],
3776
                    'title' => $row['title'],
3777
                    'level' => $row['level'],
3778
                    'type' => $row['type'],
3779
                    'masked' => $row['masked'],
3780
                    'order' => $row['order'],
3781
                    'encrypted_data' => $row['encrypted_data'],
3782
                    'role_visibility' => $row['role_visibility'],
3783
                    'is_mandatory' => $row['is_mandatory'],
3784
                    'category_id' => $row['category_id'],
3785
                );
3786
            }
3787
        }
3788
        $arr_data['categories'] = $arrCatList;
3789
3790
        // Now get complexity
3791
        $valTemp = '';
3792
        $data = DB::queryFirstRow(
3793
            'SELECT valeur
3794
            FROM ' . prefixTable('misc') . '
3795
            WHERE type = %s AND intitule=%i',
3796
            'complex',
3797
            $folder
3798
        );
3799
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3800
            $valTemp = array(
3801
                'value' => $data['valeur'],
3802
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3803
            );
3804
        }
3805
        $arr_data['complexity'] = $valTemp;
3806
3807
        // Now get Roles
3808
        $valTemp = '';
3809
        $rows_tmp = DB::query(
3810
            'SELECT t.title
3811
            FROM ' . prefixTable('roles_values') . ' as v
3812
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3813
            WHERE v.folder_id = %i
3814
            GROUP BY title',
3815
            $folder
3816
        );
3817
        foreach ($rows_tmp as $record) {
3818
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3819
        }
3820
        $arr_data['visibilityRoles'] = $valTemp;
3821
3822
        // now save in DB
3823
        DB::update(
3824
            prefixTable('nested_tree'),
3825
            array(
3826
                'categories' => json_encode($arr_data),
3827
            ),
3828
            'id = %i',
3829
            $folder
3830
        );
3831
    }
3832
}
3833
3834
/**
3835
 * List all users that have specific roles
3836
 *
3837
 * @param array $roles
3838
 * @return array
3839
 */
3840
function getUsersWithRoles(
3841
    array $roles
3842
): array
3843
{
3844
    $arrUsers = array();
3845
3846
    foreach ($roles as $role) {
3847
        // loop on users and check if user has this role
3848
        $rows = DB::query(
3849
            'SELECT id, fonction_id
3850
            FROM ' . prefixTable('users') . '
3851
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3852
            $_SESSION['user_id']
3853
        );
3854
        foreach ($rows as $user) {
3855
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3856
            if (in_array($role, $userRoles, true) === true) {
3857
                array_push($arrUsers, $user['id']);
3858
            }
3859
        }
3860
    }
3861
3862
    return $arrUsers;
3863
}
3864
3865
// #3476 - check if function str_contains exists (using PHP 8.0.0 or h)
3866
// else define it
3867
if (!function_exists('str_contains')) {
3868
    function str_contains($haystack, $needle) {
3869
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
3870
    }
3871
}
3872
3873
/**
3874
 * Get all users informations
3875
 *
3876
 * @param integer $userId
3877
 * @return array
3878
 */
3879
function getFullUserInfos(
3880
    int $userId
3881
): array
3882
{
3883
    if (empty($userId) === true) {
3884
        return array();
3885
    }
3886
3887
    $val = DB::queryfirstrow(
3888
        'SELECT *
3889
        FROM ' . prefixTable('users') . '
3890
        WHERE id = %i',
3891
        $userId
3892
    );
3893
3894
    return $val;
3895
}
3896
3897
/**
3898
 * Is required an upgrade
3899
 *
3900
 * @return boolean
3901
 */
3902
function upgradeRequired(): bool
3903
{
3904
    // Get settings.php
3905
    include_once __DIR__. '/../includes/config/settings.php';
3906
3907
    // Get timestamp in DB
3908
    $val = DB::queryfirstrow(
3909
        'SELECT valeur
3910
        FROM ' . prefixTable('misc') . '
3911
        WHERE type = %s AND intitule = %s',
3912
        'admin',
3913
        'upgrade_timestamp'
3914
    );
3915
    
3916
    // if not exists then error
3917
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3918
3919
    // if empty or too old then error
3920
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3921
        return true;
3922
    }
3923
3924
    return false;
3925
}
3926
3927
/**
3928
 * Permits to change the user keys on his demand
3929
 *
3930
 * @param integer $userId
3931
 * @param string $passwordClear
3932
 * @param integer $nbItemsToTreat
3933
 * @param string $encryptionKey
3934
 * @param boolean $deleteExistingKeys
3935
 * @param boolean $sendEmailToUser
3936
 * @param boolean $encryptWithUserPassword
3937
 * @param boolean $generate_user_new_password
3938
 * @param string $emailBody
3939
 * @return string
3940
 */
3941
function handleUserKeys(
3942
    int $userId,
3943
    string $passwordClear,
3944
    int $nbItemsToTreat,
3945
    string $encryptionKey = '',
3946
    bool $deleteExistingKeys = false,
3947
    bool $sendEmailToUser = true,
3948
    bool $encryptWithUserPassword = false,
3949
    bool $generate_user_new_password = false,
3950
    string $emailBody = ''
3951
): string
3952
{
3953
3954
    // prepapre background tasks for item keys generation        
3955
    $userTP = DB::queryFirstRow(
3956
        'SELECT pw, public_key, private_key
3957
        FROM ' . prefixTable('users') . '
3958
        WHERE id = %i',
3959
        TP_USER_ID
3960
    );
3961
    if (DB::count() > 0) {
3962
        // Do we need to generate new user password
3963
        if ($generate_user_new_password === true) {
3964
            // Generate a new password
3965
            $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3966
3967
            // Hash the new password
3968
            $pwdlib = new SplClassLoader('PasswordLib', '../includes/libraries');
3969
            $pwdlib->register();
3970
            $pwdlib = new PasswordLib\PasswordLib();
3971
            $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3972
            if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3973
                return prepareExchangedData(
3974
                    __DIR__.'/..',
3975
                    array(
3976
                        'error' => true,
3977
                        'message' => langHdl('pw_hash_not_correct'),
3978
                    ),
3979
                    'encode'
3980
                );
3981
            }
3982
3983
            // Generate new keys
3984
            $userKeys = generateUserKeys($passwordClear);
3985
3986
            // Save in DB
3987
            DB::update(
3988
                prefixTable('users'),
3989
                array(
3990
                    'pw' => $hashedPassword,
3991
                    'public_key' => $userKeys['public_key'],
3992
                    'private_key' => $userKeys['private_key'],
3993
                ),
3994
                'id=%i',
3995
                $userId
3996
            );
3997
        }
3998
3999
        // Manage empty encryption key
4000
        // Let's take the user's password if asked and if no encryption key provided
4001
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
4002
4003
        // Create process
4004
        DB::insert(
4005
            prefixTable('processes'),
4006
            array(
4007
                'created_at' => time(),
4008
                'process_type' => 'create_user_keys',
4009
                'arguments' => json_encode([
4010
                    'new_user_id' => (int) $userId,
4011
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
4012
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
4013
                    'owner_id' => (int) TP_USER_ID,
4014
                    'creator_pwd' => $userTP['pw'],
4015
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
4016
                    'otp_provided_new_value' => 1,
4017
                    'email_body' => empty($emailBody) === true ? '' : langHdl($emailBody),
4018
                ]),
4019
                'updated_at' => '',
4020
                'finished_at' => '',
4021
                'output' => '',
4022
            )
4023
        );
4024
        $processId = DB::insertId();
4025
4026
        // Delete existing keys
4027
        if ($deleteExistingKeys === true) {
4028
            deleteUserObjetsKeys(
4029
                (int) $userId,
4030
            );
4031
        }
4032
4033
        // Create tasks
4034
        DB::insert(
4035
            prefixTable('processes_tasks'),
4036
            array(
4037
                'process_id' => $processId,
4038
                'created_at' => time(),
4039
                'task' => json_encode([
4040
                    'step' => 'step0',
4041
                    'index' => 0,
4042
                    'nb' => $nbItemsToTreat,
4043
                ]),
4044
            )
4045
        );
4046
4047
        DB::insert(
4048
            prefixTable('processes_tasks'),
4049
            array(
4050
                'process_id' => $processId,
4051
                'created_at' => time(),
4052
                'task' => json_encode([
4053
                    'step' => 'step10',
4054
                    'index' => 0,
4055
                    'nb' => $nbItemsToTreat,
4056
                ]),
4057
            )
4058
        );
4059
4060
        DB::insert(
4061
            prefixTable('processes_tasks'),
4062
            array(
4063
                'process_id' => $processId,
4064
                'created_at' => time(),
4065
                'task' => json_encode([
4066
                    'step' => 'step20',
4067
                    'index' => 0,
4068
                    'nb' => $nbItemsToTreat,
4069
                ]),
4070
            )
4071
        );
4072
4073
        DB::insert(
4074
            prefixTable('processes_tasks'),
4075
            array(
4076
                'process_id' => $processId,
4077
                'created_at' => time(),
4078
                'task' => json_encode([
4079
                    'step' => 'step30',
4080
                    'index' => 0,
4081
                    'nb' => $nbItemsToTreat,
4082
                ]),
4083
            )
4084
        );
4085
4086
        DB::insert(
4087
            prefixTable('processes_tasks'),
4088
            array(
4089
                'process_id' => $processId,
4090
                'created_at' => time(),
4091
                'task' => json_encode([
4092
                    'step' => 'step40',
4093
                    'index' => 0,
4094
                    'nb' => $nbItemsToTreat,
4095
                ]),
4096
            )
4097
        );
4098
4099
        DB::insert(
4100
            prefixTable('processes_tasks'),
4101
            array(
4102
                'process_id' => $processId,
4103
                'created_at' => time(),
4104
                'task' => json_encode([
4105
                    'step' => 'step50',
4106
                    'index' => 0,
4107
                    'nb' => $nbItemsToTreat,
4108
                ]),
4109
            )
4110
        );
4111
4112
        DB::insert(
4113
            prefixTable('processes_tasks'),
4114
            array(
4115
                'process_id' => $processId,
4116
                'created_at' => time(),
4117
                'task' => json_encode([
4118
                    'step' => 'step60',
4119
                    'index' => 0,
4120
                    'nb' => $nbItemsToTreat,
4121
                ]),
4122
            )
4123
        );
4124
4125
        // update user's new status
4126
        DB::update(
4127
            prefixTable('users'),
4128
            [
4129
                'is_ready_for_usage' => 0,
4130
                'otp_provided' => 1,
4131
                'ongoing_process_id' => $processId,
4132
                'special' => 'generate-keys',
4133
            ],
4134
            'id=%i',
4135
            $userId
4136
        );
4137
    }
4138
4139
    return prepareExchangedData(
4140
        __DIR__.'/..',
4141
        array(
4142
            'error' => false,
4143
            'message' => '',
4144
        ),
4145
        'encode'
4146
    );
4147
}
4148
4149
/**
4150
 * Permeits to check the consistency of date versus columns definition
4151
 *
4152
 * @param string $table
4153
 * @param array $dataFields
4154
 * @return array
4155
 */
4156
function validateDataFields(
4157
    string $table,
4158
    array $dataFields
4159
): array
4160
{
4161
    // Get table structure
4162
    $result = DB::query(
4163
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
4164
        DB_NAME,
4165
        $table
4166
    );
4167
4168
    foreach ($result as $row) {
4169
        $field = $row['COLUMN_NAME'];
4170
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
4171
4172
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
4173
            if (strlen((string) $dataFields[$field]) > $maxLength) {
4174
                return [
4175
                    'state' => false,
4176
                    'field' => $field,
4177
                    'maxLength' => $maxLength,
4178
                    'currentLength' => strlen((string) $dataFields[$field]),
4179
                ];
4180
            }
4181
        }
4182
    }
4183
    
4184
    return [
4185
        'state' => true,
4186
        'message' => '',
4187
    ];
4188
}
4189
4190
/**
4191
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
4192
 *
4193
 * @param string $string
4194
 * @return string
4195
 */
4196
function filterVarBack(string $string): string
4197
{
4198
    $arr = [
4199
        '&#060;' => '<',
4200
        '&#062;' => '>',
4201
        '&#034;' => '"',
4202
        '&#039;' => "'",
4203
        '&#038;' => '&',
4204
    ];
4205
4206
    foreach ($arr as $key => $value) {
4207
        $string = str_replace($key, $value, $string);
4208
    }
4209
4210
    return $string;
4211
}
4212