Passed
Push — master ( 64c076...b207d0 )
by Nils
09:35
created

purgeUnnecessaryKeysForUser()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 56
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 41
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 56
rs 9.264

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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