Passed
Push — master ( be2df6...388489 )
by Nils
04:23
created

handleUserKeys()   C

Complexity

Conditions 9
Paths 25

Size

Total Lines 203
Code Lines 125

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 125
c 0
b 0
f 0
nc 25
nop 8
dl 0
loc 203
rs 6.4444

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 * @version   3.0.7
15
 * @file      main.functions.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2023 Teampass.net
21
 *
22
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
23
 * ---
24
 *
25
 * @see       https://www.teampass.net
26
 */
27
28
use LdapRecord\Connection;
29
use ForceUTF8\Encoding;
30
31
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
32
    //die('Hacking attempt...');
33
}
34
35
// Load config if $SETTINGS not defined
36
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
37
    include_once __DIR__ . '/../includes/config/tp.config.php';
38
}
39
40
header('Content-type: text/html; charset=utf-8');
41
header('Cache-Control: no-cache, must-revalidate');
42
/**
43
 * Convert language code to string.
44
 *
45
 * @param string $string String to get
46
 */
47
function langHdl(string $string): string
48
{
49
    if (empty($string) === true) {
50
        // Manage error
51
        return 'ERROR in language strings!';
52
    }
53
54
    // Load superglobal
55
    if (file_exists(__DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
56
        include_once __DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
57
    } elseif (file_exists(__DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
58
        include_once __DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
59
    } elseif (file_exists(__DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
60
        include_once __DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
61
    } else {
62
        throw new Exception("Error file '/includes/libraries/protect/SuperGlobal/SuperGlobal.php' not exists", 1);
63
    }
64
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
65
    // Get language string
66
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
67
    if (is_null($session_language) === true) {
68
        /* 
69
            Load the English version to $_SESSION so we don't 
70
            return bad JSON (multiple includes add BOM characters to the json returned 
71
            which makes jquery unhappy on the UI, especially on the log page)
72
            and improve performance by avoiding to include the file for every missing strings.
73
        */
74
        if (isset($_SESSION['teampass']) === false || isset($_SESSION['teampass']['en_lang'][trim($string)]) === false) {
75
            $_SESSION['teampass']['en_lang'] = include_once __DIR__. '/../includes/language/english.php';
76
            $session_language = isset($_SESSION['teampass']['en_lang'][trim($string)]) === false ? '' : $_SESSION['teampass']['en_lang'][trim($string)];
77
        } else {
78
            $session_language = $_SESSION['teampass']['en_lang'][trim($string)];
79
        }
80
    }
81
    // If after all this, we still don't have the string even in english (especially with old logs), return the language code
82
    if (empty($session_language) === true) {
83
        return trim($string);
84
    }
85
    return addslashes($session_language);
86
}
87
88
/**
89
 * genHash().
90
 *
91
 * Generate a hash for user login
92
 *
93
 * @param string $password What password
94
 * @param string $cost     What cost
95
 *
96
 * @return string|void
97
 */
98
function bCrypt(
99
    string $password,
100
    string $cost
101
): ?string
102
{
103
    $salt = sprintf('$2y$%02d$', $cost);
104
    if (function_exists('openssl_random_pseudo_bytes')) {
105
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
106
    } else {
107
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
108
        for ($i = 0; $i < 22; ++$i) {
109
            $salt .= $chars[mt_rand(0, 63)];
110
        }
111
    }
112
113
    return crypt($password, $salt);
114
}
115
116
/**
117
 * Defuse cryption function.
118
 *
119
 * @param string $message   what to de/crypt
120
 * @param string $ascii_key key to use
121
 * @param string $type      operation to perform
122
 * @param array  $SETTINGS  Teampass settings
123
 *
124
 * @return array
125
 */
126
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
127
{
128
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
129
    $err = false;
130
    
131
    $path = __DIR__.'/../includes/libraries/Encryption/Encryption/';
132
133
    include_once $path . 'Exception/CryptoException.php';
134
    include_once $path . 'Exception/BadFormatException.php';
135
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
136
    include_once $path . 'Exception/IOException.php';
137
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
138
    include_once $path . 'Crypto.php';
139
    include_once $path . 'Encoding.php';
140
    include_once $path . 'DerivedKeys.php';
141
    include_once $path . 'Key.php';
142
    include_once $path . 'KeyOrPassword.php';
143
    include_once $path . 'File.php';
144
    include_once $path . 'RuntimeTests.php';
145
    include_once $path . 'KeyProtectedByPassword.php';
146
    include_once $path . 'Core.php';
147
    
148
    // convert KEY
149
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
150
    try {
151
        if ($type === 'encrypt') {
152
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
153
        } elseif ($type === 'decrypt') {
154
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
155
        }
156
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
157
        $err = 'an attack! either the wrong key was loaded, or the ciphertext has changed since it was created either corrupted in the database or intentionally modified by someone trying to carry out an attack.';
158
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
159
        $err = $ex;
160
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
161
        $err = $ex;
162
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
163
        $err = $ex;
164
    } catch (Defuse\Crypto\Exception\IOException $ex) {
165
        $err = $ex;
166
    }
167
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
168
169
    return [
170
        'string' => $text ?? '',
171
        'error' => $err,
172
    ];
173
}
174
175
/**
176
 * Generating a defuse key.
177
 *
178
 * @return string
179
 */
180
function defuse_generate_key()
181
{
182
    // load PhpEncryption library
183
    if (file_exists('../includes/config/tp.config.php') === true) {
184
        $path = '../includes/libraries/Encryption/Encryption/';
185
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
186
        $path = './includes/libraries/Encryption/Encryption/';
187
    } else {
188
        $path = '../includes/libraries/Encryption/Encryption/';
189
    }
190
191
    include_once $path . 'Exception/CryptoException.php';
192
    include_once $path . 'Exception/BadFormatException.php';
193
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
194
    include_once $path . 'Exception/IOException.php';
195
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
196
    include_once $path . 'Crypto.php';
197
    include_once $path . 'Encoding.php';
198
    include_once $path . 'DerivedKeys.php';
199
    include_once $path . 'Key.php';
200
    include_once $path . 'KeyOrPassword.php';
201
    include_once $path . 'File.php';
202
    include_once $path . 'RuntimeTests.php';
203
    include_once $path . 'KeyProtectedByPassword.php';
204
    include_once $path . 'Core.php';
205
206
    $key = \Defuse\Crypto\Key::createNewRandomKey();
207
    $key = $key->saveToAsciiSafeString();
208
    return $key;
209
}
210
211
/**
212
 * Generate a Defuse personal key.
213
 *
214
 * @param string $psk psk used
215
 *
216
 * @return string
217
 */
218
function defuse_generate_personal_key(string $psk): string
219
{
220
    // load PhpEncryption library
221
    if (file_exists('../includes/config/tp.config.php') === true) {
222
        $path = '../includes/libraries/Encryption/Encryption/';
223
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
224
        $path = './includes/libraries/Encryption/Encryption/';
225
    } else {
226
        $path = '../includes/libraries/Encryption/Encryption/';
227
    }
228
229
    include_once $path . 'Exception/CryptoException.php';
230
    include_once $path . 'Exception/BadFormatException.php';
231
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
232
    include_once $path . 'Exception/IOException.php';
233
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
234
    include_once $path . 'Crypto.php';
235
    include_once $path . 'Encoding.php';
236
    include_once $path . 'DerivedKeys.php';
237
    include_once $path . 'Key.php';
238
    include_once $path . 'KeyOrPassword.php';
239
    include_once $path . 'File.php';
240
    include_once $path . 'RuntimeTests.php';
241
    include_once $path . 'KeyProtectedByPassword.php';
242
    include_once $path . 'Core.php';
243
    
244
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
245
    return $protected_key->saveToAsciiSafeString(); // save this in user table
246
}
247
248
/**
249
 * Validate persoanl key with defuse.
250
 *
251
 * @param string $psk                   the user's psk
252
 * @param string $protected_key_encoded special key
253
 *
254
 * @return string
255
 */
256
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
257
{
258
    // load PhpEncryption library
259
    if (file_exists('../includes/config/tp.config.php') === true) {
260
        $path = '../includes/libraries/Encryption/Encryption/';
261
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
262
        $path = './includes/libraries/Encryption/Encryption/';
263
    } else {
264
        $path = '../includes/libraries/Encryption/Encryption/';
265
    }
266
267
    include_once $path . 'Exception/CryptoException.php';
268
    include_once $path . 'Exception/BadFormatException.php';
269
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
270
    include_once $path . 'Exception/IOException.php';
271
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
272
    include_once $path . 'Crypto.php';
273
    include_once $path . 'Encoding.php';
274
    include_once $path . 'DerivedKeys.php';
275
    include_once $path . 'Key.php';
276
    include_once $path . 'KeyOrPassword.php';
277
    include_once $path . 'File.php';
278
    include_once $path . 'RuntimeTests.php';
279
    include_once $path . 'KeyProtectedByPassword.php';
280
    include_once $path . 'Core.php';
281
282
    try {
283
        $protected_key_encoded = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
284
        $user_key = $protected_key_encoded->unlockKey($psk);
285
        $user_key_encoded = $user_key->saveToAsciiSafeString();
286
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
287
        return 'Error - Major issue as the encryption is broken.';
288
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
289
        return 'Error - The saltkey is not the correct one.';
290
    }
291
292
    return $user_key_encoded;
293
    // store it in session once user has entered his psk
294
}
295
296
/**
297
 * Decrypt a defuse string if encrypted.
298
 *
299
 * @param string $value Encrypted string
300
 *
301
 * @return string Decrypted string
302
 */
303
function defuseReturnDecrypted(string $value, $SETTINGS): string
304
{
305
    if (substr($value, 0, 3) === 'def') {
306
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
307
    }
308
309
    return $value;
310
}
311
312
/**
313
 * Trims a string depending on a specific string.
314
 *
315
 * @param string|array $chaine  what to trim
316
 * @param string       $element trim on what
317
 *
318
 * @return string
319
 */
320
function trimElement($chaine, string $element): string
321
{
322
    if (! empty($chaine)) {
323
        if (is_array($chaine) === true) {
324
            $chaine = implode(';', $chaine);
325
        }
326
        $chaine = trim($chaine);
327
        if (substr($chaine, 0, 1) === $element) {
328
            $chaine = substr($chaine, 1);
329
        }
330
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
331
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
332
        }
333
    }
334
335
    return $chaine;
336
}
337
338
/**
339
 * Permits to suppress all "special" characters from string.
340
 *
341
 * @param string $string  what to clean
342
 * @param bool   $special use of special chars?
343
 *
344
 * @return string
345
 */
346
function cleanString(string $string, bool $special = false): string
347
{
348
    // Create temporary table for special characters escape
349
    $tabSpecialChar = [];
350
    for ($i = 0; $i <= 31; ++$i) {
351
        $tabSpecialChar[] = chr($i);
352
    }
353
    array_push($tabSpecialChar, '<br />');
354
    if ((int) $special === 1) {
355
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
356
    }
357
358
    return str_replace($tabSpecialChar, "\n", $string);
359
}
360
361
/**
362
 * Erro manager for DB.
363
 *
364
 * @param array $params output from query
365
 *
366
 * @return void
367
 */
368
function db_error_handler(array $params): void
369
{
370
    echo 'Error: ' . $params['error'] . "<br>\n";
371
    echo 'Query: ' . $params['query'] . "<br>\n";
372
    throw new Exception('Error - Query', 1);
373
}
374
375
/**
376
 * Identify user's rights
377
 *
378
 * @param string|array $groupesVisiblesUser  [description]
379
 * @param string|array $groupesInterditsUser [description]
380
 * @param string       $isAdmin              [description]
381
 * @param string       $idFonctions          [description]
382
 *
383
 * @return bool
384
 */
385
function identifyUserRights(
386
    $groupesVisiblesUser,
387
    $groupesInterditsUser,
388
    $isAdmin,
389
    $idFonctions,
390
    $SETTINGS
391
) {
392
    //load ClassLoader
393
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
394
    // Load superglobal
395
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
396
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
397
    //Connect to DB
398
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
399
    if (defined('DB_PASSWD_CLEAR') === false) {
400
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
401
    }
402
    DB::$host = DB_HOST;
403
    DB::$user = DB_USER;
404
    DB::$password = DB_PASSWD_CLEAR;
405
    DB::$dbName = DB_NAME;
406
    DB::$port = DB_PORT;
407
    DB::$encoding = DB_ENCODING;
408
    DB::$ssl = DB_SSL;
409
    DB::$connect_options = DB_CONNECT_OPTIONS;
410
    //Build tree
411
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
412
    $tree->register();
413
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
414
415
    // Check if user is ADMINISTRATOR    
416
    (int) $isAdmin === 1 ?
417
        identAdmin(
418
            $idFonctions,
419
            $SETTINGS, /** @scrutinizer ignore-type */
420
            $tree
421
        )
422
        :
423
        identUser(
424
            $groupesVisiblesUser,
425
            $groupesInterditsUser,
426
            $idFonctions,
427
            $SETTINGS, /** @scrutinizer ignore-type */
428
            $tree
429
        );
430
431
    // update user's timestamp
432
    DB::update(
433
        prefixTable('users'),
434
        [
435
            'timestamp' => time(),
436
        ],
437
        'id=%i',
438
        $superGlobal->get('user_id', 'SESSION')
439
    );
440
441
    return true;
442
}
443
444
/**
445
 * Identify administrator.
446
 *
447
 * @param string $idFonctions Roles of user
448
 * @param array  $SETTINGS    Teampass settings
449
 * @param array  $tree        Tree of folders
450
 *
451
 * @return bool
452
 */
453
function identAdmin($idFonctions, $SETTINGS, $tree)
454
{
455
    // Load superglobal
456
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
457
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
458
    // Init
459
    $groupesVisibles = [];
460
    $superGlobal->put('personal_folders', [], 'SESSION');
461
    $superGlobal->put('groupes_visibles', [], 'SESSION');
462
    $superGlobal->put('no_access_folders', [], 'SESSION');
463
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
464
    $superGlobal->put('read_only_folders', [], 'SESSION');
465
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
466
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
467
    $superGlobal->put('list_folders_limited', [], 'SESSION');
468
    $superGlobal->put('no_access_folders', [], 'SESSION');
469
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
470
    // Get superglobals
471
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
472
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
473
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
474
    // Get list of Folders
475
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
476
    foreach ($rows as $record) {
477
        array_push($groupesVisibles, $record['id']);
478
    }
479
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
480
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
481
    // Exclude all PF
482
    $where = new WhereClause('and');
483
    // create a WHERE statement of pieces joined by ANDs
484
    $where->add('personal_folder=%i', 1);
485
    if (
486
        isset($SETTINGS['enable_pf_feature']) === true
487
        && (int) $SETTINGS['enable_pf_feature'] === 1
488
    ) {
489
        $where->add('title=%s', $globalsUserId);
490
        $where->negateLast();
491
    }
492
    // Get ID of personal folder
493
    $persfld = DB::queryfirstrow(
494
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
495
        $globalsUserId
496
    );
497
    if (empty($persfld['id']) === false) {
498
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
499
            array_push($globalsVisibleFolders, $persfld['id']);
500
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
501
            // get all descendants
502
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
503
            $tree->rebuild();
504
            $tst = $tree->getDescendants($persfld['id']);
505
            foreach ($tst as $t) {
506
                array_push($globalsVisibleFolders, $t->id);
507
                array_push($globalsPersonalVisibleFolders, $t->id);
508
            }
509
        }
510
    }
511
512
    // get complete list of ROLES
513
    $tmp = explode(';', $idFonctions);
514
    $rows = DB::query(
515
        'SELECT * FROM ' . prefixTable('roles_title') . '
516
        ORDER BY title ASC'
517
    );
518
    foreach ($rows as $record) {
519
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
520
            array_push($tmp, $record['id']);
521
        }
522
    }
523
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
524
    $superGlobal->put('is_admin', 1, 'SESSION');
525
    // Check if admin has created Folders and Roles
526
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
527
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
528
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
529
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
530
531
    return true;
532
}
533
534
/**
535
 * Permits to convert an element to array.
536
 *
537
 * @param string|array $element Any value to be returned as array
538
 *
539
 * @return array
540
 */
541
function convertToArray($element): array
542
{
543
    if (is_string($element) === true) {
544
        if (empty($element) === true) {
545
            return [];
546
        }
547
        return explode(
548
            ';',
549
            trimElement($element, ';')
550
        );
551
    }
552
    return $element;
553
}
554
555
/**
556
 * Defines the rights the user has.
557
 *
558
 * @param string|array $allowedFolders  Allowed folders
559
 * @param string|array $noAccessFolders Not allowed folders
560
 * @param string|array $userRoles       Roles of user
561
 * @param array        $SETTINGS        Teampass settings
562
 * @param object       $tree            Tree of folders
563
 * 
564
 * @return bool
565
 */
566
function identUser(
567
    $allowedFolders,
568
    $noAccessFolders,
569
    $userRoles,
570
    array $SETTINGS,
571
    object $tree
572
) {
573
    // Load superglobal
574
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
575
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
576
    // Init
577
    $superGlobal->put('groupes_visibles', [], 'SESSION');
578
    $superGlobal->put('personal_folders', [], 'SESSION');
579
    $superGlobal->put('no_access_folders', [], 'SESSION');
580
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
581
    $superGlobal->put('read_only_folders', [], 'SESSION');
582
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
583
    $superGlobal->put('is_admin', 0, 'SESSION');
584
    // init
585
    $personalFolders = [];
586
    $readOnlyFolders = [];
587
    $noAccessPersonalFolders = [];
588
    $restrictedFoldersForItems = [];
589
    $foldersLimited = [];
590
    $foldersLimitedFull = [];
591
    $allowedFoldersByRoles = [];
592
    // Get superglobals
593
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
594
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
595
    // Ensure consistency in array format
596
    $noAccessFolders = convertToArray($noAccessFolders);
597
    $userRoles = convertToArray($userRoles);
598
    $allowedFolders = convertToArray($allowedFolders);
599
    
600
    // Get list of folders depending on Roles
601
    $arrays = identUserGetFoldersFromRoles(
602
        $userRoles,
603
        $allowedFoldersByRoles,
604
        $readOnlyFolders,
605
        $allowedFolders
606
    );
607
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
608
    $readOnlyFolders = $arrays['readOnlyFolders'];
609
610
    // Does this user is allowed to see other items
611
    $inc = 0;
612
    $rows = DB::query(
613
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
614
            WHERE restricted_to LIKE %ss AND inactif = %s'.
615
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
616
        $globalsUserId . ';',
617
        '0'
618
    );
619
    foreach ($rows as $record) {
620
        // Exclude restriction on item if folder is fully accessible
621
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
622
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
623
            ++$inc;
624
        //}
625
    }
626
627
    // Check for the users roles if some specific rights exist on items
628
    $rows = DB::query(
629
        'SELECT i.id_tree, r.item_id
630
        FROM ' . prefixTable('items') . ' as i
631
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
632
        WHERE r.role_id IN %li AND i.id_tree <> ""
633
        ORDER BY i.id_tree ASC',
634
        $userRoles
635
    );
636
    $inc = 0;
637
    foreach ($rows as $record) {
638
        //if (isset($record['id_tree'])) {
639
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
640
            array_push($foldersLimitedFull, $record['id_tree']);
641
            ++$inc;
642
        //}
643
    }
644
645
    // Get list of Personal Folders
646
    $arrays = identUserGetPFList(
647
        $globalsPersonalFolders,
648
        $allowedFolders,
649
        $globalsUserId,
650
        $personalFolders,
651
        $noAccessPersonalFolders,
652
        $foldersLimitedFull,
653
        $allowedFoldersByRoles,
654
        array_keys($restrictedFoldersForItems),
655
        $readOnlyFolders,
656
        $noAccessFolders,
657
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
658
        $tree
659
    );
660
    $allowedFolders = $arrays['allowedFolders'];
661
    $personalFolders = $arrays['personalFolders'];
662
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
663
664
    // Return data
665
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
666
    $superGlobal->put('groupes_visibles', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC), 'SESSION');
667
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
668
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
669
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
670
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
671
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
672
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
673
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
674
    $superGlobal->put(
675
        'all_folders_including_no_access',
676
        array_unique(array_merge(
677
            $allowedFolders,
678
            $personalFolders,
679
            $noAccessFolders,
680
            $readOnlyFolders
681
        ), SORT_NUMERIC),
682
        'SESSION'
683
    );
684
    // Folders and Roles numbers
685
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
686
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
687
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
688
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
689
    // check if change proposals on User's items
690
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
691
        $countNewItems = DB::query(
692
            'SELECT COUNT(*)
693
            FROM ' . prefixTable('items_change') . ' AS c
694
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
695
            WHERE i.action = %s AND i.id_user = %i',
696
            'at_creation',
697
            $globalsUserId
698
        );
699
        $superGlobal->put('nb_item_change_proposals', $countNewItems, 'SESSION');
700
    } else {
701
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
702
    }
703
704
    return true;
705
}
706
707
/**
708
 * Get list of folders depending on Roles
709
 * 
710
 * @param array $userRoles
711
 * @param array $allowedFoldersByRoles
712
 * @param array $readOnlyFolders
713
 * @param array $allowedFolders
714
 * 
715
 * @return array
716
 */
717
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
718
{
719
    $rows = DB::query(
720
        'SELECT *
721
        FROM ' . prefixTable('roles_values') . '
722
        WHERE role_id IN %li AND type IN %ls',
723
        $userRoles,
724
        ['W', 'ND', 'NE', 'NDNE', 'R']
725
    );
726
    foreach ($rows as $record) {
727
        if ($record['type'] === 'R') {
728
            array_push($readOnlyFolders, $record['folder_id']);
729
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
730
            array_push($allowedFoldersByRoles, $record['folder_id']);
731
        }
732
    }
733
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
734
    $readOnlyFolders = array_unique($readOnlyFolders);
735
    // Clean arrays
736
    foreach ($allowedFoldersByRoles as $value) {
737
        $key = array_search($value, $readOnlyFolders);
738
        if ($key !== false) {
739
            unset($readOnlyFolders[$key]);
740
        }
741
    }
742
743
    return [
744
        'readOnlyFolders' => $readOnlyFolders,
745
        'allowedFoldersByRoles' => $allowedFoldersByRoles
746
    ];
747
}
748
749
/**
750
 * Get list of Personal Folders
751
 * 
752
 * @param int $globalsPersonalFolders
753
 * @param array $allowedFolders
754
 * @param int $globalsUserId
755
 * @param array $personalFolders
756
 * @param array $noAccessPersonalFolders
757
 * @param array $foldersLimitedFull
758
 * @param array $allowedFoldersByRoles
759
 * @param array $restrictedFoldersForItems
760
 * @param array $readOnlyFolders
761
 * @param array $noAccessFolders
762
 * @param int $enablePfFeature
763
 * @param object $tree
764
 * 
765
 * @return array
766
 */
767
function identUserGetPFList(
768
    $globalsPersonalFolders,
769
    $allowedFolders,
770
    $globalsUserId,
771
    $personalFolders,
772
    $noAccessPersonalFolders,
773
    $foldersLimitedFull,
774
    $allowedFoldersByRoles,
775
    $restrictedFoldersForItems,
776
    $readOnlyFolders,
777
    $noAccessFolders,
778
    $enablePfFeature,
779
    $tree
780
)
781
{
782
    if (
783
        (int) $enablePfFeature === 1
784
        && (int) $globalsPersonalFolders === 1
785
    ) {
786
        $persoFld = DB::queryfirstrow(
787
            'SELECT id
788
            FROM ' . prefixTable('nested_tree') . '
789
            WHERE title = %s AND personal_folder = %i'.
790
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
791
            $globalsUserId,
792
            1
793
        );
794
        if (empty($persoFld['id']) === false) {
795
            array_push($personalFolders, $persoFld['id']);
796
            array_push($allowedFolders, $persoFld['id']);
797
            // get all descendants
798
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
799
            foreach ($ids as $id) {
800
                //array_push($allowedFolders, $id);
801
                array_push($personalFolders, $id);
802
            }
803
        }
804
    }
805
    
806
    // Exclude all other PF
807
    $where = new WhereClause('and');
808
    $where->add('personal_folder=%i', 1);
809
    if (count($personalFolders) > 0) {
810
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
811
    }
812
    if (
813
        (int) $enablePfFeature === 1
814
        && (int) $globalsPersonalFolders === 1
815
    ) {
816
        $where->add('title=%s', $globalsUserId);
817
        $where->negateLast();
818
    }
819
    $persoFlds = DB::query(
820
        'SELECT id
821
        FROM ' . prefixTable('nested_tree') . '
822
        WHERE %l',
823
        $where
824
    );
825
    foreach ($persoFlds as $persoFldId) {
826
        array_push($noAccessPersonalFolders, $persoFldId['id']);
827
    }
828
829
    // All folders visibles
830
    $allowedFolders = array_unique(array_merge(
831
        $allowedFolders,
832
        $foldersLimitedFull,
833
        $allowedFoldersByRoles,
834
        $restrictedFoldersForItems,
835
        $readOnlyFolders
836
    ), SORT_NUMERIC);
837
    // Exclude from allowed folders all the specific user forbidden folders
838
    if (count($noAccessFolders) > 0) {
839
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
840
    }
841
842
    return [
843
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
844
        'personalFolders' => $personalFolders,
845
        'noAccessPersonalFolders' => $noAccessPersonalFolders
846
    ];
847
}
848
849
850
/**
851
 * Update the CACHE table.
852
 *
853
 * @param string $action   What to do
854
 * @param array  $SETTINGS Teampass settings
855
 * @param int    $ident    Ident format
856
 * 
857
 * @return void
858
 */
859
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
860
{
861
    if ($action === 'reload') {
862
        // Rebuild full cache table
863
        cacheTableRefresh($SETTINGS);
864
    } elseif ($action === 'update_value' && is_null($ident) === false) {
865
        // UPDATE an item
866
        cacheTableUpdate($SETTINGS, $ident);
867
    } elseif ($action === 'add_value' && is_null($ident) === false) {
868
        // ADD an item
869
        cacheTableAdd($SETTINGS, $ident);
870
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
871
        // DELETE an item
872
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
873
    }
874
}
875
876
/**
877
 * Cache table - refresh.
878
 *
879
 * @param array $SETTINGS Teampass settings
880
 * 
881
 * @return void
882
 */
883
function cacheTableRefresh(array $SETTINGS): void
884
{
885
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
886
    //Connect to DB
887
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
888
    if (defined('DB_PASSWD_CLEAR') === false) {
889
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
890
    }
891
    DB::$host = DB_HOST;
892
    DB::$user = DB_USER;
893
    DB::$password = DB_PASSWD_CLEAR;
894
    DB::$dbName = DB_NAME;
895
    DB::$port = DB_PORT;
896
    DB::$encoding = DB_ENCODING;
897
    DB::$ssl = DB_SSL;
898
    DB::$connect_options = DB_CONNECT_OPTIONS;
899
    //Load Tree
900
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
901
    $tree->register();
902
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
903
    // truncate table
904
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
905
    // reload date
906
    $rows = DB::query(
907
        'SELECT *
908
        FROM ' . prefixTable('items') . ' as i
909
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
910
        AND l.action = %s
911
        AND i.inactif = %i',
912
        'at_creation',
913
        0
914
    );
915
    foreach ($rows as $record) {
916
        if (empty($record['id_tree']) === false) {
917
            // Get all TAGS
918
            $tags = '';
919
            $itemTags = DB::query(
920
                'SELECT tag
921
                FROM ' . prefixTable('tags') . '
922
                WHERE item_id = %i AND tag != ""',
923
                $record['id']
924
            );
925
            foreach ($itemTags as $itemTag) {
926
                $tags .= $itemTag['tag'] . ' ';
927
            }
928
929
            // Get renewal period
930
            $resNT = DB::queryfirstrow(
931
                'SELECT renewal_period
932
                FROM ' . prefixTable('nested_tree') . '
933
                WHERE id = %i',
934
                $record['id_tree']
935
            );
936
            // form id_tree to full foldername
937
            $folder = [];
938
            $arbo = $tree->getPath($record['id_tree'], true);
939
            foreach ($arbo as $elem) {
940
                // Check if title is the ID of a user
941
                if (is_numeric($elem->title) === true) {
942
                    // Is this a User id?
943
                    $user = DB::queryfirstrow(
944
                        'SELECT id, login
945
                        FROM ' . prefixTable('users') . '
946
                        WHERE id = %i',
947
                        $elem->title
948
                    );
949
                    if (count($user) > 0) {
950
                        $elem->title = $user['login'];
951
                    }
952
                }
953
                // Build path
954
                array_push($folder, stripslashes($elem->title));
955
            }
956
            // store data
957
            DB::insert(
958
                prefixTable('cache'),
959
                [
960
                    'id' => $record['id'],
961
                    'label' => $record['label'],
962
                    'description' => $record['description'] ?? '',
963
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
964
                    'tags' => $tags,
965
                    'id_tree' => $record['id_tree'],
966
                    'perso' => $record['perso'],
967
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
968
                    'login' => $record['login'] ?? '',
969
                    'folder' => implode(' > ', $folder),
970
                    'author' => $record['id_user'],
971
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
972
                    'timestamp' => $record['date'],
973
                ]
974
            );
975
        }
976
    }
977
}
978
979
/**
980
 * Cache table - update existing value.
981
 *
982
 * @param array  $SETTINGS Teampass settings
983
 * @param int    $ident    Ident format
984
 * 
985
 * @return void
986
 */
987
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
988
{
989
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
990
    // Load superglobal
991
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
992
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
993
    //Connect to DB
994
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
995
    if (defined('DB_PASSWD_CLEAR') === false) {
996
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
997
    }
998
    DB::$host = DB_HOST;
999
    DB::$user = DB_USER;
1000
    DB::$password = DB_PASSWD_CLEAR;
1001
    DB::$dbName = DB_NAME;
1002
    DB::$port = DB_PORT;
1003
    DB::$encoding = DB_ENCODING;
1004
    DB::$ssl = DB_SSL;
1005
    DB::$connect_options = DB_CONNECT_OPTIONS;
1006
    //Load Tree
1007
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1008
    $tree->register();
1009
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1010
    // get new value from db
1011
    $data = DB::queryfirstrow(
1012
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1013
        FROM ' . prefixTable('items') . '
1014
        WHERE id=%i',
1015
        $ident
1016
    );
1017
    // Get all TAGS
1018
    $tags = '';
1019
    $itemTags = DB::query(
1020
        'SELECT tag
1021
            FROM ' . prefixTable('tags') . '
1022
            WHERE item_id = %i AND tag != ""',
1023
        $ident
1024
    );
1025
    foreach ($itemTags as $itemTag) {
1026
        $tags .= $itemTag['tag'] . ' ';
1027
    }
1028
    // form id_tree to full foldername
1029
    $folder = [];
1030
    $arbo = $tree->getPath($data['id_tree'], true);
1031
    foreach ($arbo as $elem) {
1032
        // Check if title is the ID of a user
1033
        if (is_numeric($elem->title) === true) {
1034
            // Is this a User id?
1035
            $user = DB::queryfirstrow(
1036
                'SELECT id, login
1037
                FROM ' . prefixTable('users') . '
1038
                WHERE id = %i',
1039
                $elem->title
1040
            );
1041
            if (count($user) > 0) {
1042
                $elem->title = $user['login'];
1043
            }
1044
        }
1045
        // Build path
1046
        array_push($folder, stripslashes($elem->title));
1047
    }
1048
    // finaly update
1049
    DB::update(
1050
        prefixTable('cache'),
1051
        [
1052
            'label' => $data['label'],
1053
            'description' => $data['description'],
1054
            'tags' => $tags,
1055
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1056
            'id_tree' => $data['id_tree'],
1057
            'perso' => $data['perso'],
1058
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1059
            'login' => $data['login'] ?? '',
1060
            'folder' => implode(' » ', $folder),
1061
            'author' => $superGlobal->get('user_id', 'SESSION'),
1062
        ],
1063
        'id = %i',
1064
        $ident
1065
    );
1066
}
1067
1068
/**
1069
 * Cache table - add new value.
1070
 *
1071
 * @param array  $SETTINGS Teampass settings
1072
 * @param int    $ident    Ident format
1073
 * 
1074
 * @return void
1075
 */
1076
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1077
{
1078
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1079
    // Load superglobal
1080
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1081
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1082
    // Get superglobals
1083
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1084
    //Connect to DB
1085
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1086
    if (defined('DB_PASSWD_CLEAR') === false) {
1087
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1088
    }
1089
    DB::$host = DB_HOST;
1090
    DB::$user = DB_USER;
1091
    DB::$password = DB_PASSWD_CLEAR;
1092
    DB::$dbName = DB_NAME;
1093
    DB::$port = DB_PORT;
1094
    DB::$encoding = DB_ENCODING;
1095
    DB::$ssl = DB_SSL;
1096
    DB::$connect_options = DB_CONNECT_OPTIONS;
1097
    //Load Tree
1098
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1099
    $tree->register();
1100
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1101
    // get new value from db
1102
    $data = DB::queryFirstRow(
1103
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1104
        FROM ' . prefixTable('items') . ' as i
1105
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1106
        WHERE i.id = %i
1107
        AND l.action = %s',
1108
        $ident,
1109
        'at_creation'
1110
    );
1111
    // Get all TAGS
1112
    $tags = '';
1113
    $itemTags = DB::query(
1114
        'SELECT tag
1115
            FROM ' . prefixTable('tags') . '
1116
            WHERE item_id = %i AND tag != ""',
1117
        $ident
1118
    );
1119
    foreach ($itemTags as $itemTag) {
1120
        $tags .= $itemTag['tag'] . ' ';
1121
    }
1122
    // form id_tree to full foldername
1123
    $folder = [];
1124
    $arbo = $tree->getPath($data['id_tree'], true);
1125
    foreach ($arbo as $elem) {
1126
        // Check if title is the ID of a user
1127
        if (is_numeric($elem->title) === true) {
1128
            // Is this a User id?
1129
            $user = DB::queryfirstrow(
1130
                'SELECT id, login
1131
                FROM ' . prefixTable('users') . '
1132
                WHERE id = %i',
1133
                $elem->title
1134
            );
1135
            if (count($user) > 0) {
1136
                $elem->title = $user['login'];
1137
            }
1138
        }
1139
        // Build path
1140
        array_push($folder, stripslashes($elem->title));
1141
    }
1142
    // finaly update
1143
    DB::insert(
1144
        prefixTable('cache'),
1145
        [
1146
            'id' => $data['id'],
1147
            'label' => $data['label'],
1148
            'description' => $data['description'],
1149
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1150
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1151
            'id_tree' => $data['id_tree'],
1152
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1153
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1154
            'login' => $data['login'] ?? '',
1155
            'folder' => implode(' » ', $folder),
1156
            'author' => $globalsUserId,
1157
            'timestamp' => $data['date'],
1158
        ]
1159
    );
1160
}
1161
1162
/**
1163
 * Do statistics.
1164
 *
1165
 * @param array $SETTINGS Teampass settings
1166
 *
1167
 * @return array
1168
 */
1169
function getStatisticsData(array $SETTINGS): array
1170
{
1171
    DB::query(
1172
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1173
        0
1174
    );
1175
    $counter_folders = DB::count();
1176
    DB::query(
1177
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1178
        1
1179
    );
1180
    $counter_folders_perso = DB::count();
1181
    DB::query(
1182
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1183
        0
1184
    );
1185
    $counter_items = DB::count();
1186
        DB::query(
1187
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1188
        1
1189
    );
1190
    $counter_items_perso = DB::count();
1191
        DB::query(
1192
        'SELECT id FROM ' . prefixTable('users') . ''
1193
    );
1194
    $counter_users = DB::count();
1195
        DB::query(
1196
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1197
        1
1198
    );
1199
    $admins = DB::count();
1200
    DB::query(
1201
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1202
        1
1203
    );
1204
    $managers = DB::count();
1205
    DB::query(
1206
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1207
        1
1208
    );
1209
    $readOnly = DB::count();
1210
    // list the languages
1211
    $usedLang = [];
1212
    $tp_languages = DB::query(
1213
        'SELECT name FROM ' . prefixTable('languages')
1214
    );
1215
    foreach ($tp_languages as $tp_language) {
1216
        DB::query(
1217
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1218
            $tp_language['name']
1219
        );
1220
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1221
    }
1222
1223
    // get list of ips
1224
    $usedIp = [];
1225
    $tp_ips = DB::query(
1226
        'SELECT user_ip FROM ' . prefixTable('users')
1227
    );
1228
    foreach ($tp_ips as $ip) {
1229
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1230
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1231
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1232
            $usedIp[$ip['user_ip']] = 1;
1233
        }
1234
    }
1235
1236
    return [
1237
        'error' => '',
1238
        'stat_phpversion' => phpversion(),
1239
        'stat_folders' => $counter_folders,
1240
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1241
        'stat_items' => $counter_items,
1242
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1243
        'stat_users' => $counter_users,
1244
        'stat_admins' => $admins,
1245
        'stat_managers' => $managers,
1246
        'stat_ro' => $readOnly,
1247
        'stat_kb' => $SETTINGS['enable_kb'],
1248
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1249
        'stat_fav' => $SETTINGS['enable_favourites'],
1250
        'stat_teampassversion' => TP_VERSION,
1251
        'stat_ldap' => $SETTINGS['ldap_mode'],
1252
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1253
        'stat_duo' => $SETTINGS['duo'],
1254
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1255
        'stat_api' => $SETTINGS['api'],
1256
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1257
        'stat_syslog' => $SETTINGS['syslog_enable'],
1258
        'stat_2fa' => $SETTINGS['google_authentication'],
1259
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1260
        'stat_mysqlversion' => DB::serverVersion(),
1261
        'stat_languages' => $usedLang,
1262
        'stat_country' => $usedIp,
1263
    ];
1264
}
1265
1266
/**
1267
 * Permits to prepare the way to send the email
1268
 * 
1269
 * @param string $subject       email subject
1270
 * @param string $body          email message
1271
 * @param string $email         email
1272
 * @param string $receiverName  Receiver name
1273
 * @param array  $SETTINGS      settings
1274
 *
1275
 * @return void
1276
 */
1277
function prepareSendingEmail(
1278
    $subject,
1279
    $body,
1280
    $email,
1281
    $receiverName,
1282
    $SETTINGS
1283
): void 
1284
{
1285
    DB::insert(
1286
        prefixTable('processes'),
1287
        array(
1288
            'created_at' => time(),
1289
            'process_type' => 'send_email',
1290
            'arguments' => json_encode([
1291
                'subject' => $subject,
1292
                'receivers' => $email,
1293
                'body' => $body,
1294
                'receiver_name' => $receiverName,
1295
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1296
            'updated_at' => '',
1297
            'finished_at' => '',
1298
            'output' => '',
1299
        )
1300
    );
1301
}
1302
1303
/**
1304
 * Permits to send an email.
1305
 *
1306
 * @param string $subject     email subject
1307
 * @param string $textMail    email message
1308
 * @param string $email       email
1309
 * @param array  $SETTINGS    settings
1310
 * @param string $textMailAlt email message alt
1311
 * @param bool   $silent      no errors
1312
 *
1313
 * @return string some json info
1314
 */
1315
function sendEmail(
1316
    $subject,
1317
    $textMail,
1318
    $email,
1319
    $SETTINGS,
1320
    $textMailAlt = null,
1321
    $silent = true,
1322
    $cron = false
1323
) {
1324
    // CAse where email not defined
1325
    if ($email === 'none' || empty($email) === true) {
1326
        return json_encode(
1327
            [
1328
                'error' => true,
1329
                'message' => langHdl('forgot_my_pw_email_sent'),
1330
            ]
1331
        );
1332
    }
1333
1334
    // Build and send email
1335
    $email = buildEmail(
1336
        $subject,
1337
        $textMail,
1338
        $email,
1339
        $SETTINGS,
1340
        $textMailAlt = null,
1341
        $silent = true,
1342
        $cron
1343
    );
1344
1345
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1346
        return json_encode(
1347
            [
1348
                'error' => false,
1349
                'message' => langHdl('forgot_my_pw_email_sent'),
1350
            ]
1351
        );
1352
    }
1353
    // Debug purpose
1354
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1355
        return json_encode(
1356
            [
1357
                'error' => true,
1358
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1359
            ]
1360
        );
1361
    }
1362
    return json_encode(
1363
        [
1364
            'error' => false,
1365
            'message' => langHdl('share_sent_ok'),
1366
        ]
1367
    );
1368
}
1369
1370
1371
function buildEmail(
1372
    $subject,
1373
    $textMail,
1374
    $email,
1375
    $SETTINGS,
1376
    $textMailAlt = null,
1377
    $silent = true,
1378
    $cron = false
1379
)
1380
{
1381
    // Load settings
1382
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1383
    // Load superglobal
1384
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1385
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1386
    // Get user language
1387
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . (null !== $superGlobal->get('user_language', 'SESSION', 'user') ? $superGlobal->get('user_language', 'SESSION', 'user') : 'english') . '.php';
1388
    // Load library
1389
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1390
    // load PHPMailer
1391
    $mail = new SplClassLoader('PHPMailer\PHPMailer', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1392
    $mail->register();
1393
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1394
1395
    // send to user
1396
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1397
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false && $silent === false ? $SETTINGS['email_debug_level'] : 0;
1398
    $mail->Port = (int) $SETTINGS['email_port'];
1399
    //COULD BE USED
1400
    $mail->CharSet = 'utf-8';
1401
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1402
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1403
    $mail->SMTPOptions = [
1404
        'ssl' => [
1405
            'verify_peer' => false,
1406
            'verify_peer_name' => false,
1407
            'allow_self_signed' => true,
1408
        ],
1409
    ];
1410
    $mail->isSmtp();
1411
    // send via SMTP
1412
    $mail->Host = $SETTINGS['email_smtp_server'];
1413
    // SMTP servers
1414
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1415
    // turn on SMTP authentication
1416
    $mail->Username = $SETTINGS['email_auth_username'];
1417
    // SMTP username
1418
    $mail->Password = $SETTINGS['email_auth_pwd'];
1419
    // SMTP password
1420
    $mail->From = $SETTINGS['email_from'];
1421
    $mail->FromName = $SETTINGS['email_from_name'];
1422
    // Prepare for each person
1423
    foreach (array_filter(explode(',', $email)) as $dest) {
1424
        $mail->addAddress($dest);
1425
    }
1426
    
1427
    // Prepare HTML
1428
    $text_html = emailBody($textMail);
1429
    $mail->WordWrap = 80;
1430
    // set word wrap
1431
    $mail->isHtml(true);
1432
    // send as HTML
1433
    $mail->Subject = $subject;
1434
    $mail->Body = $text_html;
1435
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1436
1437
    try {
1438
        // send email
1439
        $mail->send();
1440
    } catch (Exception $e) {
1441
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1442
            return json_encode(
1443
                [
1444
                    'error' => true,
1445
                    'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1446
                ]
1447
            );
1448
        }
1449
        return '';
1450
    }
1451
    $mail->smtpClose();
1452
1453
    return json_encode(
1454
        [
1455
            'error' => true,
1456
            'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1457
        ]
1458
    );
1459
}
1460
1461
/**
1462
 * Returns the email body.
1463
 *
1464
 * @param string $textMail Text for the email
1465
 */
1466
function emailBody(string $textMail): string
1467
{
1468
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1469
    w3.org/TR/html4/loose.dtd"><html>
1470
    <head><title>Email Template</title>
1471
    <style type="text/css">
1472
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1473
    </style></head>
1474
    <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">
1475
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1476
    <tr><td style="border-collapse: collapse;"><br>
1477
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1478
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1479
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1480
        </td></tr></table></td>
1481
    </tr>
1482
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1483
        <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;">
1484
        <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;">
1485
        <br><div style="float:right;">' .
1486
        $textMail .
1487
        '<br><br></td></tr></table>
1488
    </td></tr></table>
1489
    <br></body></html>';
1490
}
1491
1492
/**
1493
 * Generate a Key.
1494
 * 
1495
 * @return string
1496
 */
1497
function generateKey(): string
1498
{
1499
    return substr(md5(rand() . rand()), 0, 15);
1500
}
1501
1502
/**
1503
 * Convert date to timestamp.
1504
 *
1505
 * @param string $date        The date
1506
 * @param string $date_format Date format
1507
 *
1508
 * @return int
1509
 */
1510
function dateToStamp(string $date, string $date_format): int
1511
{
1512
    $date = date_parse_from_format($date_format, $date);
1513
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1514
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
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(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1769
{
1770
    if (empty($who)) {
1771
        $who = getClientIpServer();
1772
    }
1773
1774
    // include librairies & connect to DB
1775
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1776
    if (defined('DB_PASSWD_CLEAR') === false) {
1777
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1778
    }
1779
    DB::$host = DB_HOST;
1780
    DB::$user = DB_USER;
1781
    DB::$password = DB_PASSWD_CLEAR;
1782
    DB::$dbName = DB_NAME;
1783
    DB::$port = DB_PORT;
1784
    DB::$encoding = DB_ENCODING;
1785
    DB::$ssl = DB_SSL;
1786
    DB::$connect_options = DB_CONNECT_OPTIONS;
1787
    DB::insert(
1788
        prefixTable('log_system'),
1789
        [
1790
            'type' => $type,
1791
            'date' => time(),
1792
            'label' => $label,
1793
            'qui' => $who,
1794
            'field_1' => $field_1 === null ? '' : $field_1,
1795
        ]
1796
    );
1797
    // If SYSLOG
1798
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1799
        if ($type === 'user_mngt') {
1800
            send_syslog(
1801
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1802
                $SETTINGS['syslog_host'],
1803
                $SETTINGS['syslog_port'],
1804
                'teampass'
1805
            );
1806
        } else {
1807
            send_syslog(
1808
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1809
                $SETTINGS['syslog_host'],
1810
                $SETTINGS['syslog_port'],
1811
                'teampass'
1812
            );
1813
        }
1814
    }
1815
}
1816
1817
/**
1818
 * Log events.
1819
 *
1820
 * @param array  $SETTINGS        Teampass settings
1821
 * @param int    $item_id         Item id
1822
 * @param string $item_label      Item label
1823
 * @param int    $id_user         User id
1824
 * @param string $action          Code for reason
1825
 * @param string $login           User login
1826
 * @param string $raison          Code for reason
1827
 * @param string $encryption_type Encryption on
1828
 * 
1829
 * @return void
1830
 */
1831
function logItems(
1832
    array $SETTINGS,
1833
    int $item_id,
1834
    string $item_label,
1835
    int $id_user,
1836
    string $action,
1837
    ?string $login = null,
1838
    ?string $raison = null,
1839
    ?string $encryption_type = null
1840
): void {
1841
    // include librairies & connect to DB
1842
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1843
    if (defined('DB_PASSWD_CLEAR') === false) {
1844
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1845
    }
1846
    DB::$host = DB_HOST;
1847
    DB::$user = DB_USER;
1848
    DB::$password = DB_PASSWD_CLEAR;
1849
    DB::$dbName = DB_NAME;
1850
    DB::$port = DB_PORT;
1851
    DB::$encoding = DB_ENCODING;
1852
    DB::$ssl = DB_SSL;
1853
    DB::$connect_options = DB_CONNECT_OPTIONS;
1854
    // Insert log in DB
1855
    DB::insert(
1856
        prefixTable('log_items'),
1857
        [
1858
            'id_item' => $item_id,
1859
            'date' => time(),
1860
            'id_user' => $id_user,
1861
            'action' => $action,
1862
            'raison' => $raison,
1863
            'raison_iv' => '',
1864
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1865
        ]
1866
    );
1867
    // Timestamp the last change
1868
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1869
        DB::update(
1870
            prefixTable('misc'),
1871
            [
1872
                'valeur' => time(),
1873
            ],
1874
            'type = %s AND intitule = %s',
1875
            'timestamp',
1876
            'last_item_change'
1877
        );
1878
    }
1879
1880
    // SYSLOG
1881
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1882
        // Extract reason
1883
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1884
        // Get item info if not known
1885
        if (empty($item_label) === true) {
1886
            $dataItem = DB::queryfirstrow(
1887
                'SELECT id, id_tree, label
1888
                FROM ' . prefixTable('items') . '
1889
                WHERE id = %i',
1890
                $item_id
1891
            );
1892
            $item_label = $dataItem['label'];
1893
        }
1894
1895
        send_syslog(
1896
            'action=' . str_replace('at_', '', $action) .
1897
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1898
                ' itemno=' . $item_id .
1899
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1900
                ' itemname="' . addslashes($item_label) . '"',
1901
            $SETTINGS['syslog_host'],
1902
            $SETTINGS['syslog_port'],
1903
            'teampass'
1904
        );
1905
    }
1906
1907
    // send notification if enabled
1908
    //notifyOnChange($item_id, $action, $SETTINGS);
1909
}
1910
1911
/**
1912
 * If enabled, then notify admin/manager.
1913
 *
1914
 * @param int    $item_id  Item id
1915
 * @param string $action   Action to do
1916
 * @param array  $SETTINGS Teampass settings
1917
 * 
1918
 * @return void
1919
 */
1920
/*
1921
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1922
{
1923
    if (
1924
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1925
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1926
        && $action === 'at_shown'
1927
    ) {
1928
        // Load superglobal
1929
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1930
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1931
        // Get superglobals
1932
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1933
        $globalsName = $superGlobal->get('name', 'SESSION');
1934
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1935
        // Get info about item
1936
        $dataItem = DB::queryfirstrow(
1937
            'SELECT id, id_tree, label
1938
            FROM ' . prefixTable('items') . '
1939
            WHERE id = %i',
1940
            $item_id
1941
        );
1942
        $item_label = $dataItem['label'];
1943
        // send back infos
1944
        DB::insert(
1945
            prefixTable('emails'),
1946
            [
1947
                'timestamp' => time(),
1948
                'subject' => langHdl('email_on_open_notification_subject'),
1949
                'body' => str_replace(
1950
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1951
                    [
1952
                        addslashes($globalsName . ' ' . $globalsLastname),
1953
                        addslashes($item_label),
1954
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1955
                    ],
1956
                    langHdl('email_on_open_notification_mail')
1957
                ),
1958
                'receivers' => $globalsNotifiedEmails,
1959
                'status' => '',
1960
            ]
1961
        );
1962
    }
1963
}
1964
*/
1965
1966
/**
1967
 * Prepare notification email to subscribers.
1968
 *
1969
 * @param int    $item_id  Item id
1970
 * @param string $label    Item label
1971
 * @param array  $changes  List of changes
1972
 * @param array  $SETTINGS Teampass settings
1973
 * 
1974
 * @return void
1975
 */
1976
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1977
{
1978
    // Load superglobal
1979
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1980
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1981
    // Get superglobals
1982
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1983
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1984
    $globalsName = $superGlobal->get('name', 'SESSION');
1985
    // send email to user that what to be notified
1986
    $notification = DB::queryOneColumn(
1987
        'email',
1988
        'SELECT *
1989
        FROM ' . prefixTable('notification') . ' AS n
1990
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1991
        WHERE n.item_id = %i AND n.user_id != %i',
1992
        $item_id,
1993
        $globalsUserId
1994
    );
1995
    if (DB::count() > 0) {
1996
        // Prepare path
1997
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1998
        // Get list of changes
1999
        $htmlChanges = '<ul>';
2000
        foreach ($changes as $change) {
2001
            $htmlChanges .= '<li>' . $change . '</li>';
2002
        }
2003
        $htmlChanges .= '</ul>';
2004
        // send email
2005
        DB::insert(
2006
            prefixTable('emails'),
2007
            [
2008
                'timestamp' => time(),
2009
                'subject' => langHdl('email_subject_item_updated'),
2010
                'body' => str_replace(
2011
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
2012
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
2013
                    langHdl('email_body_item_updated')
2014
                ),
2015
                'receivers' => implode(',', $notification),
2016
                'status' => '',
2017
            ]
2018
        );
2019
    }
2020
}
2021
2022
/**
2023
 * Returns the Item + path.
2024
 *
2025
 * @param int    $id_tree  Node id
2026
 * @param string $label    Label
2027
 * @param array  $SETTINGS TP settings
2028
 * 
2029
 * @return string
2030
 */
2031
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
2032
{
2033
    // Class loader
2034
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
2035
    //Load Tree
2036
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
2037
    $tree->register();
2038
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
2039
    $arbo = $tree->getPath($id_tree, true);
2040
    $path = '';
2041
    foreach ($arbo as $elem) {
2042
        if (empty($path) === true) {
2043
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
2044
        } else {
2045
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
2046
        }
2047
    }
2048
2049
    // Build text to show user
2050
    if (empty($label) === false) {
2051
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
2052
    }
2053
    return empty($path) === true ? '' : $path;
2054
}
2055
2056
/**
2057
 * Get the client ip address.
2058
 *
2059
 * @return string IP address
2060
 */
2061
function getClientIpServer(): string
2062
{
2063
    if (getenv('HTTP_CLIENT_IP')) {
2064
        $ipaddress = getenv('HTTP_CLIENT_IP');
2065
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
2066
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
2067
    } elseif (getenv('HTTP_X_FORWARDED')) {
2068
        $ipaddress = getenv('HTTP_X_FORWARDED');
2069
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
2070
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
2071
    } elseif (getenv('HTTP_FORWARDED')) {
2072
        $ipaddress = getenv('HTTP_FORWARDED');
2073
    } elseif (getenv('REMOTE_ADDR')) {
2074
        $ipaddress = getenv('REMOTE_ADDR');
2075
    } else {
2076
        $ipaddress = 'UNKNOWN';
2077
    }
2078
2079
    return $ipaddress;
2080
}
2081
2082
/**
2083
 * Escape all HTML, JavaScript, and CSS.
2084
 *
2085
 * @param string $input    The input string
2086
 * @param string $encoding Which character encoding are we using?
2087
 * 
2088
 * @return string
2089
 */
2090
function noHTML(string $input, string $encoding = 'UTF-8'): string
2091
{
2092
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2093
}
2094
2095
/**
2096
 * Permits to handle the Teampass config file
2097
 * $action accepts "rebuild" and "update"
2098
 *
2099
 * @param string $action   Action to perform
2100
 * @param array  $SETTINGS Teampass settings
2101
 * @param string $field    Field to refresh
2102
 * @param string $value    Value to set
2103
 *
2104
 * @return string|bool
2105
 */
2106
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2107
{
2108
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2109
    // include librairies & connect to DB
2110
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2111
    if (defined('DB_PASSWD_CLEAR') === false) {
2112
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2113
    }
2114
    DB::$host = DB_HOST;
2115
    DB::$user = DB_USER;
2116
    DB::$password = DB_PASSWD_CLEAR;
2117
    DB::$dbName = DB_NAME;
2118
    DB::$port = DB_PORT;
2119
    DB::$encoding = DB_ENCODING;
2120
    DB::$ssl = DB_SSL;
2121
    DB::$connect_options = DB_CONNECT_OPTIONS;
2122
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2123
        // perform a copy
2124
        if (file_exists($tp_config_file)) {
2125
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2126
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2127
            }
2128
        }
2129
2130
        // regenerate
2131
        $data = [];
2132
        $data[0] = "<?php\n";
2133
        $data[1] = "global \$SETTINGS;\n";
2134
        $data[2] = "\$SETTINGS = array (\n";
2135
        $rows = DB::query(
2136
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2137
            'admin'
2138
        );
2139
        foreach ($rows as $record) {
2140
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
2141
        }
2142
        array_push($data, ");\n");
2143
        $data = array_unique($data);
2144
    // ---
2145
    } elseif ($action === 'update' && empty($field) === false) {
2146
        $data = file($tp_config_file);
2147
        $inc = 0;
2148
        $bFound = false;
2149
        foreach ($data as $line) {
2150
            if (stristr($line, ');')) {
2151
                break;
2152
            }
2153
2154
            if (stristr($line, "'" . $field . "' => '")) {
2155
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS) . "',\n";
2156
                $bFound = true;
2157
                break;
2158
            }
2159
            ++$inc;
2160
        }
2161
        if ($bFound === false) {
2162
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS) . "',\n);\n";
2163
        }
2164
    }
2165
2166
    // update file
2167
    file_put_contents($tp_config_file, implode('', $data ?? []));
2168
    return true;
2169
}
2170
2171
/**
2172
 * Permits to replace &#92; to permit correct display
2173
 *
2174
 * @param string $input Some text
2175
 * 
2176
 * @return string
2177
 */
2178
function handleBackslash(string $input): string
2179
{
2180
    return str_replace('&amp;#92;', '&#92;', $input);
2181
}
2182
2183
/**
2184
 * Permits to load settings
2185
 * 
2186
 * @return void
2187
*/
2188
function loadSettings(): void
2189
{
2190
    global $SETTINGS;
2191
    /* LOAD CPASSMAN SETTINGS */
2192
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2193
        $SETTINGS = [];
2194
        $SETTINGS['duplicate_folder'] = 0;
2195
        //by default, this is set to 0;
2196
        $SETTINGS['duplicate_item'] = 0;
2197
        //by default, this is set to 0;
2198
        $SETTINGS['number_of_used_pw'] = 5;
2199
        //by default, this value is set to 5;
2200
        $settings = [];
2201
        $rows = DB::query(
2202
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2203
            [
2204
                'type' => 'admin',
2205
                'type2' => 'settings',
2206
            ]
2207
        );
2208
        foreach ($rows as $record) {
2209
            if ($record['type'] === 'admin') {
2210
                $SETTINGS[$record['intitule']] = $record['valeur'];
2211
            } else {
2212
                $settings[$record['intitule']] = $record['valeur'];
2213
            }
2214
        }
2215
        $SETTINGS['loaded'] = 1;
2216
        $SETTINGS['default_session_expiration_time'] = 5;
2217
    }
2218
}
2219
2220
/**
2221
 * check if folder has custom fields.
2222
 * Ensure that target one also has same custom fields
2223
 * 
2224
 * @param int $source_id
2225
 * @param int $target_id 
2226
 * 
2227
 * @return bool
2228
*/
2229
function checkCFconsistency(int $source_id, int $target_id): bool
2230
{
2231
    $source_cf = [];
2232
    $rows = DB::QUERY(
2233
        'SELECT id_category
2234
            FROM ' . prefixTable('categories_folders') . '
2235
            WHERE id_folder = %i',
2236
        $source_id
2237
    );
2238
    foreach ($rows as $record) {
2239
        array_push($source_cf, $record['id_category']);
2240
    }
2241
2242
    $target_cf = [];
2243
    $rows = DB::QUERY(
2244
        'SELECT id_category
2245
            FROM ' . prefixTable('categories_folders') . '
2246
            WHERE id_folder = %i',
2247
        $target_id
2248
    );
2249
    foreach ($rows as $record) {
2250
        array_push($target_cf, $record['id_category']);
2251
    }
2252
2253
    $cf_diff = array_diff($source_cf, $target_cf);
2254
    if (count($cf_diff) > 0) {
2255
        return false;
2256
    }
2257
2258
    return true;
2259
}
2260
2261
/**
2262
 * Will encrypte/decrypt a fil eusing Defuse.
2263
 *
2264
 * @param string $type        can be either encrypt or decrypt
2265
 * @param string $source_file path to source file
2266
 * @param string $target_file path to target file
2267
 * @param array  $SETTINGS    Settings
2268
 * @param string $password    A password
2269
 *
2270
 * @return string|bool
2271
 */
2272
function prepareFileWithDefuse(
2273
    string $type,
2274
    string $source_file,
2275
    string $target_file,
2276
    array $SETTINGS,
2277
    string $password = null
2278
) {
2279
    // Load AntiXSS
2280
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
2281
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2282
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
2283
    $antiXss = new voku\helper\AntiXSS();
2284
    // Protect against bad inputs
2285
    if (is_array($source_file) === true || is_array($target_file) === true) {
2286
        return 'error_cannot_be_array';
2287
    }
2288
2289
    // Sanitize
2290
    $source_file = $antiXss->xss_clean($source_file);
2291
    $target_file = $antiXss->xss_clean($target_file);
2292
    if (empty($password) === true || is_null($password) === true) {
2293
        // get KEY to define password
2294
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2295
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2296
    }
2297
2298
    $err = '';
2299
    if ($type === 'decrypt') {
2300
        // Decrypt file
2301
        $err = defuseFileDecrypt(
2302
            $source_file,
2303
            $target_file,
2304
            $SETTINGS, /** @scrutinizer ignore-type */
2305
            $password
2306
        );
2307
    } elseif ($type === 'encrypt') {
2308
        // Encrypt file
2309
        $err = defuseFileEncrypt(
2310
            $source_file,
2311
            $target_file,
2312
            $SETTINGS, /** @scrutinizer ignore-type */
2313
            $password
2314
        );
2315
    }
2316
2317
    // return error
2318
    return $err === true ? '' : $err;
2319
}
2320
2321
/**
2322
 * Encrypt a file with Defuse.
2323
 *
2324
 * @param string $source_file path to source file
2325
 * @param string $target_file path to target file
2326
 * @param array  $SETTINGS    Settings
2327
 * @param string $password    A password
2328
 *
2329
 * @return string|bool
2330
 */
2331
function defuseFileEncrypt(
2332
    string $source_file,
2333
    string $target_file,
2334
    array $SETTINGS,
2335
    string $password = null
2336
) {
2337
    // load PhpEncryption library
2338
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2339
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2340
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2341
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2342
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2343
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2344
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2345
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2346
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2347
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2348
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2349
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2350
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2351
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2352
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2353
    try {
2354
        \Defuse\Crypto\File::encryptFileWithPassword(
2355
            $source_file,
2356
            $target_file,
2357
            $password
2358
        );
2359
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2360
        $err = 'wrong_key';
2361
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2362
        $err = $ex;
2363
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2364
        $err = $ex;
2365
    }
2366
2367
    // return error
2368
    return empty($err) === false ? $err : true;
2369
}
2370
2371
/**
2372
 * Decrypt a file with Defuse.
2373
 *
2374
 * @param string $source_file path to source file
2375
 * @param string $target_file path to target file
2376
 * @param array  $SETTINGS    Settings
2377
 * @param string $password    A password
2378
 *
2379
 * @return string|bool
2380
 */
2381
function defuseFileDecrypt(
2382
    string $source_file,
2383
    string $target_file,
2384
    array $SETTINGS,
2385
    string $password = null
2386
) {
2387
    // load PhpEncryption library
2388
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2389
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2390
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2391
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2392
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2393
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2394
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2395
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2396
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2397
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2398
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2399
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2400
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2401
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2402
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2403
    try {
2404
        \Defuse\Crypto\File::decryptFileWithPassword(
2405
            $source_file,
2406
            $target_file,
2407
            $password
2408
        );
2409
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2410
        $err = 'wrong_key';
2411
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2412
        $err = $ex;
2413
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2414
        $err = $ex;
2415
    }
2416
2417
    // return error
2418
    return empty($err) === false ? $err : true;
2419
}
2420
2421
/*
2422
* NOT TO BE USED
2423
*/
2424
/**
2425
 * Undocumented function.
2426
 *
2427
 * @param string $text Text to debug
2428
 */
2429
function debugTeampass(string $text): void
2430
{
2431
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2432
    if ($debugFile !== false) {
2433
        fputs($debugFile, $text);
2434
        fclose($debugFile);
2435
    }
2436
}
2437
2438
/**
2439
 * DELETE the file with expected command depending on server type.
2440
 *
2441
 * @param string $file     Path to file
2442
 * @param array  $SETTINGS Teampass settings
2443
 *
2444
 * @return void
2445
 */
2446
function fileDelete(string $file, array $SETTINGS): void
2447
{
2448
    // Load AntiXSS
2449
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
2450
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2451
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
2452
    $antiXss = new voku\helper\AntiXSS();
2453
    $file = $antiXss->xss_clean($file);
2454
    if (is_file($file)) {
2455
        unlink($file);
2456
    }
2457
}
2458
2459
/**
2460
 * Permits to extract the file extension.
2461
 *
2462
 * @param string $file File name
2463
 *
2464
 * @return string
2465
 */
2466
function getFileExtension(string $file): string
2467
{
2468
    if (strpos($file, '.') === false) {
2469
        return $file;
2470
    }
2471
2472
    return substr($file, strrpos($file, '.') + 1);
2473
}
2474
2475
/**
2476
 * Chmods files and folders with different permissions.
2477
 *
2478
 * This is an all-PHP alternative to using: \n
2479
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2480
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2481
 *
2482
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2483
  *
2484
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2485
 * @param int    $filePerm The permissions any found files should get.
2486
 * @param int    $dirPerm  The permissions any found folder should get.
2487
 *
2488
 * @return bool Returns TRUE if the path if found and FALSE if not.
2489
 *
2490
 * @warning The permission levels has to be entered in octal format, which
2491
 * normally means adding a zero ("0") in front of the permission level. \n
2492
 * More info at: http://php.net/chmod.
2493
*/
2494
2495
function recursiveChmod(
2496
    string $path,
2497
    int $filePerm = 0644,
2498
    int  $dirPerm = 0755
2499
) {
2500
    // Check if the path exists
2501
    if (! file_exists($path)) {
2502
        return false;
2503
    }
2504
2505
    // See whether this is a file
2506
    if (is_file($path)) {
2507
        // Chmod the file with our given filepermissions
2508
        chmod($path, $filePerm);
2509
    // If this is a directory...
2510
    } elseif (is_dir($path)) {
2511
        // Then get an array of the contents
2512
        $foldersAndFiles = scandir($path);
2513
        // Remove "." and ".." from the list
2514
        $entries = array_slice($foldersAndFiles, 2);
2515
        // Parse every result...
2516
        foreach ($entries as $entry) {
2517
            // And call this function again recursively, with the same permissions
2518
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2519
        }
2520
2521
        // When we are done with the contents of the directory, we chmod the directory itself
2522
        chmod($path, $dirPerm);
2523
    }
2524
2525
    // Everything seemed to work out well, return true
2526
    return true;
2527
}
2528
2529
/**
2530
 * Check if user can access to this item.
2531
 *
2532
 * @param int   $item_id ID of item
2533
 * @param array $SETTINGS
2534
 *
2535
 * @return bool|string
2536
 */
2537
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2538
{
2539
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2540
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2541
    // Prepare superGlobal variables
2542
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2543
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2544
    // Load item data
2545
    $data = DB::queryFirstRow(
2546
        'SELECT id_tree
2547
        FROM ' . prefixTable('items') . '
2548
        WHERE id = %i',
2549
        $item_id
2550
    );
2551
    // Check if user can access this folder
2552
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2553
        // Now check if this folder is restricted to user
2554
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2555
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2556
        ) {
2557
            return 'ERR_FOLDER_NOT_ALLOWED';
2558
        }
2559
    }
2560
2561
    return true;
2562
}
2563
2564
/**
2565
 * Creates a unique key.
2566
 *
2567
 * @param int $lenght Key lenght
2568
 *
2569
 * @return string
2570
 */
2571
function uniqidReal(int $lenght = 13): string
2572
{
2573
    if (function_exists('random_bytes')) {
2574
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2575
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2576
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2577
    } else {
2578
        throw new Exception('no cryptographically secure random function available');
2579
    }
2580
2581
    return substr(bin2hex($bytes), 0, $lenght);
2582
}
2583
2584
/**
2585
 * Obfuscate an email.
2586
 *
2587
 * @param string $email Email address
2588
 *
2589
 * @return string
2590
 */
2591
function obfuscateEmail(string $email): string
2592
{
2593
    $email = explode("@", $email);
2594
    $name = $email[0];
2595
    if (strlen($name) > 3) {
2596
        $name = substr($name, 0, 2);
2597
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2598
            $name .= "*";
2599
        }
2600
        $name .= substr($email[0], -1, 1);
2601
    }
2602
    $host = explode(".", $email[1])[0];
2603
    if (strlen($host) > 3) {
2604
        $host = substr($host, 0, 1);
2605
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2606
            $host .= "*";
2607
        }
2608
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2609
    }
2610
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2611
    return $email;
2612
}
2613
2614
/**
2615
 * Perform a Query.
2616
 *
2617
 * @param array  $SETTINGS Teamapss settings
2618
 * @param string $fields   Fields to use
2619
 * @param string $table    Table to use
2620
 *
2621
 * @return array
2622
 */
2623
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2624
{
2625
    // include librairies & connect to DB
2626
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2627
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2628
    if (defined('DB_PASSWD_CLEAR') === false) {
2629
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2630
    }
2631
    DB::$host = DB_HOST;
2632
    DB::$user = DB_USER;
2633
    DB::$password = DB_PASSWD_CLEAR;
2634
    DB::$dbName = DB_NAME;
2635
    DB::$port = DB_PORT;
2636
    DB::$encoding = DB_ENCODING;
2637
    DB::$ssl = DB_SSL;
2638
    DB::$connect_options = DB_CONNECT_OPTIONS;
2639
    // Insert log in DB
2640
    return DB::query(
2641
        'SELECT ' . $fields . '
2642
        FROM ' . prefixTable($table)
2643
    );
2644
}
2645
2646
/**
2647
 * Undocumented function.
2648
 *
2649
 * @param int $bytes Size of file
2650
 *
2651
 * @return string
2652
 */
2653
function formatSizeUnits(int $bytes): string
2654
{
2655
    if ($bytes >= 1073741824) {
2656
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2657
    } elseif ($bytes >= 1048576) {
2658
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2659
    } elseif ($bytes >= 1024) {
2660
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2661
    } elseif ($bytes > 1) {
2662
        $bytes .= ' bytes';
2663
    } elseif ($bytes === 1) {
2664
        $bytes .= ' byte';
2665
    } else {
2666
        $bytes = '0 bytes';
2667
    }
2668
2669
    return $bytes;
2670
}
2671
2672
/**
2673
 * Generate user pair of keys.
2674
 *
2675
 * @param string $userPwd User password
2676
 *
2677
 * @return array
2678
 */
2679
function generateUserKeys(string $userPwd): array
2680
{
2681
    // include library
2682
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2683
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2684
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2685
    // Load classes
2686
    $rsa = new Crypt_RSA();
2687
    $cipher = new Crypt_AES();
2688
    // Create the private and public key
2689
    $res = $rsa->createKey(4096);
2690
    // Encrypt the privatekey
2691
    $cipher->setPassword($userPwd);
2692
    $privatekey = $cipher->encrypt($res['privatekey']);
2693
    return [
2694
        'private_key' => base64_encode($privatekey),
2695
        'public_key' => base64_encode($res['publickey']),
2696
        'private_key_clear' => base64_encode($res['privatekey']),
2697
    ];
2698
}
2699
2700
/**
2701
 * Permits to decrypt the user's privatekey.
2702
 *
2703
 * @param string $userPwd        User password
2704
 * @param string $userPrivateKey User private key
2705
 *
2706
 * @return string
2707
 */
2708
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2709
{
2710
    if (empty($userPwd) === false) {
2711
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2712
        // Load classes
2713
        $cipher = new Crypt_AES();
2714
        // Encrypt the privatekey
2715
        $cipher->setPassword($userPwd);
2716
        try {
2717
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2718
        } catch (Exception $e) {
2719
            return $e;
2720
        }
2721
    }
2722
    return '';
2723
}
2724
2725
/**
2726
 * Permits to encrypt the user's privatekey.
2727
 *
2728
 * @param string $userPwd        User password
2729
 * @param string $userPrivateKey User private key
2730
 *
2731
 * @return string
2732
 */
2733
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2734
{
2735
    if (empty($userPwd) === false) {
2736
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2737
        // Load classes
2738
        $cipher = new Crypt_AES();
2739
        // Encrypt the privatekey
2740
        $cipher->setPassword($userPwd);        
2741
        try {
2742
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2743
        } catch (Exception $e) {
2744
            return $e;
2745
        }
2746
    }
2747
    return '';
2748
}
2749
2750
/**
2751
 * Encrypts a string using AES.
2752
 *
2753
 * @param string $data String to encrypt
2754
 * @param string $key
2755
 *
2756
 * @return array
2757
 */
2758
function doDataEncryption(string $data, string $key = NULL): array
2759
{
2760
    // Includes
2761
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2762
    // Load classes
2763
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2764
    // Generate an object key
2765
    $objectKey = is_null($key) === true ? uniqidReal(32) : $key;
2766
    // Set it as password
2767
    $cipher->setPassword($objectKey);
2768
    return [
2769
        'encrypted' => base64_encode($cipher->encrypt($data)),
2770
        'objectKey' => base64_encode($objectKey),
2771
    ];
2772
}
2773
2774
/**
2775
 * Decrypts a string using AES.
2776
 *
2777
 * @param string $data Encrypted data
2778
 * @param string $key  Key to uncrypt
2779
 *
2780
 * @return string
2781
 */
2782
function doDataDecryption(string $data, string $key): string
2783
{
2784
    // Includes
2785
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2786
    // Load classes
2787
    $cipher = new Crypt_AES();
2788
    // Set the object key
2789
    $cipher->setPassword(base64_decode($key));
2790
    return base64_encode($cipher->decrypt(base64_decode($data)));
2791
}
2792
2793
/**
2794
 * Encrypts using RSA a string using a public key.
2795
 *
2796
 * @param string $key       Key to be encrypted
2797
 * @param string $publicKey User public key
2798
 *
2799
 * @return string
2800
 */
2801
function encryptUserObjectKey(string $key, string $publicKey): string
2802
{
2803
    // Includes
2804
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2805
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2806
    // Load classes
2807
    $rsa = new Crypt_RSA();
2808
    $rsa->loadKey(base64_decode($publicKey));
2809
    // Encrypt
2810
    return base64_encode($rsa->encrypt(base64_decode($key)));
2811
}
2812
2813
/**
2814
 * Decrypts using RSA an encrypted string using a private key.
2815
 *
2816
 * @param string $key        Encrypted key
2817
 * @param string $privateKey User private key
2818
 *
2819
 * @return string
2820
 */
2821
function decryptUserObjectKey(string $key, string $privateKey): string
2822
{
2823
    // Includes
2824
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2825
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2826
    // Load classes
2827
    $rsa = new Crypt_RSA();
2828
    $rsa->loadKey(base64_decode($privateKey));
2829
    // Decrypt
2830
    try {
2831
        $tmpValue = $rsa->decrypt(base64_decode($key));
2832
        if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2833
            $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2834
        } else {
2835
            $ret = '';
2836
        }
2837
    } catch (Exception $e) {
2838
        return $e;
2839
    }
2840
2841
    return $ret;
2842
}
2843
2844
/**
2845
 * Encrypts a file.
2846
 *
2847
 * @param string $fileInName File name
2848
 * @param string $fileInPath Path to file
2849
 *
2850
 * @return array
2851
 */
2852
function encryptFile(string $fileInName, string $fileInPath): array
2853
{
2854
    if (defined('FILE_BUFFER_SIZE') === false) {
2855
        define('FILE_BUFFER_SIZE', 128 * 1024);
2856
    }
2857
2858
    // Includes
2859
    include_once __DIR__.'/../includes/config/include.php';
2860
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2861
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2862
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2863
    // Load classes
2864
    $cipher = new Crypt_AES();
2865
    // Generate an object key
2866
    $objectKey = uniqidReal(32);
2867
    // Set it as password
2868
    $cipher->setPassword($objectKey);
2869
    // Prevent against out of memory
2870
    $cipher->enableContinuousBuffer();
2871
    //$cipher->disablePadding();
2872
2873
    // Encrypt the file content
2874
    $plaintext = file_get_contents(
2875
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2876
    );
2877
    $ciphertext = $cipher->encrypt($plaintext);
2878
    // Save new file
2879
    $hash = md5($plaintext);
2880
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2881
    file_put_contents($fileOut, $ciphertext);
2882
    unlink($fileInPath . '/' . $fileInName);
2883
    return [
2884
        'fileHash' => base64_encode($hash),
2885
        'objectKey' => base64_encode($objectKey),
2886
    ];
2887
}
2888
2889
/**
2890
 * Decrypt a file.
2891
 *
2892
 * @param string $fileName File name
2893
 * @param string $filePath Path to file
2894
 * @param string $key      Key to use
2895
 *
2896
 * @return string
2897
 */
2898
function decryptFile(string $fileName, string $filePath, string $key): string
2899
{
2900
    if (! defined('FILE_BUFFER_SIZE')) {
2901
        define('FILE_BUFFER_SIZE', 128 * 1024);
2902
    }
2903
2904
    // Includes
2905
    include_once __DIR__.'/../includes/config/include.php';
2906
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2907
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2908
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2909
    // Get file name
2910
    $fileName = base64_decode($fileName);
2911
    // Load classes
2912
    $cipher = new Crypt_AES();
2913
    // Set the object key
2914
    $cipher->setPassword(base64_decode($key));
2915
    // Prevent against out of memory
2916
    $cipher->enableContinuousBuffer();
2917
    $cipher->disablePadding();
2918
    // Get file content
2919
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2920
    // Decrypt file content and return
2921
    return base64_encode($cipher->decrypt($ciphertext));
2922
}
2923
2924
/**
2925
 * Generate a simple password
2926
 *
2927
 * @param int $length Length of string
2928
 * @param bool $symbolsincluded Allow symbols
2929
 *
2930
 * @return string
2931
 */
2932
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2933
{
2934
    // Generate new user password
2935
    $small_letters = range('a', 'z');
2936
    $big_letters = range('A', 'Z');
2937
    $digits = range(0, 9);
2938
    $symbols = $symbolsincluded === true ?
2939
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2940
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2941
    $count = count($res);
2942
    // first variant
2943
2944
    $random_string = '';
2945
    for ($i = 0; $i < $length; ++$i) {
2946
        $random_string .= $res[random_int(0, $count - 1)];
2947
    }
2948
2949
    return $random_string;
2950
}
2951
2952
/**
2953
 * Permit to store the sharekey of an object for users.
2954
 *
2955
 * @param string $object_name             Type for table selection
2956
 * @param int    $post_folder_is_personal Personal
2957
 * @param int    $post_folder_id          Folder
2958
 * @param int    $post_object_id          Object
2959
 * @param string $objectKey               Object key
2960
 * @param array  $SETTINGS                Teampass settings
2961
 *
2962
 * @return void
2963
 */
2964
function storeUsersShareKey(
2965
    string $object_name,
2966
    int $post_folder_is_personal,
2967
    int $post_folder_id,
2968
    int $post_object_id,
2969
    string $objectKey,
2970
    array $SETTINGS
2971
): void {
2972
    // include librairies & connect to DB
2973
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2974
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2975
    if (defined('DB_PASSWD_CLEAR') === false) {
2976
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2977
    }
2978
    DB::$host = DB_HOST;
2979
    DB::$user = DB_USER;
2980
    DB::$password = DB_PASSWD_CLEAR;
2981
    DB::$dbName = DB_NAME;
2982
    DB::$port = DB_PORT;
2983
    DB::$encoding = DB_ENCODING;
2984
    DB::$ssl = DB_SSL;
2985
    DB::$connect_options = DB_CONNECT_OPTIONS;
2986
    // Delete existing entries for this object
2987
    DB::delete(
2988
        $object_name,
2989
        'object_id = %i',
2990
        $post_object_id
2991
    );
2992
    // Superglobals
2993
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2994
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2995
    // Prepare superGlobal variables
2996
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2997
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2998
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2999
    if (
3000
        (int) $post_folder_is_personal === 1
3001
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
3002
    ) {
3003
        // If this is a personal object
3004
        // Only create the sharekey for user
3005
        DB::insert(
3006
            $object_name,
3007
            [
3008
                'object_id' => (int) $post_object_id,
3009
                'user_id' => (int) $sessionUserId,
3010
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
3011
            ]
3012
        );
3013
    } else {
3014
        // This is a public object
3015
        // Create sharekey for each user
3016
        $users = DB::query(
3017
            'SELECT id, public_key
3018
            FROM ' . prefixTable('users') . '
3019
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
3020
            AND public_key != ""'
3021
        );
3022
        foreach ($users as $user) {
3023
            // Insert in DB the new object key for this item by user
3024
            DB::insert(
3025
                $object_name,
3026
                [
3027
                    'object_id' => $post_object_id,
3028
                    'user_id' => (int) $user['id'],
3029
                    'share_key' => encryptUserObjectKey(
3030
                        $objectKey,
3031
                        $user['public_key']
3032
                    ),
3033
                ]
3034
            );
3035
        }
3036
    }
3037
}
3038
3039
/**
3040
 * Is this string base64 encoded?
3041
 *
3042
 * @param string $str Encoded string?
3043
 *
3044
 * @return bool
3045
 */
3046
function isBase64(string $str): bool
3047
{
3048
    $str = (string) trim($str);
3049
    if (! isset($str[0])) {
3050
        return false;
3051
    }
3052
3053
    $base64String = (string) base64_decode($str, true);
3054
    if ($base64String && base64_encode($base64String) === $str) {
3055
        return true;
3056
    }
3057
3058
    return false;
3059
}
3060
3061
/**
3062
 * Undocumented function
3063
 *
3064
 * @param string $field Parameter
3065
 *
3066
 * @return array|bool|resource|string
3067
 */
3068
function filterString(string $field)
3069
{
3070
    // Sanitize string
3071
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
3072
    if (empty($field) === false) {
3073
        // Load AntiXSS
3074
        include_once __DIR__.'/../includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
3075
        $antiXss = new voku\helper\AntiXSS();
3076
        // Return
3077
        return $antiXss->xss_clean($field);
3078
    }
3079
3080
    return false;
3081
}
3082
3083
/**
3084
 * CHeck if provided credentials are allowed on server
3085
 *
3086
 * @param string $login    User Login
3087
 * @param string $password User Pwd
3088
 * @param array  $SETTINGS Teampass settings
3089
 *
3090
 * @return bool
3091
 */
3092
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3093
{
3094
    // Build ldap configuration array
3095
    $config = [
3096
        // Mandatory Configuration Options
3097
        'hosts' => [$SETTINGS['ldap_hosts']],
3098
        'base_dn' => $SETTINGS['ldap_bdn'],
3099
        'username' => $SETTINGS['ldap_username'],
3100
        'password' => $SETTINGS['ldap_password'],
3101
3102
        // Optional Configuration Options
3103
        'port' => $SETTINGS['ldap_port'],
3104
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3105
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3106
        'version' => 3,
3107
        'timeout' => 5,
3108
        'follow_referrals' => false,
3109
3110
        // Custom LDAP Options
3111
        'options' => [
3112
            // See: http://php.net/ldap_set_option
3113
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
3114
        ],
3115
    ];
3116
    // Load expected libraries
3117
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3118
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3119
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3120
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3121
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3122
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3123
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3124
    $ad->register();
3125
    $connection = new Connection($config);
3126
    // Connect to LDAP
3127
    try {
3128
        $connection->connect();
3129
    } catch (\LdapRecord\Auth\BindException $e) {
3130
        $error = $e->getDetailedError();
3131
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3132
        return false;
3133
    }
3134
3135
    // Authenticate user
3136
    try {
3137
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3138
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3139
        } else {
3140
            $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);
3141
        }
3142
    } catch (\LdapRecord\Auth\BindException $e) {
3143
        $error = $e->getDetailedError();
3144
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3145
        return false;
3146
    }
3147
3148
    return true;
3149
}
3150
3151
/**
3152
 * Removes from DB all sharekeys of this user
3153
 *
3154
 * @param int $userId User's id
3155
 * @param array   $SETTINGS Teampass settings
3156
 *
3157
 * @return bool
3158
 */
3159
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
3160
{
3161
    // include librairies & connect to DB
3162
    include_once __DIR__. '/../includes/config/settings.php';
3163
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3164
    if (defined('DB_PASSWD_CLEAR') === false) {
3165
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3166
    }
3167
    DB::$host = DB_HOST;
3168
    DB::$user = DB_USER;
3169
    DB::$password = DB_PASSWD_CLEAR;
3170
    DB::$dbName = DB_NAME;
3171
    DB::$port = DB_PORT;
3172
    DB::$encoding = DB_ENCODING;
3173
    DB::$ssl = DB_SSL;
3174
    DB::$connect_options = DB_CONNECT_OPTIONS;
3175
    // Remove all item sharekeys items
3176
    DB::delete(
3177
        prefixTable('sharekeys_items'),
3178
        'user_id = %i',
3179
        $userId
3180
    );
3181
    // Remove all item sharekeys files
3182
    DB::delete(
3183
        prefixTable('sharekeys_files'),
3184
        'user_id = %i',
3185
        $userId
3186
    );
3187
    // Remove all item sharekeys fields
3188
    DB::delete(
3189
        prefixTable('sharekeys_fields'),
3190
        'user_id = %i',
3191
        $userId
3192
    );
3193
    // Remove all item sharekeys logs
3194
    DB::delete(
3195
        prefixTable('sharekeys_logs'),
3196
        'user_id = %i',
3197
        $userId
3198
    );
3199
    // Remove all item sharekeys suggestions
3200
    DB::delete(
3201
        prefixTable('sharekeys_suggestions'),
3202
        'user_id = %i',
3203
        $userId
3204
    );
3205
    return false;
3206
}
3207
3208
/**
3209
 * Manage list of timezones   $SETTINGS Teampass settings
3210
 *
3211
 * @return array
3212
 */
3213
function timezone_list()
3214
{
3215
    static $timezones = null;
3216
    if ($timezones === null) {
3217
        $timezones = [];
3218
        $offsets = [];
3219
        $now = new DateTime('now', new DateTimeZone('UTC'));
3220
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3221
            $now->setTimezone(new DateTimeZone($timezone));
3222
            $offsets[] = $offset = $now->getOffset();
3223
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3224
        }
3225
3226
        array_multisort($offsets, $timezones);
3227
    }
3228
3229
    return $timezones;
3230
}
3231
3232
/**
3233
 * Provide timezone offset
3234
 *
3235
 * @param int $offset Timezone offset
3236
 *
3237
 * @return string
3238
 */
3239
function format_GMT_offset($offset): string
3240
{
3241
    $hours = intval($offset / 3600);
3242
    $minutes = abs(intval($offset % 3600 / 60));
3243
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3244
}
3245
3246
/**
3247
 * Provides timezone name
3248
 *
3249
 * @param string $name Timezone name
3250
 *
3251
 * @return string
3252
 */
3253
function format_timezone_name($name): string
3254
{
3255
    $name = str_replace('/', ', ', $name);
3256
    $name = str_replace('_', ' ', $name);
3257
3258
    return str_replace('St ', 'St. ', $name);
3259
}
3260
3261
/**
3262
 * Provides info if user should use MFA based on roles
3263
 *
3264
 * @param string $userRolesIds  User roles ids
3265
 * @param string $mfaRoles      Roles for which MFA is requested
3266
 *
3267
 * @return bool
3268
 */
3269
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3270
{
3271
    if (empty($mfaRoles) === true) {
3272
        return true;
3273
    }
3274
3275
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3276
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3277
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3278
        return true;
3279
    }
3280
3281
    return false;
3282
}
3283
3284
/**
3285
 * Permits to clean a string for export purpose
3286
 *
3287
 * @param string $text
3288
 * @param bool $emptyCheckOnly
3289
 * 
3290
 * @return string
3291
 */
3292
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3293
{
3294
    if (is_null($text) === true || empty($text) === true) {
3295
        return '';
3296
    }
3297
    // only expected to check if $text was empty
3298
    elseif ($emptyCheckOnly === true) {
3299
        return $text;
3300
    }
3301
3302
    return strip_tags(
3303
        cleanString(
3304
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3305
            true)
3306
        );
3307
}
3308
3309
/**
3310
 * Permits to check if user ID is valid
3311
 *
3312
 * @param integer $post_user_id
3313
 * @return bool
3314
 */
3315
function isUserIdValid($userId): bool
3316
{
3317
    if (is_null($userId) === false
3318
        && isset($userId) === true
3319
        && empty($userId) === false
3320
    ) {
3321
        return true;
3322
    }
3323
    return false;
3324
}
3325
3326
/**
3327
 * Check if a key exists and if its value equal the one expected
3328
 *
3329
 * @param string $key
3330
 * @param integer|string $value
3331
 * @param array $array
3332
 * 
3333
 * @return boolean
3334
 */
3335
function isKeyExistingAndEqual(
3336
    string $key,
3337
    /*PHP8 - integer|string*/$value,
3338
    array $array
3339
): bool
3340
{
3341
    if (isset($array[$key]) === true
3342
        && (is_int($value) === true ?
3343
            (int) $array[$key] === $value :
3344
            (string) $array[$key] === $value)
3345
    ) {
3346
        return true;
3347
    }
3348
    return false;
3349
}
3350
3351
/**
3352
 * Check if a variable is not set or equal to a value
3353
 *
3354
 * @param string|null $var
3355
 * @param integer|string $value
3356
 * 
3357
 * @return boolean
3358
 */
3359
function isKeyNotSetOrEqual(
3360
    /*PHP8 - string|null*/$var,
3361
    /*PHP8 - integer|string*/$value
3362
): bool
3363
{
3364
    if (isset($var) === false
3365
        || (is_int($value) === true ?
3366
            (int) $var === $value :
3367
            (string) $var === $value)
3368
    ) {
3369
        return true;
3370
    }
3371
    return false;
3372
}
3373
3374
/**
3375
 * Check if a key exists and if its value < to the one expected
3376
 *
3377
 * @param string $key
3378
 * @param integer $value
3379
 * @param array $array
3380
 * 
3381
 * @return boolean
3382
 */
3383
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3384
{
3385
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3386
        return true;
3387
    }
3388
    return false;
3389
}
3390
3391
/**
3392
 * Check if a key exists and if its value > to the one expected
3393
 *
3394
 * @param string $key
3395
 * @param integer $value
3396
 * @param array $array
3397
 * 
3398
 * @return boolean
3399
 */
3400
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3401
{
3402
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3403
        return true;
3404
    }
3405
    return false;
3406
}
3407
3408
/**
3409
 * Check if values in array are set
3410
 * Return true if all set
3411
 * Return false if one of them is not set
3412
 *
3413
 * @param array $arrayOfValues
3414
 * @return boolean
3415
 */
3416
function isSetArrayOfValues(array $arrayOfValues): bool
3417
{
3418
    foreach($arrayOfValues as $value) {
3419
        if (isset($value) === false) {
3420
            return false;
3421
        }
3422
    }
3423
    return true;
3424
}
3425
3426
/**
3427
 * Check if values in array are set
3428
 * Return true if all set
3429
 * Return false if one of them is not set
3430
 *
3431
 * @param array $arrayOfValues
3432
 * @param integer|string $value
3433
 * @return boolean
3434
 */
3435
function isArrayOfVarsEqualToValue(
3436
    array $arrayOfVars,
3437
    /*PHP8 - integer|string*/$value
3438
) : bool
3439
{
3440
    foreach($arrayOfVars as $variable) {
3441
        if ($variable !== $value) {
3442
            return false;
3443
        }
3444
    }
3445
    return true;
3446
}
3447
3448
/**
3449
 * Checks if at least one variable in array is equal to value
3450
 *
3451
 * @param array $arrayOfValues
3452
 * @param integer|string $value
3453
 * @return boolean
3454
 */
3455
function isOneVarOfArrayEqualToValue(
3456
    array $arrayOfVars,
3457
    /*PHP8 - integer|string*/$value
3458
) : bool
3459
{
3460
    foreach($arrayOfVars as $variable) {
3461
        if ($variable === $value) {
3462
            return true;
3463
        }
3464
    }
3465
    return false;
3466
}
3467
3468
/**
3469
 * Checks is value is null, not set OR empty
3470
 *
3471
 * @param string|int|null $value
3472
 * @return boolean
3473
 */
3474
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3475
{
3476
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3477
        return true;
3478
    }
3479
    return false;
3480
}
3481
3482
/**
3483
 * Checks if value is set and if empty is equal to passed boolean
3484
 *
3485
 * @param string|int $value
3486
 * @param boolean $boolean
3487
 * @return boolean
3488
 */
3489
function isValueSetEmpty($value, $boolean = true) : bool
3490
{
3491
    if (isset($value) === true && empty($value) === $boolean) {
3492
        return true;
3493
    }
3494
    return false;
3495
}
3496
3497
/**
3498
 * Ensure Complexity is translated
3499
 *
3500
 * @return void
3501
 */
3502
function defineComplexity() : void
3503
{
3504
    if (defined('TP_PW_COMPLEXITY') === false) {
3505
        define(
3506
            'TP_PW_COMPLEXITY',
3507
            [
3508
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3509
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3510
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3511
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3512
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3513
            ]
3514
        );
3515
    }
3516
}
3517
3518
/**
3519
 * Uses Sanitizer to perform data sanitization
3520
 *
3521
 * @param array     $data
3522
 * @param array     $filters
3523
 * @param string    $path
3524
 * @return array
3525
 */
3526
function dataSanitizer(
3527
    array $data,
3528
    array $filters,
3529
    string $path = __DIR__. '/..' // Path to Teampass root
3530
): array
3531
{
3532
    // Load Sanitizer library
3533
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3534
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3535
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3536
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3537
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3538
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3539
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3540
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3541
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3542
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3543
3544
    // Sanitize post and get variables
3545
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3546
    return $sanitizer->sanitize();
3547
}
3548
3549
/**
3550
 * Permits to manage the cache tree for a user
3551
 *
3552
 * @param integer $user_id
3553
 * @param string $data
3554
 * @param array $SETTINGS
3555
 * @param string $field_update
3556
 * @return void
3557
 */
3558
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3559
{
3560
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3561
    //Connect to DB
3562
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3563
    if (defined('DB_PASSWD_CLEAR') === false) {
3564
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3565
    }
3566
    DB::$host = DB_HOST;
3567
    DB::$user = DB_USER;
3568
    DB::$password = DB_PASSWD_CLEAR;
3569
    DB::$dbName = DB_NAME;
3570
    DB::$port = DB_PORT;
3571
    DB::$encoding = DB_ENCODING;
3572
    DB::$ssl = DB_SSL;
3573
    DB::$connect_options = DB_CONNECT_OPTIONS;
3574
3575
    // Exists ?
3576
    $userCacheId = DB::queryfirstrow(
3577
        'SELECT increment_id
3578
        FROM ' . prefixTable('cache_tree') . '
3579
        WHERE user_id = %i',
3580
        $user_id
3581
    );
3582
    
3583
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3584
        DB::insert(
3585
            prefixTable('cache_tree'),
3586
            array(
3587
                'data' => $data,
3588
                'timestamp' => time(),
3589
                'user_id' => $user_id,
3590
                'visible_folders' => '',
3591
            )
3592
        );
3593
    } else {
3594
        if (empty($field_update) === true) {
3595
            DB::update(
3596
                prefixTable('cache_tree'),
3597
                [
3598
                    'timestamp' => time(),
3599
                    'data' => $data,
3600
                ],
3601
                'increment_id = %i',
3602
                $userCacheId['increment_id']
3603
            );
3604
        } else {
3605
            DB::update(
3606
                prefixTable('cache_tree'),
3607
                [
3608
                    $field_update => $data,
3609
                ],
3610
                'increment_id = %i',
3611
                $userCacheId['increment_id']
3612
            );
3613
        }
3614
    }
3615
}
3616
3617
/**
3618
 * Permits to calculate a %
3619
 *
3620
 * @param float $nombre
3621
 * @param float $total
3622
 * @param float $pourcentage
3623
 * @return float
3624
 */
3625
function pourcentage(float $nombre, float $total, float $pourcentage): float
3626
{ 
3627
    $resultat = ($nombre/$total) * $pourcentage;
3628
    return round($resultat);
3629
}
3630
3631
/**
3632
 * Load the folders list from the cache
3633
 *
3634
 * @param string $fieldName
3635
 * @param string $sessionName
3636
 * @param boolean $forceRefresh
3637
 * @return array
3638
 */
3639
function loadFoldersListByCache(
3640
    string $fieldName,
3641
    string $sessionName,
3642
    bool $forceRefresh = false
3643
): array
3644
{
3645
    // Case when refresh is EXPECTED / MANDATORY
3646
    if ($forceRefresh === true) {
3647
        return [
3648
            'state' => false,
3649
            'data' => [],
3650
        ];
3651
    }
3652
3653
    // Get last folder update
3654
    $lastFolderChange = DB::queryfirstrow(
3655
        'SELECT valeur FROM ' . prefixTable('misc') . '
3656
        WHERE type = %s AND intitule = %s',
3657
        'timestamp',
3658
        'last_folder_change'
3659
    );
3660
    if (DB::count() === 0) {
3661
        $lastFolderChange['valeur'] = 0;
3662
    }
3663
3664
    // Case when an update in the tree has been done
3665
    // Refresh is then mandatory
3666
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3667
        return [
3668
            'state' => false,
3669
            'data' => [],
3670
        ];
3671
    }
3672
3673
    // Does this user has the tree structure in session?
3674
    // If yes then use it
3675
    if (count(isset($_SESSION['teampassUser'][$sessionName]) === true ? $_SESSION['teampassUser'][$sessionName] : []) > 0) {
3676
        return [
3677
            'state' => true,
3678
            'data' => json_encode($_SESSION['teampassUser'][$sessionName]),
3679
        ];
3680
    }
3681
3682
    // Does this user has a tree cache
3683
    $userCacheTree = DB::queryfirstrow(
3684
        'SELECT '.$fieldName.'
3685
        FROM ' . prefixTable('cache_tree') . '
3686
        WHERE user_id = %i',
3687
        $_SESSION['user_id']
3688
    );
3689
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3690
        return [
3691
            'state' => true,
3692
            'data' => $userCacheTree[$fieldName],
3693
        ];
3694
    }
3695
3696
    return [
3697
        'state' => false,
3698
        'data' => [],
3699
    ];
3700
}
3701
3702
3703
/**
3704
 * Permits to refresh the categories of folders
3705
 *
3706
 * @param array $folderIds
3707
 * @return void
3708
 */
3709
function handleFoldersCategories(
3710
    array $folderIds
3711
)
3712
{
3713
    //load ClassLoader
3714
    include_once __DIR__. '/../sources/SplClassLoader.php';
3715
    
3716
    //Connect to DB
3717
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3718
    if (defined('DB_PASSWD_CLEAR') === false) {
3719
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
3720
    }
3721
    DB::$host = DB_HOST;
3722
    DB::$user = DB_USER;
3723
    DB::$password = DB_PASSWD_CLEAR;
3724
    DB::$dbName = DB_NAME;
3725
    DB::$port = DB_PORT;
3726
    DB::$encoding = DB_ENCODING;
3727
    DB::$ssl = DB_SSL;
3728
    DB::$connect_options = DB_CONNECT_OPTIONS;
3729
3730
    $arr_data = array();
3731
3732
    // force full list of folders
3733
    if (count($folderIds) === 0) {
3734
        $folderIds = DB::queryFirstColumn(
3735
            'SELECT id
3736
            FROM ' . prefixTable('nested_tree') . '
3737
            WHERE personal_folder=%i',
3738
            0
3739
        );
3740
    }
3741
3742
    // Get complexity
3743
    defineComplexity();
3744
3745
    // update
3746
    foreach ($folderIds as $folder) {
3747
        // Do we have Categories
3748
        // get list of associated Categories
3749
        $arrCatList = array();
3750
        $rows_tmp = DB::query(
3751
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3752
            f.id_category AS category_id
3753
            FROM ' . prefixTable('categories_folders') . ' AS f
3754
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3755
            WHERE id_folder=%i',
3756
            $folder
3757
        );
3758
        if (DB::count() > 0) {
3759
            foreach ($rows_tmp as $row) {
3760
                $arrCatList[$row['id']] = array(
3761
                    'id' => $row['id'],
3762
                    'title' => $row['title'],
3763
                    'level' => $row['level'],
3764
                    'type' => $row['type'],
3765
                    'masked' => $row['masked'],
3766
                    'order' => $row['order'],
3767
                    'encrypted_data' => $row['encrypted_data'],
3768
                    'role_visibility' => $row['role_visibility'],
3769
                    'is_mandatory' => $row['is_mandatory'],
3770
                    'category_id' => $row['category_id'],
3771
                );
3772
            }
3773
        }
3774
        $arr_data['categories'] = $arrCatList;
3775
3776
        // Now get complexity
3777
        $valTemp = '';
3778
        $data = DB::queryFirstRow(
3779
            'SELECT valeur
3780
            FROM ' . prefixTable('misc') . '
3781
            WHERE type = %s AND intitule=%i',
3782
            'complex',
3783
            $folder
3784
        );
3785
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3786
            $valTemp = array(
3787
                'value' => $data['valeur'],
3788
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3789
            );
3790
        }
3791
        $arr_data['complexity'] = $valTemp;
3792
3793
        // Now get Roles
3794
        $valTemp = '';
3795
        $rows_tmp = DB::query(
3796
            'SELECT t.title
3797
            FROM ' . prefixTable('roles_values') . ' as v
3798
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3799
            WHERE v.folder_id = %i
3800
            GROUP BY title',
3801
            $folder
3802
        );
3803
        foreach ($rows_tmp as $record) {
3804
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3805
        }
3806
        $arr_data['visibilityRoles'] = $valTemp;
3807
3808
        // now save in DB
3809
        DB::update(
3810
            prefixTable('nested_tree'),
3811
            array(
3812
                'categories' => json_encode($arr_data),
3813
            ),
3814
            'id = %i',
3815
            $folder
3816
        );
3817
    }
3818
}
3819
3820
/**
3821
 * List all users that have specific roles
3822
 *
3823
 * @param array $roles
3824
 * @return array
3825
 */
3826
function getUsersWithRoles(
3827
    array $roles
3828
): array
3829
{
3830
    $arrUsers = array();
3831
3832
    foreach ($roles as $role) {
3833
        // loop on users and check if user has this role
3834
        $rows = DB::query(
3835
            'SELECT id, fonction_id
3836
            FROM ' . prefixTable('users') . '
3837
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3838
            $_SESSION['user_id']
3839
        );
3840
        foreach ($rows as $user) {
3841
            $userRoles = explode(';', is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? $user['fonction_id'] : []);
0 ignored issues
show
Bug introduced by
It seems like is_null($user['fonction_...fonction_id'] : array() can also be of type array; however, parameter $string of explode() 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

3841
            $userRoles = explode(';', /** @scrutinizer ignore-type */ is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? $user['fonction_id'] : []);
Loading history...
3842
            if (in_array($role, $userRoles, true) === true) {
3843
                array_push($arrUsers, $user['id']);
3844
            }
3845
        }
3846
    }
3847
3848
    return $arrUsers;
3849
}
3850
3851
// #3476 - check if function str_contains exists (using PHP 8.0.0 or h)
3852
// else define it
3853
if (!function_exists('str_contains')) {
3854
    function str_contains($haystack, $needle) {
3855
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
3856
    }
3857
}
3858
3859
/**
3860
 * Get all users informations
3861
 *
3862
 * @param integer $userId
3863
 * @return array
3864
 */
3865
function getFullUserInfos(
3866
    int $userId
3867
): array
3868
{
3869
    if (empty($userId) === true) {
3870
        return array();
3871
    }
3872
3873
    $val = DB::queryfirstrow(
3874
        'SELECT *
3875
        FROM ' . prefixTable('users') . '
3876
        WHERE id = %i',
3877
        $userId
3878
    );
3879
3880
    return $val;
3881
}
3882
3883
/**
3884
 * Is required an upgrade
3885
 *
3886
 * @return boolean
3887
 */
3888
function upgradeRequired(): bool
3889
{
3890
    // Get settings.php
3891
    include_once __DIR__. '/../includes/config/settings.php';
3892
3893
    // Get timestamp in DB
3894
    $val = DB::queryfirstrow(
3895
        'SELECT valeur
3896
        FROM ' . prefixTable('misc') . '
3897
        WHERE type = %s AND intitule = %s',
3898
        'admin',
3899
        'upgrade_timestamp'
3900
    );
3901
    
3902
    // if not exists then error
3903
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3904
3905
    // if empty or too old then error
3906
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3907
        return true;
3908
    }
3909
3910
    return false;
3911
}
3912
3913
/**
3914
 * Permits to change the user keys on his demand
3915
 *
3916
 * @param integer $userId
3917
 * @param string $passwordClear
3918
 * @param string $encryptionKey
3919
 * @param boolean $deleteExistingKeys
3920
 * @param boolean $sendEmailToUser
3921
 * @param boolean $encryptWithUserPassword
3922
 * @param boolean $encryptWithUserPassword
3923
 * @param integer $nbItemsToTreat
3924
 * @return string
3925
 */
3926
function handleUserKeys(
3927
    int $userId,
3928
    string $passwordClear,
3929
    string $encryptionKey = '',
3930
    bool $deleteExistingKeys = false,
3931
    bool $sendEmailToUser = true,
3932
    bool $encryptWithUserPassword = false,
3933
    bool $generate_user_new_password = false,
3934
    int $nbItemsToTreat
3935
): string
3936
{
3937
3938
    // prepapre background tasks for item keys generation        
3939
    $userTP = DB::queryFirstRow(
3940
        'SELECT pw, public_key, private_key
3941
        FROM ' . prefixTable('users') . '
3942
        WHERE id = %i',
3943
        TP_USER_ID
3944
    );
3945
    if (DB::count() > 0) {
3946
        // Do we need to generate new user password
3947
        if ($generate_user_new_password === true) {
3948
            // Generate a new password
3949
            $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3950
3951
            // Hash the new password
3952
            $pwdlib = new SplClassLoader('PasswordLib', '../includes/libraries');
3953
            $pwdlib->register();
3954
            $pwdlib = new PasswordLib\PasswordLib();
3955
            $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3956
            if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3957
                echo prepareExchangedData(
3958
                $SETTINGS['cpassman_dir'],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $SETTINGS seems to be never defined.
Loading history...
3959
                    array(
3960
                        'error' => true,
3961
                        'message' => langHdl('pw_hash_not_correct'),
3962
                    ),
3963
                    'encode'
3964
                );
3965
            }
3966
3967
            // Generate new keys
3968
            $userKeys = generateUserKeys($passwordClear);
3969
3970
            // Save in DB
3971
            DB::update(
3972
                prefixTable('users'),
3973
                array(
3974
                    'pw' => $hashedPassword,
3975
                    'public_key' => $userKeys['public_key'],
3976
                    'private_key' => $userKeys['private_key'],
3977
                ),
3978
                'id=%i',
3979
                $userId
3980
            );
3981
        }
3982
3983
        // Manage empty encryption key
3984
        // Let's take the user's password if asked and if no encryption key provided
3985
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3986
3987
        // Create process
3988
        DB::insert(
3989
            prefixTable('processes'),
3990
            array(
3991
                'created_at' => time(),
3992
                'process_type' => 'create_user_keys',
3993
                'arguments' => json_encode([
3994
                    'new_user_id' => (int) $userId,
3995
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3996
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3997
                    'owner_id' => (int) TP_USER_ID,
3998
                    'creator_pwd' => $userTP['pw'],
3999
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
4000
                    'otp_provided_new_value' => 1,
4001
                ]),
4002
                'updated_at' => '',
4003
                'finished_at' => '',
4004
                'output' => '',
4005
            )
4006
        );
4007
        $processId = DB::insertId();
4008
4009
        // Delete existing keys
4010
        if ($deleteExistingKeys === true) {
4011
            deleteUserObjetsKeys(
4012
                (int) $userId,
4013
            );
4014
        }
4015
4016
        // Create tasks
4017
        DB::insert(
4018
            prefixTable('processes_tasks'),
4019
            array(
4020
                'process_id' => $processId,
4021
                'created_at' => time(),
4022
                'task' => json_encode([
4023
                    'step' => 'step0',
4024
                    'index' => 0,
4025
                    'nb' => $nbItemsToTreat,
4026
                ]),
4027
            )
4028
        );
4029
4030
        DB::insert(
4031
            prefixTable('processes_tasks'),
4032
            array(
4033
                'process_id' => $processId,
4034
                'created_at' => time(),
4035
                'task' => json_encode([
4036
                    'step' => 'step1',
4037
                    'index' => 0,
4038
                    'nb' => $nbItemsToTreat,
4039
                ]),
4040
            )
4041
        );
4042
4043
        DB::insert(
4044
            prefixTable('processes_tasks'),
4045
            array(
4046
                'process_id' => $processId,
4047
                'created_at' => time(),
4048
                'task' => json_encode([
4049
                    'step' => 'step2',
4050
                    'index' => 0,
4051
                    'nb' => $nbItemsToTreat,
4052
                ]),
4053
            )
4054
        );
4055
4056
        DB::insert(
4057
            prefixTable('processes_tasks'),
4058
            array(
4059
                'process_id' => $processId,
4060
                'created_at' => time(),
4061
                'task' => json_encode([
4062
                    'step' => 'step3',
4063
                    'index' => 0,
4064
                    'nb' => $nbItemsToTreat,
4065
                ]),
4066
            )
4067
        );
4068
4069
        DB::insert(
4070
            prefixTable('processes_tasks'),
4071
            array(
4072
                'process_id' => $processId,
4073
                'created_at' => time(),
4074
                'task' => json_encode([
4075
                    'step' => 'step4',
4076
                    'index' => 0,
4077
                    'nb' => $nbItemsToTreat,
4078
                ]),
4079
            )
4080
        );
4081
4082
        DB::insert(
4083
            prefixTable('processes_tasks'),
4084
            array(
4085
                'process_id' => $processId,
4086
                'created_at' => time(),
4087
                'task' => json_encode([
4088
                    'step' => 'step5',
4089
                    'index' => 0,
4090
                    'nb' => $nbItemsToTreat,
4091
                ]),
4092
            )
4093
        );
4094
4095
        DB::insert(
4096
            prefixTable('processes_tasks'),
4097
            array(
4098
                'process_id' => $processId,
4099
                'created_at' => time(),
4100
                'task' => json_encode([
4101
                    'step' => 'step6',
4102
                    'index' => 0,
4103
                    'nb' => $nbItemsToTreat,
4104
                ]),
4105
            )
4106
        );
4107
4108
        // update user's new status
4109
        DB::update(
4110
            prefixTable('users'),
4111
            [
4112
                'is_ready_for_usage' => 0,
4113
                'otp_provided' => 1,
4114
                'ongoing_process_id' => $processId,
4115
                'special' => 'generate-keys',
4116
            ],
4117
            'id=%i',
4118
            $userId
4119
        );
4120
    }
4121
4122
    return prepareExchangedData(
4123
        __DIR__.'/..',
4124
        array(
4125
            'error' => false,
4126
            'message' => '',
4127
        ),
4128
        'encode'
4129
    );
4130
}