Passed
Push — master ( b85c6b...c02a92 )
by Nils
04:55
created

createTaskForItemUpdate()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 70
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 40
c 1
b 0
f 0
nc 4
nop 5
dl 0
loc 70
rs 8.9688

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 file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.functions.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use LdapRecord\Connection;
33
use ForceUTF8\Encoding;
34
use Elegant\Sanitizer\Sanitizer;
35
use voku\helper\AntiXSS;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use TeampassClasses\SessionManager\SessionManager;
39
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
40
use TeampassClasses\Language\Language;
41
use TeampassClasses\NestedTree\NestedTree;
42
use Defuse\Crypto\Key;
43
use Defuse\Crypto\Crypto;
44
use Defuse\Crypto\KeyProtectedByPassword;
45
use Defuse\Crypto\File as CryptoFile;
46
use Defuse\Crypto\Exception as CryptoException;
47
use Elegant\Sanitizer\Filters\Uppercase;
48
use PHPMailer\PHPMailer\PHPMailer;
49
use PasswordLib\PasswordLib;
50
use Symfony\Component\Process\Exception\ProcessFailedException;
51
use Symfony\Component\Process\Process;
52
use Symfony\Component\Process\PhpExecutableFinder;
53
use TeampassClasses\Encryption\Encryption;
54
55
56
// Load config if $SETTINGS not defined
57
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
58
    include_once __DIR__ . '/../includes/config/tp.config.php';
59
}
60
61
header('Content-type: text/html; charset=utf-8');
62
header('Cache-Control: no-cache, must-revalidate');
63
64
loadClasses('DB');
65
$session = SessionManager::getSession();
66
67
68
/**
69
 * genHash().
70
 *
71
 * Generate a hash for user login
72
 *
73
 * @param string $password What password
74
 * @param string $cost     What cost
75
 *
76
 * @return string|void
77
 */
78
function bCrypt(
79
    string $password,
80
    string $cost
81
): ?string
82
{
83
    $salt = sprintf('$2y$%02d$', $cost);
84
    if (function_exists('openssl_random_pseudo_bytes')) {
85
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
86
    } else {
87
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
88
        for ($i = 0; $i < 22; ++$i) {
89
            $salt .= $chars[mt_rand(0, 63)];
90
        }
91
    }
92
93
    return crypt($password, $salt);
94
}
95
96
/**
97
 * Checks if a string is hex encoded
98
 *
99
 * @param string $str
100
 * @return boolean
101
 */
102
function isHex(string $str): bool
103
{
104
    if (str_starts_with(strtolower($str), '0x')) {
105
        $str = substr($str, 2);
106
    }
107
108
    return ctype_xdigit($str);
109
}
110
111
/**
112
 * Defuse cryption function.
113
 *
114
 * @param string $message   what to de/crypt
115
 * @param string $ascii_key key to use
116
 * @param string $type      operation to perform
117
 * @param array  $SETTINGS  Teampass settings
118
 *
119
 * @return array
120
 */
121
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
122
{
123
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
124
    $err = false;
125
    
126
    // convert KEY
127
    $key = Key::loadFromAsciiSafeString($ascii_key);
128
    try {
129
        if ($type === 'encrypt') {
130
            $text = Crypto::encrypt($message, $key);
131
        } elseif ($type === 'decrypt') {
132
            $text = Crypto::decrypt($message, $key);
133
        }
134
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
135
        $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.';
136
    } catch (CryptoException\BadFormatException $ex) {
137
        $err = $ex;
138
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
139
        $err = $ex;
140
    } catch (CryptoException\CryptoException $ex) {
141
        $err = $ex;
142
    } catch (CryptoException\IOException $ex) {
143
        $err = $ex;
144
    }
145
146
    return [
147
        'string' => $text ?? '',
148
        'error' => $err,
149
    ];
150
}
151
152
/**
153
 * Generating a defuse key.
154
 *
155
 * @return string
156
 */
157
function defuse_generate_key()
158
{
159
    $key = Key::createNewRandomKey();
160
    $key = $key->saveToAsciiSafeString();
161
    return $key;
162
}
163
164
/**
165
 * Generate a Defuse personal key.
166
 *
167
 * @param string $psk psk used
168
 *
169
 * @return string
170
 */
171
function defuse_generate_personal_key(string $psk): string
172
{
173
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
174
    return $protected_key->saveToAsciiSafeString(); // save this in user table
175
}
176
177
/**
178
 * Validate persoanl key with defuse.
179
 *
180
 * @param string $psk                   the user's psk
181
 * @param string $protected_key_encoded special key
182
 *
183
 * @return string
184
 */
185
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
186
{
187
    try {
188
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
189
        $user_key = $protected_key_encoded->unlockKey($psk);
190
        $user_key_encoded = $user_key->saveToAsciiSafeString();
191
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
192
        return 'Error - Major issue as the encryption is broken.';
193
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
194
        return 'Error - The saltkey is not the correct one.';
195
    }
196
197
    return $user_key_encoded;
198
    // store it in session once user has entered his psk
199
}
200
201
/**
202
 * Decrypt a defuse string if encrypted.
203
 *
204
 * @param string $value Encrypted string
205
 *
206
 * @return string Decrypted string
207
 */
208
function defuseReturnDecrypted(string $value, $SETTINGS): string
209
{
210
    if (substr($value, 0, 3) === 'def') {
211
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
212
    }
213
214
    return $value;
215
}
216
217
/**
218
 * Trims a string depending on a specific string.
219
 *
220
 * @param string|array $chaine  what to trim
221
 * @param string       $element trim on what
222
 *
223
 * @return string
224
 */
225
function trimElement($chaine, string $element): string
226
{
227
    if (! empty($chaine)) {
228
        if (is_array($chaine) === true) {
229
            $chaine = implode(';', $chaine);
230
        }
231
        $chaine = trim($chaine);
232
        if (substr($chaine, 0, 1) === $element) {
233
            $chaine = substr($chaine, 1);
234
        }
235
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
236
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
237
        }
238
    }
239
240
    return $chaine;
241
}
242
243
/**
244
 * Permits to suppress all "special" characters from string.
245
 *
246
 * @param string $string  what to clean
247
 * @param bool   $special use of special chars?
248
 *
249
 * @return string
250
 */
251
function cleanString(string $string, bool $special = false): string
252
{
253
    // Create temporary table for special characters escape
254
    $tabSpecialChar = [];
255
    for ($i = 0; $i <= 31; ++$i) {
256
        $tabSpecialChar[] = chr($i);
257
    }
258
    array_push($tabSpecialChar, '<br />');
259
    if ((int) $special === 1) {
260
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
261
    }
262
263
    return str_replace($tabSpecialChar, "\n", $string);
264
}
265
266
/**
267
 * Erro manager for DB.
268
 *
269
 * @param array $params output from query
270
 *
271
 * @return void
272
 */
273
function db_error_handler(array $params): void
274
{
275
    echo 'Error: ' . $params['error'] . "<br>\n";
276
    echo 'Query: ' . $params['query'] . "<br>\n";
277
    throw new Exception('Error - Query', 1);
278
}
279
280
/**
281
 * Identify user's rights
282
 *
283
 * @param string|array $groupesVisiblesUser  [description]
284
 * @param string|array $groupesInterditsUser [description]
285
 * @param string       $isAdmin              [description]
286
 * @param string       $idFonctions          [description]
287
 *
288
 * @return bool
289
 */
290
function identifyUserRights(
291
    $groupesVisiblesUser,
292
    $groupesInterditsUser,
293
    $isAdmin,
294
    $idFonctions,
295
    $SETTINGS
296
) {
297
    $session = SessionManager::getSession();
298
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
299
300
    // Check if user is ADMINISTRATOR    
301
    (int) $isAdmin === 1 ?
302
        identAdmin(
303
            $idFonctions,
304
            $SETTINGS, /** @scrutinizer ignore-type */
305
            $tree
306
        )
307
        :
308
        identUser(
309
            $groupesVisiblesUser,
310
            $groupesInterditsUser,
311
            $idFonctions,
312
            $SETTINGS, /** @scrutinizer ignore-type */
313
            $tree
314
        );
315
316
    // update user's timestamp
317
    DB::update(
318
        prefixTable('users'),
319
        [
320
            'timestamp' => time(),
321
        ],
322
        'id=%i',
323
        $session->get('user-id')
324
    );
325
326
    return true;
327
}
328
329
/**
330
 * Identify administrator.
331
 *
332
 * @param string $idFonctions Roles of user
333
 * @param array  $SETTINGS    Teampass settings
334
 * @param object $tree        Tree of folders
335
 *
336
 * @return bool
337
 */
338
function identAdmin($idFonctions, $SETTINGS, $tree)
339
{
340
    
341
    $session = SessionManager::getSession();
342
    $groupesVisibles = [];
343
    $session->set('user-personal_folders', []);
344
    $session->set('user-accessible_folders', []);
345
    $session->set('user-no_access_folders', []);
346
    $session->set('user-personal_visible_folders', []);
347
    $session->set('user-read_only_folders', []);
348
    $session->set('system-list_restricted_folders_for_items', []);
349
    $session->set('system-list_folders_editable_by_role', []);
350
    $session->set('user-list_folders_limited', []);
351
    $session->set('user-forbiden_personal_folders', []);
352
    $globalsUserId = $session->get('user-id');
353
    $globalsVisibleFolders = $session->get('user-accessible_folders');
354
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
355
    // Get list of Folders
356
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
357
    foreach ($rows as $record) {
358
        array_push($groupesVisibles, $record['id']);
359
    }
360
    $session->set('user-accessible_folders', $groupesVisibles);
361
    $session->set('user-all_non_personal_folders', $groupesVisibles);
362
    // Exclude all PF
363
    $where = new WhereClause('and');
364
    // create a WHERE statement of pieces joined by ANDs
365
    $where->add('personal_folder=%i', 1);
366
    if (
367
        isset($SETTINGS['enable_pf_feature']) === true
368
        && (int) $SETTINGS['enable_pf_feature'] === 1
369
    ) {
370
        $where->add('title=%s', $globalsUserId);
371
        $where->negateLast();
372
    }
373
    // Get ID of personal folder
374
    $persfld = DB::queryfirstrow(
375
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
376
        $globalsUserId
377
    );
378
    if (empty($persfld['id']) === false) {
379
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
380
            array_push($globalsVisibleFolders, $persfld['id']);
381
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
382
            // get all descendants
383
            $tree->rebuild();
384
            $tst = $tree->getDescendants($persfld['id']);
385
            foreach ($tst as $t) {
386
                array_push($globalsVisibleFolders, $t->id);
387
                array_push($globalsPersonalVisibleFolders, $t->id);
388
            }
389
        }
390
    }
391
392
    // get complete list of ROLES
393
    $tmp = explode(';', $idFonctions);
394
    $rows = DB::query(
395
        'SELECT * FROM ' . prefixTable('roles_title') . '
396
        ORDER BY title ASC'
397
    );
398
    foreach ($rows as $record) {
399
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
400
            array_push($tmp, $record['id']);
401
        }
402
    }
403
    $session->set('user-roles', implode(';', $tmp));
404
    $session->set('user-admin', 1);
405
    // Check if admin has created Folders and Roles
406
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
407
    $session->set('user-nb_folders', DB::count());
408
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
409
    $session->set('user-nb_roles', DB::count());
410
411
    return true;
412
}
413
414
/**
415
 * Permits to convert an element to array.
416
 *
417
 * @param string|array $element Any value to be returned as array
418
 *
419
 * @return array
420
 */
421
function convertToArray($element): array
422
{
423
    if (is_string($element) === true) {
424
        if (empty($element) === true) {
425
            return [];
426
        }
427
        return explode(
428
            ';',
429
            trimElement($element, ';')
430
        );
431
    }
432
    return $element;
433
}
434
435
/**
436
 * Defines the rights the user has.
437
 *
438
 * @param string|array $allowedFolders  Allowed folders
439
 * @param string|array $noAccessFolders Not allowed folders
440
 * @param string|array $userRoles       Roles of user
441
 * @param array        $SETTINGS        Teampass settings
442
 * @param object       $tree            Tree of folders
443
 * 
444
 * @return bool
445
 */
446
function identUser(
447
    $allowedFolders,
448
    $noAccessFolders,
449
    $userRoles,
450
    array $SETTINGS,
451
    object $tree
452
) {
453
    
454
    $session = SessionManager::getSession();
455
    // Init
456
    $session->set('user-accessible_folders', []);
457
    $session->set('user-personal_folders', []);
458
    $session->set('user-no_access_folders', []);
459
    $session->set('user-personal_visible_folders', []);
460
    $session->set('user-read_only_folders', []);
461
    $session->set('user-user-roles', $userRoles);
462
    $session->set('user-admin', 0);
463
    // init
464
    $personalFolders = [];
465
    $readOnlyFolders = [];
466
    $noAccessPersonalFolders = [];
467
    $restrictedFoldersForItems = [];
468
    $foldersLimited = [];
469
    $foldersLimitedFull = [];
470
    $allowedFoldersByRoles = [];
471
    $globalsUserId = $session->get('user-id');
472
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
473
    // Ensure consistency in array format
474
    $noAccessFolders = convertToArray($noAccessFolders);
475
    $userRoles = convertToArray($userRoles);
476
    $allowedFolders = convertToArray($allowedFolders);
477
    
478
    // Get list of folders depending on Roles
479
    $arrays = identUserGetFoldersFromRoles(
480
        $userRoles,
481
        $allowedFoldersByRoles,
482
        $readOnlyFolders,
483
        $allowedFolders
484
    );
485
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
486
    $readOnlyFolders = $arrays['readOnlyFolders'];
487
488
    // Does this user is allowed to see other items
489
    $inc = 0;
490
    $rows = DB::query(
491
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
492
            WHERE restricted_to LIKE %ss AND inactif = %s'.
493
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
494
        $globalsUserId,
495
        '0'
496
    );
497
    foreach ($rows as $record) {
498
        // Exclude restriction on item if folder is fully accessible
499
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
500
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
501
            ++$inc;
502
        //}
503
    }
504
505
    // Check for the users roles if some specific rights exist on items
506
    $rows = DB::query(
507
        'SELECT i.id_tree, r.item_id
508
        FROM ' . prefixTable('items') . ' as i
509
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
510
        WHERE i.id_tree <> "" '.
511
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
512
        'ORDER BY i.id_tree ASC',
513
        $userRoles
514
    );
515
    $inc = 0;
516
    foreach ($rows as $record) {
517
        //if (isset($record['id_tree'])) {
518
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
519
            array_push($foldersLimitedFull, $record['id_tree']);
520
            ++$inc;
521
        //}
522
    }
523
524
    // Get list of Personal Folders
525
    $arrays = identUserGetPFList(
526
        $globalsPersonalFolders,
527
        $allowedFolders,
528
        $globalsUserId,
529
        $personalFolders,
530
        $noAccessPersonalFolders,
531
        $foldersLimitedFull,
532
        $allowedFoldersByRoles,
533
        array_keys($restrictedFoldersForItems),
534
        $readOnlyFolders,
535
        $noAccessFolders,
536
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
537
        $tree
538
    );
539
    $allowedFolders = $arrays['allowedFolders'];
540
    $personalFolders = $arrays['personalFolders'];
541
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
542
543
    // Return data
544
    $session->set('user-all_non_personal_folders', $allowedFolders);
545
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
546
    $session->set('user-read_only_folders', $readOnlyFolders);
547
    $session->set('user-no_access_folders', $noAccessFolders);
548
    $session->set('user-personal_folders', $personalFolders);
549
    $session->set('user-list_folders_limited', $foldersLimited);
550
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
551
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
552
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
553
    $session->set(
554
        'all_folders_including_no_access',
555
        array_unique(array_merge(
556
            $allowedFolders,
557
            $personalFolders,
558
            $noAccessFolders,
559
            $readOnlyFolders
560
        ), SORT_NUMERIC)
561
    );
562
    // Folders and Roles numbers
563
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
564
    $session->set('user-nb_folders', DB::count());
565
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
566
    $session->set('user-nb_roles', DB::count());
567
    // check if change proposals on User's items
568
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
569
        $countNewItems = DB::query(
570
            'SELECT COUNT(*)
571
            FROM ' . prefixTable('items_change') . ' AS c
572
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
573
            WHERE i.action = %s AND i.id_user = %i',
574
            'at_creation',
575
            $globalsUserId
576
        );
577
        $session->set('user-nb_item_change_proposals', $countNewItems);
578
    } else {
579
        $session->set('user-nb_item_change_proposals', 0);
580
    }
581
582
    return true;
583
}
584
585
/**
586
 * Get list of folders depending on Roles
587
 * 
588
 * @param array $userRoles
589
 * @param array $allowedFoldersByRoles
590
 * @param array $readOnlyFolders
591
 * @param array $allowedFolders
592
 * 
593
 * @return array
594
 */
595
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
596
{
597
    $rows = DB::query(
598
        'SELECT *
599
        FROM ' . prefixTable('roles_values') . '
600
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
601
        ['W', 'ND', 'NE', 'NDNE', 'R'],
602
        $userRoles,
603
    );
604
    foreach ($rows as $record) {
605
        if ($record['type'] === 'R') {
606
            array_push($readOnlyFolders, $record['folder_id']);
607
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
608
            array_push($allowedFoldersByRoles, $record['folder_id']);
609
        }
610
    }
611
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
612
    $readOnlyFolders = array_unique($readOnlyFolders);
613
    
614
    // Clean arrays
615
    foreach ($allowedFoldersByRoles as $value) {
616
        $key = array_search($value, $readOnlyFolders);
617
        if ($key !== false) {
618
            unset($readOnlyFolders[$key]);
619
        }
620
    }
621
    return [
622
        'readOnlyFolders' => $readOnlyFolders,
623
        'allowedFoldersByRoles' => $allowedFoldersByRoles
624
    ];
625
}
626
627
/**
628
 * Get list of Personal Folders
629
 * 
630
 * @param int $globalsPersonalFolders
631
 * @param array $allowedFolders
632
 * @param int $globalsUserId
633
 * @param array $personalFolders
634
 * @param array $noAccessPersonalFolders
635
 * @param array $foldersLimitedFull
636
 * @param array $allowedFoldersByRoles
637
 * @param array $restrictedFoldersForItems
638
 * @param array $readOnlyFolders
639
 * @param array $noAccessFolders
640
 * @param int $enablePfFeature
641
 * @param object $tree
642
 * 
643
 * @return array
644
 */
645
function identUserGetPFList(
646
    $globalsPersonalFolders,
647
    $allowedFolders,
648
    $globalsUserId,
649
    $personalFolders,
650
    $noAccessPersonalFolders,
651
    $foldersLimitedFull,
652
    $allowedFoldersByRoles,
653
    $restrictedFoldersForItems,
654
    $readOnlyFolders,
655
    $noAccessFolders,
656
    $enablePfFeature,
657
    $tree
658
)
659
{
660
    if (
661
        (int) $enablePfFeature === 1
662
        && (int) $globalsPersonalFolders === 1
663
    ) {
664
        $persoFld = DB::queryfirstrow(
665
            'SELECT id
666
            FROM ' . prefixTable('nested_tree') . '
667
            WHERE title = %s AND personal_folder = %i'.
668
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
669
            $globalsUserId,
670
            1
671
        );
672
        if (empty($persoFld['id']) === false) {
673
            array_push($personalFolders, $persoFld['id']);
674
            array_push($allowedFolders, $persoFld['id']);
675
            // get all descendants
676
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
677
            foreach ($ids as $id) {
678
                //array_push($allowedFolders, $id);
679
                array_push($personalFolders, $id);
680
            }
681
        }
682
    }
683
    
684
    // Exclude all other PF
685
    $where = new WhereClause('and');
686
    $where->add('personal_folder=%i', 1);
687
    if (count($personalFolders) > 0) {
688
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
689
    }
690
    if (
691
        (int) $enablePfFeature === 1
692
        && (int) $globalsPersonalFolders === 1
693
    ) {
694
        $where->add('title=%s', $globalsUserId);
695
        $where->negateLast();
696
    }
697
    $persoFlds = DB::query(
698
        'SELECT id
699
        FROM ' . prefixTable('nested_tree') . '
700
        WHERE %l',
701
        $where
702
    );
703
    foreach ($persoFlds as $persoFldId) {
704
        array_push($noAccessPersonalFolders, $persoFldId['id']);
705
    }
706
707
    // All folders visibles
708
    $allowedFolders = array_unique(array_merge(
709
        $allowedFolders,
710
        $foldersLimitedFull,
711
        $allowedFoldersByRoles,
712
        $restrictedFoldersForItems,
713
        $readOnlyFolders
714
    ), SORT_NUMERIC);
715
    // Exclude from allowed folders all the specific user forbidden folders
716
    if (count($noAccessFolders) > 0) {
717
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
718
    }
719
720
    return [
721
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
722
        'personalFolders' => $personalFolders,
723
        'noAccessPersonalFolders' => $noAccessPersonalFolders
724
    ];
725
}
726
727
728
/**
729
 * Update the CACHE table.
730
 *
731
 * @param string $action   What to do
732
 * @param array  $SETTINGS Teampass settings
733
 * @param int    $ident    Ident format
734
 * 
735
 * @return void
736
 */
737
function updateCacheTable(string $action, ?int $ident = null): void
738
{
739
    if ($action === 'reload') {
740
        // Rebuild full cache table
741
        cacheTableRefresh();
742
    } elseif ($action === 'update_value' && is_null($ident) === false) {
743
        // UPDATE an item
744
        cacheTableUpdate($ident);
745
    } elseif ($action === 'add_value' && is_null($ident) === false) {
746
        // ADD an item
747
        cacheTableAdd($ident);
748
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
749
        // DELETE an item
750
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
751
    }
752
}
753
754
/**
755
 * Cache table - refresh.
756
 *
757
 * @return void
758
 */
759
function cacheTableRefresh(): void
760
{
761
    // Load class DB
762
    loadClasses('DB');
763
764
    //Load Tree
765
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
766
    // truncate table
767
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
768
    // reload date
769
    $rows = DB::query(
770
        'SELECT *
771
        FROM ' . prefixTable('items') . ' as i
772
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
773
        AND l.action = %s
774
        AND i.inactif = %i',
775
        'at_creation',
776
        0
777
    );
778
    foreach ($rows as $record) {
779
        if (empty($record['id_tree']) === false) {
780
            // Get all TAGS
781
            $tags = '';
782
            $itemTags = DB::query(
783
                'SELECT tag
784
                FROM ' . prefixTable('tags') . '
785
                WHERE item_id = %i AND tag != ""',
786
                $record['id']
787
            );
788
            foreach ($itemTags as $itemTag) {
789
                $tags .= $itemTag['tag'] . ' ';
790
            }
791
792
            // Get renewal period
793
            $resNT = DB::queryfirstrow(
794
                'SELECT renewal_period
795
                FROM ' . prefixTable('nested_tree') . '
796
                WHERE id = %i',
797
                $record['id_tree']
798
            );
799
            // form id_tree to full foldername
800
            $folder = [];
801
            $arbo = $tree->getPath($record['id_tree'], true);
802
            foreach ($arbo as $elem) {
803
                // Check if title is the ID of a user
804
                if (is_numeric($elem->title) === true) {
805
                    // Is this a User id?
806
                    $user = DB::queryfirstrow(
807
                        'SELECT id, login
808
                        FROM ' . prefixTable('users') . '
809
                        WHERE id = %i',
810
                        $elem->title
811
                    );
812
                    if (count($user) > 0) {
813
                        $elem->title = $user['login'];
814
                    }
815
                }
816
                // Build path
817
                array_push($folder, stripslashes($elem->title));
818
            }
819
            // store data
820
            DB::insert(
821
                prefixTable('cache'),
822
                [
823
                    'id' => $record['id'],
824
                    'label' => $record['label'],
825
                    'description' => $record['description'] ?? '',
826
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
827
                    'tags' => $tags,
828
                    'id_tree' => $record['id_tree'],
829
                    'perso' => $record['perso'],
830
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
831
                    'login' => $record['login'] ?? '',
832
                    'folder' => implode(' > ', $folder),
833
                    'author' => $record['id_user'],
834
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
835
                    'timestamp' => $record['date'],
836
                ]
837
            );
838
        }
839
    }
840
}
841
842
/**
843
 * Cache table - update existing value.
844
 *
845
 * @param int    $ident    Ident format
846
 * 
847
 * @return void
848
 */
849
function cacheTableUpdate(?int $ident = null): void
850
{
851
    $session = SessionManager::getSession();
852
    loadClasses('DB');
853
854
    //Load Tree
855
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
856
    // get new value from db
857
    $data = DB::queryfirstrow(
858
        'SELECT label, description, id_tree, perso, restricted_to, login, url
859
        FROM ' . prefixTable('items') . '
860
        WHERE id=%i',
861
        $ident
862
    );
863
    // Get all TAGS
864
    $tags = '';
865
    $itemTags = DB::query(
866
        'SELECT tag
867
            FROM ' . prefixTable('tags') . '
868
            WHERE item_id = %i AND tag != ""',
869
        $ident
870
    );
871
    foreach ($itemTags as $itemTag) {
872
        $tags .= $itemTag['tag'] . ' ';
873
    }
874
    // form id_tree to full foldername
875
    $folder = [];
876
    $arbo = $tree->getPath($data['id_tree'], true);
877
    foreach ($arbo as $elem) {
878
        // Check if title is the ID of a user
879
        if (is_numeric($elem->title) === true) {
880
            // Is this a User id?
881
            $user = DB::queryfirstrow(
882
                'SELECT id, login
883
                FROM ' . prefixTable('users') . '
884
                WHERE id = %i',
885
                $elem->title
886
            );
887
            if (count($user) > 0) {
888
                $elem->title = $user['login'];
889
            }
890
        }
891
        // Build path
892
        array_push($folder, stripslashes($elem->title));
893
    }
894
    // finaly update
895
    DB::update(
896
        prefixTable('cache'),
897
        [
898
            'label' => $data['label'],
899
            'description' => $data['description'],
900
            'tags' => $tags,
901
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
902
            'id_tree' => $data['id_tree'],
903
            'perso' => $data['perso'],
904
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
905
            'login' => $data['login'] ?? '',
906
            'folder' => implode(' » ', $folder),
907
            'author' => $session->get('user-id'),
908
        ],
909
        'id = %i',
910
        $ident
911
    );
912
}
913
914
/**
915
 * Cache table - add new value.
916
 *
917
 * @param int    $ident    Ident format
918
 * 
919
 * @return void
920
 */
921
function cacheTableAdd(?int $ident = null): void
922
{
923
    $session = SessionManager::getSession();
924
    $globalsUserId = $session->get('user-id');
925
926
    // Load class DB
927
    loadClasses('DB');
928
929
    //Load Tree
930
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
931
    // get new value from db
932
    $data = DB::queryFirstRow(
933
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
934
        FROM ' . prefixTable('items') . ' as i
935
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
936
        WHERE i.id = %i
937
        AND l.action = %s',
938
        $ident,
939
        'at_creation'
940
    );
941
    // Get all TAGS
942
    $tags = '';
943
    $itemTags = DB::query(
944
        'SELECT tag
945
            FROM ' . prefixTable('tags') . '
946
            WHERE item_id = %i AND tag != ""',
947
        $ident
948
    );
949
    foreach ($itemTags as $itemTag) {
950
        $tags .= $itemTag['tag'] . ' ';
951
    }
952
    // form id_tree to full foldername
953
    $folder = [];
954
    $arbo = $tree->getPath($data['id_tree'], true);
955
    foreach ($arbo as $elem) {
956
        // Check if title is the ID of a user
957
        if (is_numeric($elem->title) === true) {
958
            // Is this a User id?
959
            $user = DB::queryfirstrow(
960
                'SELECT id, login
961
                FROM ' . prefixTable('users') . '
962
                WHERE id = %i',
963
                $elem->title
964
            );
965
            if (count($user) > 0) {
966
                $elem->title = $user['login'];
967
            }
968
        }
969
        // Build path
970
        array_push($folder, stripslashes($elem->title));
971
    }
972
    // finaly update
973
    DB::insert(
974
        prefixTable('cache'),
975
        [
976
            'id' => $data['id'],
977
            'label' => $data['label'],
978
            'description' => $data['description'],
979
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
980
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
981
            'id_tree' => $data['id_tree'],
982
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
983
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
984
            'login' => $data['login'] ?? '',
985
            'folder' => implode(' » ', $folder),
986
            'author' => $globalsUserId,
987
            'timestamp' => $data['date'],
988
        ]
989
    );
990
}
991
992
/**
993
 * Do statistics.
994
 *
995
 * @param array $SETTINGS Teampass settings
996
 *
997
 * @return array
998
 */
999
function getStatisticsData(array $SETTINGS): array
1000
{
1001
    DB::query(
1002
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1003
        0
1004
    );
1005
    $counter_folders = DB::count();
1006
    DB::query(
1007
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1008
        1
1009
    );
1010
    $counter_folders_perso = DB::count();
1011
    DB::query(
1012
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1013
        0
1014
    );
1015
    $counter_items = DB::count();
1016
        DB::query(
1017
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1018
        1
1019
    );
1020
    $counter_items_perso = DB::count();
1021
        DB::query(
1022
        'SELECT id FROM ' . prefixTable('users') . ''
1023
    );
1024
    $counter_users = DB::count();
1025
        DB::query(
1026
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1027
        1
1028
    );
1029
    $admins = DB::count();
1030
    DB::query(
1031
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1032
        1
1033
    );
1034
    $managers = DB::count();
1035
    DB::query(
1036
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1037
        1
1038
    );
1039
    $readOnly = DB::count();
1040
    // list the languages
1041
    $usedLang = [];
1042
    $tp_languages = DB::query(
1043
        'SELECT name FROM ' . prefixTable('languages')
1044
    );
1045
    foreach ($tp_languages as $tp_language) {
1046
        DB::query(
1047
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1048
            $tp_language['name']
1049
        );
1050
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1051
    }
1052
1053
    // get list of ips
1054
    $usedIp = [];
1055
    $tp_ips = DB::query(
1056
        'SELECT user_ip FROM ' . prefixTable('users')
1057
    );
1058
    foreach ($tp_ips as $ip) {
1059
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1060
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1061
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1062
            $usedIp[$ip['user_ip']] = 1;
1063
        }
1064
    }
1065
1066
    return [
1067
        'error' => '',
1068
        'stat_phpversion' => phpversion(),
1069
        'stat_folders' => $counter_folders,
1070
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1071
        'stat_items' => $counter_items,
1072
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1073
        'stat_users' => $counter_users,
1074
        'stat_admins' => $admins,
1075
        'stat_managers' => $managers,
1076
        'stat_ro' => $readOnly,
1077
        'stat_kb' => $SETTINGS['enable_kb'],
1078
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1079
        'stat_fav' => $SETTINGS['enable_favourites'],
1080
        'stat_teampassversion' => TP_VERSION,
1081
        'stat_ldap' => $SETTINGS['ldap_mode'],
1082
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1083
        'stat_duo' => $SETTINGS['duo'],
1084
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1085
        'stat_api' => $SETTINGS['api'],
1086
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1087
        'stat_syslog' => $SETTINGS['syslog_enable'],
1088
        'stat_2fa' => $SETTINGS['google_authentication'],
1089
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1090
        'stat_mysqlversion' => DB::serverVersion(),
1091
        'stat_languages' => $usedLang,
1092
        'stat_country' => $usedIp,
1093
    ];
1094
}
1095
1096
/**
1097
 * Permits to prepare the way to send the email
1098
 * 
1099
 * @param string $subject       email subject
1100
 * @param string $body          email message
1101
 * @param string $email         email
1102
 * @param string $receiverName  Receiver name
1103
 * @param array  $SETTINGS      settings
1104
 *
1105
 * @return void
1106
 */
1107
function prepareSendingEmail(
1108
    $subject,
1109
    $body,
1110
    $email,
1111
    $receiverName = ''
1112
): void 
1113
{
1114
    DB::insert(
1115
        prefixTable('processes'),
1116
        array(
1117
            'created_at' => time(),
1118
            'process_type' => 'send_email',
1119
            'arguments' => json_encode([
1120
                'subject' => $subject,
1121
                'receivers' => $email,
1122
                'body' => $body,
1123
                'receiver_name' => $receiverName,
1124
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1125
            'updated_at' => '',
1126
            'finished_at' => '',
1127
            'output' => '',
1128
        )
1129
    );
1130
}
1131
1132
/**
1133
 * Permits to send an email.
1134
 *
1135
 * @param string $subject     email subject
1136
 * @param string $textMail    email message
1137
 * @param string $email       email
1138
 * @param array  $SETTINGS    settings
1139
 * @param string $textMailAlt email message alt
1140
 * @param bool   $silent      no errors
1141
 *
1142
 * @return string some json info
1143
 */
1144
function sendEmail(
1145
    $subject,
1146
    $textMail,
1147
    $email,
1148
    $SETTINGS,
1149
    $textMailAlt = null,
1150
    $silent = true,
1151
    $cron = false
1152
) {
1153
    $lang = new Language();
1154
1155
    // CAse where email not defined
1156
    if ($email === 'none' || empty($email) === true) {
1157
        return json_encode(
1158
            [
1159
                'error' => true,
1160
                'message' => $lang->get('forgot_my_pw_email_sent'),
1161
            ]
1162
        );
1163
    }
1164
1165
    // Build and send email
1166
    $email = buildEmail(
1167
        $subject,
1168
        $textMail,
1169
        $email,
1170
        $SETTINGS,
1171
        $textMailAlt = null,
1172
        $silent = true,
1173
        $cron
1174
    );
1175
1176
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1177
        return json_encode(
1178
            [
1179
                'error' => false,
1180
                'message' => $lang->get('forgot_my_pw_email_sent'),
1181
            ]
1182
        );
1183
    }
1184
    // Debug purpose
1185
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1186
        return json_encode(
1187
            [
1188
                'error' => true,
1189
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1190
            ]
1191
        );
1192
    }
1193
    return json_encode(
1194
        [
1195
            'error' => false,
1196
            'message' => $lang->get('share_sent_ok'),
1197
        ]
1198
    );
1199
}
1200
1201
1202
function buildEmail(
1203
    $subject,
1204
    $textMail,
1205
    $email,
1206
    $SETTINGS,
1207
    $textMailAlt = null,
1208
    $silent = true,
1209
    $cron = false
1210
)
1211
{
1212
    // Load PHPMailer
1213
    $mail = new PHPMailer(true);
1214
    $languageDir = $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/';
1215
1216
    try {
1217
        // Set language and SMTPDebug
1218
        $mail->setLanguage('en', $languageDir);
1219
        $mail->SMTPDebug = ($cron || $silent) ? 0 : $SETTINGS['email_debug_level'];
1220
        
1221
        /*
1222
        // Define custom Debug output function
1223
        $mail->Debugoutput = function($str, $level) {
1224
            // Path to your log file
1225
            $logFilePath = '/var/log/phpmailer.log';
1226
            file_put_contents($logFilePath, gmdate('Y-m-d H:i:s'). "\t$level\t$str\n", FILE_APPEND | LOCK_EX);
1227
        };
1228
        */
1229
1230
        // Configure SMTP
1231
        $mail->isSMTP();
1232
        $mail->Host = $SETTINGS['email_smtp_server'];
1233
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1;
1234
        $mail->Username = $SETTINGS['email_auth_username'];
1235
        $mail->Password = $SETTINGS['email_auth_pwd'];
1236
        $mail->Port = (int) $SETTINGS['email_port'];
1237
        $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1238
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none';
1239
        $mail->SMTPOptions = [
1240
            'ssl' => [
1241
                'verify_peer' => false,
1242
                'verify_peer_name' => false,
1243
                'allow_self_signed' => true,
1244
            ],
1245
        ];
1246
1247
        // Set From and FromName
1248
        $mail->From = $SETTINGS['email_from'];
1249
        $mail->FromName = $SETTINGS['email_from_name'];
1250
1251
        // Prepare recipients
1252
        foreach (array_filter(explode(',', $email)) as $dest) {
1253
            $mail->addAddress($dest);
1254
        }
1255
        
1256
        // Prepare HTML and AltBody
1257
        $text_html = emailBody($textMail);
1258
        $mail->WordWrap = 80;
1259
        $mail->isHtml(true);
1260
        $mail->Subject = $subject;
1261
        $mail->Body = $text_html;
1262
        $mail->AltBody = is_null($textMailAlt) ? '' : $textMailAlt;
1263
        
1264
        // Send email
1265
        $mail->send();
1266
        $mail->smtpClose();
1267
        
1268
        return '';
1269
    } catch (Exception $e) {
1270
        if (!$silent || (int) $SETTINGS['email_debug_level'] !== 0) {
1271
            return json_encode([
1272
                'error' => true,
1273
                'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1274
            ]);
1275
        }
1276
        return '';
1277
    }
1278
}
1279
1280
/**
1281
 * Returns the email body.
1282
 *
1283
 * @param string $textMail Text for the email
1284
 */
1285
function emailBody(string $textMail): string
1286
{
1287
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1288
    w3.org/TR/html4/loose.dtd"><html>
1289
    <head><title>Email Template</title>
1290
    <style type="text/css">
1291
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1292
    </style></head>
1293
    <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">
1294
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1295
    <tr><td style="border-collapse: collapse;"><br>
1296
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1297
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1298
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1299
        </td></tr></table></td>
1300
    </tr>
1301
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1302
        <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;">
1303
        <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;">
1304
        <br><div style="float:right;">' .
1305
        $textMail .
1306
        '<br><br></td></tr></table>
1307
    </td></tr></table>
1308
    <br></body></html>';
1309
}
1310
1311
/**
1312
 * Convert date to timestamp.
1313
 *
1314
 * @param string $date        The date
1315
 * @param string $date_format Date format
1316
 *
1317
 * @return int
1318
 */
1319
function dateToStamp(string $date, string $date_format): int
1320
{
1321
    $date = date_parse_from_format($date_format, $date);
1322
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1323
        return mktime(
1324
            empty($date['hour']) === false ? $date['hour'] : 23,
1325
            empty($date['minute']) === false ? $date['minute'] : 59,
1326
            empty($date['second']) === false ? $date['second'] : 59,
1327
            $date['month'],
1328
            $date['day'],
1329
            $date['year']
1330
        );
1331
    }
1332
    return 0;
1333
}
1334
1335
/**
1336
 * Is this a date.
1337
 *
1338
 * @param string $date Date
1339
 *
1340
 * @return bool
1341
 */
1342
function isDate(string $date): bool
1343
{
1344
    return strtotime($date) !== false;
1345
}
1346
1347
/**
1348
 * Check if isUTF8().
1349
 *
1350
 * @param string|array $string Is the string
1351
 *
1352
 * @return int is the string in UTF8 format
1353
 */
1354
function isUTF8($string): int
1355
{
1356
    if (is_array($string) === true) {
1357
        $string = $string['string'];
1358
    }
1359
1360
    return preg_match(
1361
        '%^(?:
1362
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1363
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1364
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1365
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1366
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1367
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1368
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1369
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1370
        )*$%xs',
1371
        $string
1372
    );
1373
}
1374
1375
/**
1376
 * Prepare an array to UTF8 format before JSON_encode.
1377
 *
1378
 * @param array $array Array of values
1379
 *
1380
 * @return array
1381
 */
1382
function utf8Converter(array $array): array
1383
{
1384
    array_walk_recursive(
1385
        $array,
1386
        static function (&$item): void {
1387
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1388
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1389
            }
1390
        }
1391
    );
1392
    return $array;
1393
}
1394
1395
/**
1396
 * Permits to prepare data to be exchanged.
1397
 *
1398
 * @param array|string $data Text
1399
 * @param string       $type Parameter
1400
 * @param string       $key  Optional key
1401
 *
1402
 * @return string|array
1403
 */
1404
function prepareExchangedData($data, string $type, ?string $key = null)
1405
{
1406
    $session = SessionManager::getSession();
1407
    
1408
    // Perform
1409
    if ($type === 'encode' && is_array($data) === true) {
1410
        // Now encode
1411
        return Encryption::encrypt(
1412
            json_encode(
1413
                $data,
1414
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1415
            ),
1416
            $session->get('key')
1417
        );
1418
    }
1419
    if ($type === 'decode' && is_array($data) === false) {
1420
        // check if key exists
1421
        return json_decode(
1422
            (string) Encryption::decrypt(
1423
                (string) $data,
1424
                $session->get('key')
1425
            ),
1426
            true
1427
        );
1428
    }
1429
    return '';
1430
}
1431
1432
1433
/**
1434
 * Create a thumbnail.
1435
 *
1436
 * @param string  $src           Source
1437
 * @param string  $dest          Destination
1438
 * @param int $desired_width Size of width
1439
 * 
1440
 * @return void|string|bool
1441
 */
1442
function makeThumbnail(string $src, string $dest, int $desired_width)
1443
{
1444
    /* read the source image */
1445
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1446
        $source_image = imagecreatefrompng($src);
1447
        if ($source_image === false) {
1448
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1449
        }
1450
    } else {
1451
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1452
    }
1453
1454
    // Get height and width
1455
    $width = imagesx($source_image);
1456
    $height = imagesy($source_image);
1457
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1458
    $desired_height = (int) floor($height * $desired_width / $width);
1459
    /* create a new, "virtual" image */
1460
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1461
    if ($virtual_image === false) {
1462
        return false;
1463
    }
1464
    /* copy source image at a resized size */
1465
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1466
    /* create the physical thumbnail image to its destination */
1467
    imagejpeg($virtual_image, $dest);
1468
}
1469
1470
/**
1471
 * Check table prefix in SQL query.
1472
 *
1473
 * @param string $table Table name
1474
 * 
1475
 * @return string
1476
 */
1477
function prefixTable(string $table): string
1478
{
1479
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1480
    if (empty($safeTable) === false) {
1481
        // sanitize string
1482
        return $safeTable;
1483
    }
1484
    // stop error no table
1485
    return 'table_not_exists';
1486
}
1487
1488
/**
1489
 * GenerateCryptKey
1490
 *
1491
 * @param int     $size      Length
1492
 * @param bool $secure Secure
1493
 * @param bool $numerals Numerics
1494
 * @param bool $uppercase Uppercase letters
1495
 * @param bool $symbols Symbols
1496
 * @param bool $lowercase Lowercase
1497
 * @param array   $SETTINGS  SETTINGS
1498
 * 
1499
 * @return string
1500
 */
1501
function GenerateCryptKey(
1502
    int $size = 20,
1503
    bool $secure = false,
1504
    bool $numerals = false,
1505
    bool $uppercase = false,
1506
    bool $symbols = false,
1507
    bool $lowercase = false,
1508
    array $SETTINGS = []
1509
): string {
1510
    $generator = new ComputerPasswordGenerator();
1511
    $generator->setRandomGenerator(new Php7RandomGenerator());
1512
    
1513
    // Manage size
1514
    $generator->setLength((int) $size);
1515
    if ($secure === true) {
1516
        $generator->setSymbols(true);
1517
        $generator->setLowercase(true);
1518
        $generator->setUppercase(true);
1519
        $generator->setNumbers(true);
1520
    } else {
1521
        $generator->setLowercase($lowercase);
1522
        $generator->setUppercase($uppercase);
1523
        $generator->setNumbers($numerals);
1524
        $generator->setSymbols($symbols);
1525
    }
1526
1527
    return $generator->generatePasswords()[0];
1528
}
1529
1530
/**
1531
 * Send sysLOG message
1532
 *
1533
 * @param string    $message
1534
 * @param string    $host
1535
 * @param int       $port
1536
 * @param string    $component
1537
 * 
1538
 * @return void
1539
*/
1540
function send_syslog($message, $host, $port, $component = 'teampass'): void
1541
{
1542
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1543
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1544
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1545
    socket_close($sock);
1546
}
1547
1548
/**
1549
 * Permits to log events into DB
1550
 *
1551
 * @param array  $SETTINGS Teampass settings
1552
 * @param string $type     Type
1553
 * @param string $label    Label
1554
 * @param string $who      Who
1555
 * @param string $login    Login
1556
 * @param string|int $field_1  Field
1557
 * 
1558
 * @return void
1559
 */
1560
function logEvents(
1561
    array $SETTINGS, 
1562
    string $type, 
1563
    string $label, 
1564
    string $who, 
1565
    ?string $login = null, 
1566
    $field_1 = null
1567
): void
1568
{
1569
    if (empty($who)) {
1570
        $who = getClientIpServer();
1571
    }
1572
1573
    // Load class DB
1574
    loadClasses('DB');
1575
1576
    DB::insert(
1577
        prefixTable('log_system'),
1578
        [
1579
            'type' => $type,
1580
            'date' => time(),
1581
            'label' => $label,
1582
            'qui' => $who,
1583
            'field_1' => $field_1 === null ? '' : $field_1,
1584
        ]
1585
    );
1586
    // If SYSLOG
1587
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1588
        if ($type === 'user_mngt') {
1589
            send_syslog(
1590
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1591
                $SETTINGS['syslog_host'],
1592
                $SETTINGS['syslog_port'],
1593
                'teampass'
1594
            );
1595
        } else {
1596
            send_syslog(
1597
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1598
                $SETTINGS['syslog_host'],
1599
                $SETTINGS['syslog_port'],
1600
                'teampass'
1601
            );
1602
        }
1603
    }
1604
}
1605
1606
/**
1607
 * Log events.
1608
 *
1609
 * @param array  $SETTINGS        Teampass settings
1610
 * @param int    $item_id         Item id
1611
 * @param string $item_label      Item label
1612
 * @param int    $id_user         User id
1613
 * @param string $action          Code for reason
1614
 * @param string $login           User login
1615
 * @param string $raison          Code for reason
1616
 * @param string $encryption_type Encryption on
1617
 * @param string $time Encryption Time
1618
 * @param string $old_value       Old value
1619
 * 
1620
 * @return void
1621
 */
1622
function logItems(
1623
    array $SETTINGS,
1624
    int $item_id,
1625
    string $item_label,
1626
    int $id_user,
1627
    string $action,
1628
    ?string $login = null,
1629
    ?string $raison = null,
1630
    ?string $encryption_type = null,
1631
    ?string $time = null,
1632
    ?string $old_value = null
1633
): void {
1634
    // Load class DB
1635
    loadClasses('DB');
1636
1637
    // Insert log in DB
1638
    DB::insert(
1639
        prefixTable('log_items'),
1640
        [
1641
            'id_item' => $item_id,
1642
            'date' => is_null($time) === true ? time() : $time,
1643
            'id_user' => $id_user,
1644
            'action' => $action,
1645
            'raison' => $raison,
1646
            'old_value' => $old_value,
1647
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1648
        ]
1649
    );
1650
    // Timestamp the last change
1651
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1652
        DB::update(
1653
            prefixTable('misc'),
1654
            [
1655
                'valeur' => time(),
1656
            ],
1657
            'type = %s AND intitule = %s',
1658
            'timestamp',
1659
            'last_item_change'
1660
        );
1661
    }
1662
1663
    // SYSLOG
1664
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1665
        // Extract reason
1666
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1667
        // Get item info if not known
1668
        if (empty($item_label) === true) {
1669
            $dataItem = DB::queryfirstrow(
1670
                'SELECT id, id_tree, label
1671
                FROM ' . prefixTable('items') . '
1672
                WHERE id = %i',
1673
                $item_id
1674
            );
1675
            $item_label = $dataItem['label'];
1676
        }
1677
1678
        send_syslog(
1679
            'action=' . str_replace('at_', '', $action) .
1680
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1681
                ' itemno=' . $item_id .
1682
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1683
                ' itemname="' . addslashes($item_label) . '"',
1684
            $SETTINGS['syslog_host'],
1685
            $SETTINGS['syslog_port'],
1686
            'teampass'
1687
        );
1688
    }
1689
1690
    // send notification if enabled
1691
    //notifyOnChange($item_id, $action, $SETTINGS);
1692
}
1693
1694
/**
1695
 * Prepare notification email to subscribers.
1696
 *
1697
 * @param int    $item_id  Item id
1698
 * @param string $label    Item label
1699
 * @param array  $changes  List of changes
1700
 * @param array  $SETTINGS Teampass settings
1701
 * 
1702
 * @return void
1703
 */
1704
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1705
{
1706
    $session = SessionManager::getSession();
1707
    $lang = new Language();
1708
    $globalsUserId = $session->get('user-id');
1709
    $globalsLastname = $session->get('user-lastname');
1710
    $globalsName = $session->get('user-name');
1711
    // send email to user that what to be notified
1712
    $notification = DB::queryOneColumn(
1713
        'email',
1714
        'SELECT *
1715
        FROM ' . prefixTable('notification') . ' AS n
1716
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1717
        WHERE n.item_id = %i AND n.user_id != %i',
1718
        $item_id,
1719
        $globalsUserId
1720
    );
1721
    if (DB::count() > 0) {
1722
        // Prepare path
1723
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1724
        // Get list of changes
1725
        $htmlChanges = '<ul>';
1726
        foreach ($changes as $change) {
1727
            $htmlChanges .= '<li>' . $change . '</li>';
1728
        }
1729
        $htmlChanges .= '</ul>';
1730
        // send email
1731
        DB::insert(
1732
            prefixTable('emails'),
1733
            [
1734
                'timestamp' => time(),
1735
                'subject' => $lang->get('email_subject_item_updated'),
1736
                'body' => str_replace(
1737
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1738
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1739
                    $lang->get('email_body_item_updated')
1740
                ),
1741
                'receivers' => implode(',', $notification),
1742
                'status' => '',
1743
            ]
1744
        );
1745
    }
1746
}
1747
1748
/**
1749
 * Returns the Item + path.
1750
 *
1751
 * @param int    $id_tree  Node id
1752
 * @param string $label    Label
1753
 * @param array  $SETTINGS TP settings
1754
 * 
1755
 * @return string
1756
 */
1757
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1758
{
1759
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1760
    $arbo = $tree->getPath($id_tree, true);
1761
    $path = '';
1762
    foreach ($arbo as $elem) {
1763
        if (empty($path) === true) {
1764
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1765
        } else {
1766
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1767
        }
1768
    }
1769
1770
    // Build text to show user
1771
    if (empty($label) === false) {
1772
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1773
    }
1774
    return empty($path) === true ? '' : $path;
1775
}
1776
1777
/**
1778
 * Get the client ip address.
1779
 *
1780
 * @return string IP address
1781
 */
1782
function getClientIpServer(): string
1783
{
1784
    if (getenv('HTTP_CLIENT_IP')) {
1785
        $ipaddress = getenv('HTTP_CLIENT_IP');
1786
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1787
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1788
    } elseif (getenv('HTTP_X_FORWARDED')) {
1789
        $ipaddress = getenv('HTTP_X_FORWARDED');
1790
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1791
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1792
    } elseif (getenv('HTTP_FORWARDED')) {
1793
        $ipaddress = getenv('HTTP_FORWARDED');
1794
    } elseif (getenv('REMOTE_ADDR')) {
1795
        $ipaddress = getenv('REMOTE_ADDR');
1796
    } else {
1797
        $ipaddress = 'UNKNOWN';
1798
    }
1799
1800
    return $ipaddress;
1801
}
1802
1803
/**
1804
 * Escape all HTML, JavaScript, and CSS.
1805
 *
1806
 * @param string $input    The input string
1807
 * @param string $encoding Which character encoding are we using?
1808
 * 
1809
 * @return string
1810
 */
1811
function noHTML(string $input, string $encoding = 'UTF-8'): string
1812
{
1813
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1814
}
1815
1816
/**
1817
 * Permits to handle the Teampass config file
1818
 * $action accepts "rebuild" and "update"
1819
 *
1820
 * @param string $action   Action to perform
1821
 * @param array  $SETTINGS Teampass settings
1822
 * @param string $field    Field to refresh
1823
 * @param string $value    Value to set
1824
 *
1825
 * @return string|bool
1826
 */
1827
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1828
{
1829
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1830
1831
    // Load class DB
1832
    loadClasses('DB');
1833
1834
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1835
        // perform a copy
1836
        if (file_exists($tp_config_file)) {
1837
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1838
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1839
            }
1840
        }
1841
1842
        // regenerate
1843
        $data = [];
1844
        $data[0] = "<?php\n";
1845
        $data[1] = "global \$SETTINGS;\n";
1846
        $data[2] = "\$SETTINGS = array (\n";
1847
        $rows = DB::query(
1848
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1849
            'admin'
1850
        );
1851
        foreach ($rows as $record) {
1852
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
1853
        }
1854
        array_push($data, ");\n");
1855
        $data = array_unique($data);
1856
    // ---
1857
    } elseif ($action === 'update' && empty($field) === false) {
1858
        $data = file($tp_config_file);
1859
        $inc = 0;
1860
        $bFound = false;
1861
        foreach ($data as $line) {
1862
            if (stristr($line, ');')) {
1863
                break;
1864
            }
1865
1866
            if (stristr($line, "'" . $field . "' => '")) {
1867
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
1868
                $bFound = true;
1869
                break;
1870
            }
1871
            ++$inc;
1872
        }
1873
        if ($bFound === false) {
1874
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
1875
        }
1876
    }
1877
1878
    // update file
1879
    file_put_contents($tp_config_file, implode('', $data ?? []));
1880
    return true;
1881
}
1882
1883
/**
1884
 * Permits to replace &#92; to permit correct display
1885
 *
1886
 * @param string $input Some text
1887
 * 
1888
 * @return string
1889
 */
1890
function handleBackslash(string $input): string
1891
{
1892
    return str_replace('&amp;#92;', '&#92;', $input);
1893
}
1894
1895
/**
1896
 * Permits to load settings
1897
 * 
1898
 * @return void
1899
*/
1900
function loadSettings(): void
1901
{
1902
    global $SETTINGS;
1903
    /* LOAD CPASSMAN SETTINGS */
1904
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1905
        $SETTINGS = [];
1906
        $SETTINGS['duplicate_folder'] = 0;
1907
        //by default, this is set to 0;
1908
        $SETTINGS['duplicate_item'] = 0;
1909
        //by default, this is set to 0;
1910
        $SETTINGS['number_of_used_pw'] = 5;
1911
        //by default, this value is set to 5;
1912
        $settings = [];
1913
        $rows = DB::query(
1914
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1915
            [
1916
                'type' => 'admin',
1917
                'type2' => 'settings',
1918
            ]
1919
        );
1920
        foreach ($rows as $record) {
1921
            if ($record['type'] === 'admin') {
1922
                $SETTINGS[$record['intitule']] = $record['valeur'];
1923
            } else {
1924
                $settings[$record['intitule']] = $record['valeur'];
1925
            }
1926
        }
1927
        $SETTINGS['loaded'] = 1;
1928
        $SETTINGS['default_session_expiration_time'] = 5;
1929
    }
1930
}
1931
1932
/**
1933
 * check if folder has custom fields.
1934
 * Ensure that target one also has same custom fields
1935
 * 
1936
 * @param int $source_id
1937
 * @param int $target_id 
1938
 * 
1939
 * @return bool
1940
*/
1941
function checkCFconsistency(int $source_id, int $target_id): bool
1942
{
1943
    $source_cf = [];
1944
    $rows = DB::QUERY(
1945
        'SELECT id_category
1946
            FROM ' . prefixTable('categories_folders') . '
1947
            WHERE id_folder = %i',
1948
        $source_id
1949
    );
1950
    foreach ($rows as $record) {
1951
        array_push($source_cf, $record['id_category']);
1952
    }
1953
1954
    $target_cf = [];
1955
    $rows = DB::QUERY(
1956
        'SELECT id_category
1957
            FROM ' . prefixTable('categories_folders') . '
1958
            WHERE id_folder = %i',
1959
        $target_id
1960
    );
1961
    foreach ($rows as $record) {
1962
        array_push($target_cf, $record['id_category']);
1963
    }
1964
1965
    $cf_diff = array_diff($source_cf, $target_cf);
1966
    if (count($cf_diff) > 0) {
1967
        return false;
1968
    }
1969
1970
    return true;
1971
}
1972
1973
/**
1974
 * Will encrypte/decrypt a fil eusing Defuse.
1975
 *
1976
 * @param string $type        can be either encrypt or decrypt
1977
 * @param string $source_file path to source file
1978
 * @param string $target_file path to target file
1979
 * @param array  $SETTINGS    Settings
1980
 * @param string $password    A password
1981
 *
1982
 * @return string|bool
1983
 */
1984
function prepareFileWithDefuse(
1985
    string $type,
1986
    string $source_file,
1987
    string $target_file,
1988
    array $SETTINGS,
1989
    string $password = null
1990
) {
1991
    // Load AntiXSS
1992
    $antiXss = new AntiXSS();
1993
    // Protect against bad inputs
1994
    if (is_array($source_file) === true || is_array($target_file) === true) {
1995
        return 'error_cannot_be_array';
1996
    }
1997
1998
    // Sanitize
1999
    $source_file = $antiXss->xss_clean($source_file);
2000
    $target_file = $antiXss->xss_clean($target_file);
2001
    if (empty($password) === true || is_null($password) === true) {
2002
        // get KEY to define password
2003
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2004
        $password = Key::loadFromAsciiSafeString($ascii_key);
2005
    }
2006
2007
    $err = '';
2008
    if ($type === 'decrypt') {
2009
        // Decrypt file
2010
        $err = defuseFileDecrypt(
2011
            $source_file,
2012
            $target_file,
2013
            $SETTINGS, /** @scrutinizer ignore-type */
2014
            $password
2015
        );
2016
    } elseif ($type === 'encrypt') {
2017
        // Encrypt file
2018
        $err = defuseFileEncrypt(
2019
            $source_file,
2020
            $target_file,
2021
            $SETTINGS, /** @scrutinizer ignore-type */
2022
            $password
2023
        );
2024
    }
2025
2026
    // return error
2027
    return $err === true ? $err : '';
2028
}
2029
2030
/**
2031
 * Encrypt a file with Defuse.
2032
 *
2033
 * @param string $source_file path to source file
2034
 * @param string $target_file path to target file
2035
 * @param array  $SETTINGS    Settings
2036
 * @param string $password    A password
2037
 *
2038
 * @return string|bool
2039
 */
2040
function defuseFileEncrypt(
2041
    string $source_file,
2042
    string $target_file,
2043
    array $SETTINGS,
2044
    string $password = null
2045
) {
2046
    try {
2047
        CryptoFile::encryptFileWithPassword(
2048
            $source_file,
2049
            $target_file,
2050
            $password
2051
        );
2052
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2053
        $err = 'wrong_key';
2054
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2055
        $err = $ex;
2056
    } catch (CryptoException\IOException $ex) {
2057
        $err = $ex;
2058
    }
2059
2060
    // return error
2061
    return empty($err) === false ? $err : true;
2062
}
2063
2064
/**
2065
 * Decrypt a file with Defuse.
2066
 *
2067
 * @param string $source_file path to source file
2068
 * @param string $target_file path to target file
2069
 * @param array  $SETTINGS    Settings
2070
 * @param string $password    A password
2071
 *
2072
 * @return string|bool
2073
 */
2074
function defuseFileDecrypt(
2075
    string $source_file,
2076
    string $target_file,
2077
    array $SETTINGS,
2078
    string $password = null
2079
) {
2080
    try {
2081
        CryptoFile::decryptFileWithPassword(
2082
            $source_file,
2083
            $target_file,
2084
            $password
2085
        );
2086
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2087
        $err = 'wrong_key';
2088
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2089
        $err = $ex;
2090
    } catch (CryptoException\IOException $ex) {
2091
        $err = $ex;
2092
    }
2093
2094
    // return error
2095
    return empty($err) === false ? $err : true;
2096
}
2097
2098
/*
2099
* NOT TO BE USED
2100
*/
2101
/**
2102
 * Undocumented function.
2103
 *
2104
 * @param string $text Text to debug
2105
 */
2106
function debugTeampass(string $text): void
2107
{
2108
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2109
    if ($debugFile !== false) {
2110
        fputs($debugFile, $text);
2111
        fclose($debugFile);
2112
    }
2113
}
2114
2115
/**
2116
 * DELETE the file with expected command depending on server type.
2117
 *
2118
 * @param string $file     Path to file
2119
 * @param array  $SETTINGS Teampass settings
2120
 *
2121
 * @return void
2122
 */
2123
function fileDelete(string $file, array $SETTINGS): void
2124
{
2125
    // Load AntiXSS
2126
    $antiXss = new AntiXSS();
2127
    $file = $antiXss->xss_clean($file);
2128
    if (is_file($file)) {
2129
        unlink($file);
2130
    }
2131
}
2132
2133
/**
2134
 * Permits to extract the file extension.
2135
 *
2136
 * @param string $file File name
2137
 *
2138
 * @return string
2139
 */
2140
function getFileExtension(string $file): string
2141
{
2142
    if (strpos($file, '.') === false) {
2143
        return $file;
2144
    }
2145
2146
    return substr($file, strrpos($file, '.') + 1);
2147
}
2148
2149
/**
2150
 * Chmods files and folders with different permissions.
2151
 *
2152
 * This is an all-PHP alternative to using: \n
2153
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2154
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2155
 *
2156
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2157
  *
2158
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2159
 * @param int    $filePerm The permissions any found files should get.
2160
 * @param int    $dirPerm  The permissions any found folder should get.
2161
 *
2162
 * @return bool Returns TRUE if the path if found and FALSE if not.
2163
 *
2164
 * @warning The permission levels has to be entered in octal format, which
2165
 * normally means adding a zero ("0") in front of the permission level. \n
2166
 * More info at: http://php.net/chmod.
2167
*/
2168
2169
function recursiveChmod(
2170
    string $path,
2171
    int $filePerm = 0644,
2172
    int  $dirPerm = 0755
2173
) {
2174
    // Check if the path exists
2175
    if (! file_exists($path)) {
2176
        return false;
2177
    }
2178
2179
    // See whether this is a file
2180
    if (is_file($path)) {
2181
        // Chmod the file with our given filepermissions
2182
        try {
2183
            chmod($path, $filePerm);
2184
        } catch (Exception $e) {
2185
            return false;
2186
        }
2187
    // If this is a directory...
2188
    } elseif (is_dir($path)) {
2189
        // Then get an array of the contents
2190
        $foldersAndFiles = scandir($path);
2191
        // Remove "." and ".." from the list
2192
        $entries = array_slice($foldersAndFiles, 2);
2193
        // Parse every result...
2194
        foreach ($entries as $entry) {
2195
            // And call this function again recursively, with the same permissions
2196
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2197
        }
2198
2199
        // When we are done with the contents of the directory, we chmod the directory itself
2200
        try {
2201
            chmod($path, $filePerm);
2202
        } catch (Exception $e) {
2203
            return false;
2204
        }
2205
    }
2206
2207
    // Everything seemed to work out well, return true
2208
    return true;
2209
}
2210
2211
/**
2212
 * Check if user can access to this item.
2213
 *
2214
 * @param int   $item_id ID of item
2215
 * @param array $SETTINGS
2216
 *
2217
 * @return bool|string
2218
 */
2219
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2220
{
2221
    
2222
    $session = SessionManager::getSession();
2223
    $session_groupes_visibles = $session->get('user-accessible_folders');
2224
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2225
    // Load item data
2226
    $data = DB::queryFirstRow(
2227
        'SELECT id_tree
2228
        FROM ' . prefixTable('items') . '
2229
        WHERE id = %i',
2230
        $item_id
2231
    );
2232
    // Check if user can access this folder
2233
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2234
        // Now check if this folder is restricted to user
2235
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2236
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2237
        ) {
2238
            return 'ERR_FOLDER_NOT_ALLOWED';
2239
        }
2240
    }
2241
2242
    return true;
2243
}
2244
2245
/**
2246
 * Creates a unique key.
2247
 *
2248
 * @param int $lenght Key lenght
2249
 *
2250
 * @return string
2251
 */
2252
function uniqidReal(int $lenght = 13): string
2253
{
2254
    if (function_exists('random_bytes')) {
2255
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2256
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2257
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2258
    } else {
2259
        throw new Exception('no cryptographically secure random function available');
2260
    }
2261
2262
    return substr(bin2hex($bytes), 0, $lenght);
2263
}
2264
2265
/**
2266
 * Obfuscate an email.
2267
 *
2268
 * @param string $email Email address
2269
 *
2270
 * @return string
2271
 */
2272
function obfuscateEmail(string $email): string
2273
{
2274
    $email = explode("@", $email);
2275
    $name = $email[0];
2276
    if (strlen($name) > 3) {
2277
        $name = substr($name, 0, 2);
2278
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2279
            $name .= "*";
2280
        }
2281
        $name .= substr($email[0], -1, 1);
2282
    }
2283
    $host = explode(".", $email[1])[0];
2284
    if (strlen($host) > 3) {
2285
        $host = substr($host, 0, 1);
2286
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2287
            $host .= "*";
2288
        }
2289
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2290
    }
2291
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2292
    return $email;
2293
}
2294
2295
/**
2296
 * Perform a Query.
2297
 *
2298
 * @param array  $SETTINGS Teamapss settings
2299
 * @param string $fields   Fields to use
2300
 * @param string $table    Table to use
2301
 *
2302
 * @return array
2303
 */
2304
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2305
{
2306
    // include librairies & connect to DB
2307
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2308
2309
    // Load class DB
2310
    loadClasses('DB');
2311
    
2312
    // Insert log in DB
2313
    return DB::query(
2314
        'SELECT ' . $fields . '
2315
        FROM ' . prefixTable($table)
2316
    );
2317
}
2318
2319
/**
2320
 * Undocumented function.
2321
 *
2322
 * @param int $bytes Size of file
2323
 *
2324
 * @return string
2325
 */
2326
function formatSizeUnits(int $bytes): string
2327
{
2328
    if ($bytes >= 1073741824) {
2329
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2330
    } elseif ($bytes >= 1048576) {
2331
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2332
    } elseif ($bytes >= 1024) {
2333
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2334
    } elseif ($bytes > 1) {
2335
        $bytes .= ' bytes';
2336
    } elseif ($bytes === 1) {
2337
        $bytes .= ' byte';
2338
    } else {
2339
        $bytes = '0 bytes';
2340
    }
2341
2342
    return $bytes;
2343
}
2344
2345
/**
2346
 * Generate user pair of keys.
2347
 *
2348
 * @param string $userPwd User password
2349
 *
2350
 * @return array
2351
 */
2352
function generateUserKeys(string $userPwd): array
2353
{
2354
    // Sanitize
2355
    $antiXss = new AntiXSS();
2356
    $userPwd = $antiXss->xss_clean($userPwd);
2357
    // Load classes
2358
    $rsa = new Crypt_RSA();
2359
    $cipher = new Crypt_AES();
2360
    // Create the private and public key
2361
    $res = $rsa->createKey(4096);
2362
    // Encrypt the privatekey
2363
    $cipher->setPassword($userPwd);
2364
    $privatekey = $cipher->encrypt($res['privatekey']);
2365
    return [
2366
        'private_key' => base64_encode($privatekey),
2367
        'public_key' => base64_encode($res['publickey']),
2368
        'private_key_clear' => base64_encode($res['privatekey']),
2369
    ];
2370
}
2371
2372
/**
2373
 * Permits to decrypt the user's privatekey.
2374
 *
2375
 * @param string $userPwd        User password
2376
 * @param string $userPrivateKey User private key
2377
 *
2378
 * @return string|object
2379
 */
2380
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2381
{
2382
    // Sanitize
2383
    $antiXss = new AntiXSS();
2384
    $userPwd = $antiXss->xss_clean($userPwd);
2385
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2386
2387
    if (empty($userPwd) === false) {
2388
        // Load classes
2389
        $cipher = new Crypt_AES();
2390
        // Encrypt the privatekey
2391
        $cipher->setPassword($userPwd);
2392
        try {
2393
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2394
        } catch (Exception $e) {
2395
            return $e;
2396
        }
2397
    }
2398
    return '';
2399
}
2400
2401
/**
2402
 * Permits to encrypt the user's privatekey.
2403
 *
2404
 * @param string $userPwd        User password
2405
 * @param string $userPrivateKey User private key
2406
 *
2407
 * @return string
2408
 */
2409
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2410
{
2411
    // Sanitize
2412
    $antiXss = new AntiXSS();
2413
    $userPwd = $antiXss->xss_clean($userPwd);
2414
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2415
2416
    if (empty($userPwd) === false) {
2417
        // Load classes
2418
        $cipher = new Crypt_AES();
2419
        // Encrypt the privatekey
2420
        $cipher->setPassword($userPwd);        
2421
        try {
2422
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2423
        } catch (Exception $e) {
2424
            return $e;
2425
        }
2426
    }
2427
    return '';
2428
}
2429
2430
/**
2431
 * Encrypts a string using AES.
2432
 *
2433
 * @param string $data String to encrypt
2434
 * @param string $key
2435
 *
2436
 * @return array
2437
 */
2438
function doDataEncryption(string $data, string $key = NULL): array
2439
{
2440
    // Sanitize
2441
    $antiXss = new AntiXSS();
2442
    $data = $antiXss->xss_clean($data);
2443
    
2444
    // Load classes
2445
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2446
    // Generate an object key
2447
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2448
    // Set it as password
2449
    $cipher->setPassword($objectKey);
2450
    return [
2451
        'encrypted' => base64_encode($cipher->encrypt($data)),
2452
        'objectKey' => base64_encode($objectKey),
2453
    ];
2454
}
2455
2456
/**
2457
 * Decrypts a string using AES.
2458
 *
2459
 * @param string $data Encrypted data
2460
 * @param string $key  Key to uncrypt
2461
 *
2462
 * @return string
2463
 */
2464
function doDataDecryption(string $data, string $key): string
2465
{
2466
    // Sanitize
2467
    $antiXss = new AntiXSS();
2468
    $data = $antiXss->xss_clean($data);
2469
    $key = $antiXss->xss_clean($key);
2470
2471
    // Load classes
2472
    $cipher = new Crypt_AES();
2473
    // Set the object key
2474
    $cipher->setPassword(base64_decode($key));
2475
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2476
}
2477
2478
/**
2479
 * Encrypts using RSA a string using a public key.
2480
 *
2481
 * @param string $key       Key to be encrypted
2482
 * @param string $publicKey User public key
2483
 *
2484
 * @return string
2485
 */
2486
function encryptUserObjectKey(string $key, string $publicKey): string
2487
{
2488
    // Sanitize
2489
    $antiXss = new AntiXSS();
2490
    $publicKey = $antiXss->xss_clean($publicKey);
2491
    // Load classes
2492
    $rsa = new Crypt_RSA();
2493
    // Load the public key
2494
    $decodedPublicKey = base64_decode($publicKey, true);
2495
    if ($decodedPublicKey === false) {
2496
        throw new InvalidArgumentException("Error while decoding key.");
2497
    }
2498
    $rsa->loadKey($decodedPublicKey);
2499
    // Encrypt
2500
    $encrypted = $rsa->encrypt(base64_decode($key));
2501
    if ($encrypted === false) {
0 ignored issues
show
introduced by
The condition $encrypted === false is always false.
Loading history...
2502
        throw new RuntimeException("Error while encrypting key.");
2503
    }
2504
    // Return
2505
    return base64_encode($encrypted);
2506
}
2507
2508
/**
2509
 * Decrypts using RSA an encrypted string using a private key.
2510
 *
2511
 * @param string $key        Encrypted key
2512
 * @param string $privateKey User private key
2513
 *
2514
 * @return string
2515
 */
2516
function decryptUserObjectKey(string $key, string $privateKey): string
2517
{
2518
    // Sanitize
2519
    $antiXss = new AntiXSS();
2520
    $privateKey = $antiXss->xss_clean($privateKey);
2521
2522
    // Load classes
2523
    $rsa = new Crypt_RSA();
2524
    // Load the private key
2525
    $decodedPrivateKey = base64_decode($privateKey, true);
2526
    if ($decodedPrivateKey === false) {
2527
        throw new InvalidArgumentException("Error while decoding private key.");
2528
    }
2529
2530
    $rsa->loadKey($decodedPrivateKey);
2531
2532
    // Decrypt
2533
    try {
2534
        $decodedKey = base64_decode($key, true);
2535
        if ($decodedKey === false) {
2536
            throw new InvalidArgumentException("Error while decoding key.");
2537
        }
2538
2539
        $tmpValue = $rsa->decrypt($decodedKey);
2540
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2541
            return base64_encode($tmpValue);
2542
        } else {
2543
            return '';
2544
        }
2545
    } catch (Exception $e) {
2546
        error_log('TEAMPASS Error - ldap - '.$e->getMessage());
2547
        return 'Exception: could not decrypt object';
2548
    }
2549
}
2550
2551
/**
2552
 * Encrypts a file.
2553
 *
2554
 * @param string $fileInName File name
2555
 * @param string $fileInPath Path to file
2556
 *
2557
 * @return array
2558
 */
2559
function encryptFile(string $fileInName, string $fileInPath): array
2560
{
2561
    if (defined('FILE_BUFFER_SIZE') === false) {
2562
        define('FILE_BUFFER_SIZE', 128 * 1024);
2563
    }
2564
2565
    // Load classes
2566
    $cipher = new Crypt_AES();
2567
2568
    // Generate an object key
2569
    $objectKey = uniqidReal(32);
2570
    // Set it as password
2571
    $cipher->setPassword($objectKey);
2572
    // Prevent against out of memory
2573
    $cipher->enableContinuousBuffer();
2574
2575
    // Encrypt the file content
2576
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2577
    $fileContent = file_get_contents($filePath);
2578
    $plaintext = $fileContent;
2579
    $ciphertext = $cipher->encrypt($plaintext);
2580
2581
    // Save new file
2582
    // deepcode ignore InsecureHash: is simply used to get a unique name
2583
    $hash = md5($plaintext);
2584
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2585
    file_put_contents($fileOut, $ciphertext);
2586
    unlink($fileInPath . '/' . $fileInName);
2587
    return [
2588
        'fileHash' => base64_encode($hash),
2589
        'objectKey' => base64_encode($objectKey),
2590
    ];
2591
}
2592
2593
/**
2594
 * Decrypt a file.
2595
 *
2596
 * @param string $fileName File name
2597
 * @param string $filePath Path to file
2598
 * @param string $key      Key to use
2599
 *
2600
 * @return string
2601
 */
2602
function decryptFile(string $fileName, string $filePath, string $key): string
2603
{
2604
    if (! defined('FILE_BUFFER_SIZE')) {
2605
        define('FILE_BUFFER_SIZE', 128 * 1024);
2606
    }
2607
    
2608
    // Load classes
2609
    $cipher = new Crypt_AES();
2610
    $antiXSS = new AntiXSS();
2611
    
2612
    // Get file name
2613
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2614
2615
    // Set the object key
2616
    $cipher->setPassword(base64_decode($key));
2617
    // Prevent against out of memory
2618
    $cipher->enableContinuousBuffer();
2619
    $cipher->disablePadding();
2620
    // Get file content
2621
    $safeFilePath = realpath($filePath . '/' . TP_FILE_PREFIX . $safeFileName);
2622
    $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2623
2624
    if (WIP) error_log('DEBUG: File image url -> '.filter_var($safeFilePath, FILTER_SANITIZE_URL));
2625
2626
    // Decrypt file content and return
2627
    return base64_encode($cipher->decrypt($ciphertext));
2628
}
2629
2630
/**
2631
 * Generate a simple password
2632
 *
2633
 * @param int $length Length of string
2634
 * @param bool $symbolsincluded Allow symbols
2635
 *
2636
 * @return string
2637
 */
2638
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2639
{
2640
    // Generate new user password
2641
    $small_letters = range('a', 'z');
2642
    $big_letters = range('A', 'Z');
2643
    $digits = range(0, 9);
2644
    $symbols = $symbolsincluded === true ?
2645
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2646
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2647
    $count = count($res);
2648
    // first variant
2649
2650
    $random_string = '';
2651
    for ($i = 0; $i < $length; ++$i) {
2652
        $random_string .= $res[random_int(0, $count - 1)];
2653
    }
2654
2655
    return $random_string;
2656
}
2657
2658
/**
2659
 * Permit to store the sharekey of an object for users.
2660
 *
2661
 * @param string $object_name             Type for table selection
2662
 * @param int    $post_folder_is_personal Personal
2663
 * @param int    $post_folder_id          Folder
2664
 * @param int    $post_object_id          Object
2665
 * @param string $objectKey               Object key
2666
 * @param array  $SETTINGS                Teampass settings
2667
 * @param int    $user_id                 User ID if needed
2668
 * @param bool   $onlyForUser                 User ID if needed
2669
 * @param bool   $deleteAll                 User ID if needed
2670
 * @param array  $objectKeyArray                 User ID if needed
2671
 *
2672
 * @return void
2673
 */
2674
function storeUsersShareKey(
2675
    string $object_name,
2676
    int $post_folder_is_personal,
2677
    int $post_folder_id,
2678
    int $post_object_id,
2679
    string $objectKey,
2680
    bool $onlyForUser = false,
2681
    bool $deleteAll = true,
2682
    array $objectKeyArray = [],
2683
    int $all_users_except_id = -1
2684
): void {
2685
    
2686
    $session = SessionManager::getSession();
2687
    loadClasses('DB');
2688
2689
    // Delete existing entries for this object
2690
    if ($deleteAll === true) {
2691
        DB::delete(
2692
            $object_name,
2693
            'object_id = %i',
2694
            $post_object_id
2695
        );
2696
    }
2697
    
2698
    if (
2699
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2700
    ) {
2701
        // Only create the sharekey for a user
2702
        $user = DB::queryFirstRow(
2703
            'SELECT public_key
2704
            FROM ' . prefixTable('users') . '
2705
            WHERE id = ' . (int) $session->get('user-id') . '
2706
            AND public_key != ""'
2707
        );
2708
2709
        if (empty($objectKey) === false) {
2710
            DB::insert(
2711
                $object_name,
2712
                [
2713
                    'object_id' => (int) $post_object_id,
2714
                    'user_id' => (int) $session->get('user-id'),
2715
                    'share_key' => encryptUserObjectKey(
2716
                        $objectKey,
2717
                        $user['public_key']
2718
                    ),
2719
                ]
2720
            );
2721
        } else if (count($objectKeyArray) > 0) {
2722
            foreach ($objectKeyArray as $object) {
2723
                DB::insert(
2724
                    $object_name,
2725
                    [
2726
                        'object_id' => (int) $object['objectId'],
2727
                        'user_id' => (int) $session->get('user-id'),
2728
                        'share_key' => encryptUserObjectKey(
2729
                            $object['objectKey'],
2730
                            $user['public_key']
2731
                        ),
2732
                    ]
2733
                );
2734
            }
2735
        }
2736
    } else {
2737
        // Create sharekey for each user
2738
        //error_log('Building QUERY - all_users_except_id: '. $all_users_except_id);
2739
        //DB::debugmode(true);
2740
        $users = DB::query(
2741
            'SELECT id, public_key
2742
            FROM ' . prefixTable('users') . '
2743
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2744
                'id IN ("' . TP_USER_ID . '","' . $session->get('user-id') . '") ' : 
2745
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '"'.($all_users_except_id === -1 ? '' : ', "'.$all_users_except_id.'"').') ') . '
2746
            AND public_key != ""'
2747
        );
2748
        //DB::debugmode(false);
2749
        foreach ($users as $user) {
2750
            // Insert in DB the new object key for this item by user
2751
            if (count($objectKeyArray) === 0) {
2752
                //error_log('TEAMPASS Error - storeUsersShareKey - ' . $object_name . ' - ' . $post_object_id . ' - ' . $user['id'] . ' - ' . $objectKey);
2753
                DB::insert(
2754
                    $object_name,
2755
                    [
2756
                        'object_id' => $post_object_id,
2757
                        'user_id' => (int) $user['id'],
2758
                        'share_key' => encryptUserObjectKey(
2759
                            $objectKey,
2760
                            $user['public_key']
2761
                        ),
2762
                    ]
2763
                );
2764
            } else {
2765
                foreach ($objectKeyArray as $object) {
2766
                    DB::insert(
2767
                        $object_name,
2768
                        [
2769
                            'object_id' => (int) $object['objectId'],
2770
                            'user_id' => (int) $user['id'],
2771
                            'share_key' => encryptUserObjectKey(
2772
                                $object['objectKey'],
2773
                                $user['public_key']
2774
                            ),
2775
                        ]
2776
                    );
2777
                }
2778
            }
2779
        }
2780
    }
2781
}
2782
2783
/**
2784
 * Is this string base64 encoded?
2785
 *
2786
 * @param string $str Encoded string?
2787
 *
2788
 * @return bool
2789
 */
2790
function isBase64(string $str): bool
2791
{
2792
    $str = (string) trim($str);
2793
    if (! isset($str[0])) {
2794
        return false;
2795
    }
2796
2797
    $base64String = (string) base64_decode($str, true);
2798
    if ($base64String && base64_encode($base64String) === $str) {
2799
        return true;
2800
    }
2801
2802
    return false;
2803
}
2804
2805
/**
2806
 * Undocumented function
2807
 *
2808
 * @param string $field Parameter
2809
 *
2810
 * @return array|bool|resource|string
2811
 */
2812
function filterString(string $field)
2813
{
2814
    // Sanitize string
2815
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2816
    if (empty($field) === false) {
2817
        // Load AntiXSS
2818
        $antiXss = new AntiXSS();
2819
        // Return
2820
        return $antiXss->xss_clean($field);
2821
    }
2822
2823
    return false;
2824
}
2825
2826
/**
2827
 * CHeck if provided credentials are allowed on server
2828
 *
2829
 * @param string $login    User Login
2830
 * @param string $password User Pwd
2831
 * @param array  $SETTINGS Teampass settings
2832
 *
2833
 * @return bool
2834
 */
2835
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2836
{
2837
    // Build ldap configuration array
2838
    $config = [
2839
        // Mandatory Configuration Options
2840
        'hosts' => [$SETTINGS['ldap_hosts']],
2841
        'base_dn' => $SETTINGS['ldap_bdn'],
2842
        'username' => $SETTINGS['ldap_username'],
2843
        'password' => $SETTINGS['ldap_password'],
2844
2845
        // Optional Configuration Options
2846
        'port' => $SETTINGS['ldap_port'],
2847
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2848
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2849
        'version' => 3,
2850
        'timeout' => 5,
2851
        'follow_referrals' => false,
2852
2853
        // Custom LDAP Options
2854
        'options' => [
2855
            // See: http://php.net/ldap_set_option
2856
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2857
        ],
2858
    ];
2859
    
2860
    $connection = new Connection($config);
2861
    // Connect to LDAP
2862
    try {
2863
        $connection->connect();
2864
    } catch (\LdapRecord\Auth\BindException $e) {
2865
        $error = $e->getDetailedError();
2866
        error_log('TEAMPASS Error - Auth - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2867
        // deepcode ignore ServerLeak: No important data is sent
2868
        echo 'An error occurred.';
2869
        return false;
2870
    }
2871
2872
    // Authenticate user
2873
    try {
2874
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2875
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2876
        } else {
2877
            $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);
2878
        }
2879
    } catch (\LdapRecord\Auth\BindException $e) {
2880
        $error = $e->getDetailedError();
2881
        error_log('TEAMPASS Error - Auth - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2882
        // deepcode ignore ServerLeak: No important data is sent
2883
        echo 'An error occurred.';
2884
        return false;
2885
    }
2886
2887
    return true;
2888
}
2889
2890
/**
2891
 * Removes from DB all sharekeys of this user
2892
 *
2893
 * @param int $userId User's id
2894
 * @param array   $SETTINGS Teampass settings
2895
 *
2896
 * @return bool
2897
 */
2898
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2899
{
2900
    // Load class DB
2901
    loadClasses('DB');
2902
2903
    // Remove all item sharekeys items
2904
    // expect if personal item
2905
    DB::delete(
2906
        prefixTable('sharekeys_items'),
2907
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2908
        $userId
2909
    );
2910
    // Remove all item sharekeys files
2911
    DB::delete(
2912
        prefixTable('sharekeys_files'),
2913
        'user_id = %i AND object_id NOT IN (
2914
            SELECT f.id 
2915
            FROM ' . prefixTable('items') . ' AS i 
2916
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2917
            WHERE i.perso = 1
2918
        )',
2919
        $userId
2920
    );
2921
    // Remove all item sharekeys fields
2922
    DB::delete(
2923
        prefixTable('sharekeys_fields'),
2924
        'user_id = %i AND object_id NOT IN (
2925
            SELECT c.id 
2926
            FROM ' . prefixTable('items') . ' AS i 
2927
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2928
            WHERE i.perso = 1
2929
        )',
2930
        $userId
2931
    );
2932
    // Remove all item sharekeys logs
2933
    DB::delete(
2934
        prefixTable('sharekeys_logs'),
2935
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2936
        $userId
2937
    );
2938
    // Remove all item sharekeys suggestions
2939
    DB::delete(
2940
        prefixTable('sharekeys_suggestions'),
2941
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2942
        $userId
2943
    );
2944
    return false;
2945
}
2946
2947
/**
2948
 * Manage list of timezones   $SETTINGS Teampass settings
2949
 *
2950
 * @return array
2951
 */
2952
function timezone_list()
2953
{
2954
    static $timezones = null;
2955
    if ($timezones === null) {
2956
        $timezones = [];
2957
        $offsets = [];
2958
        $now = new DateTime('now', new DateTimeZone('UTC'));
2959
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2960
            $now->setTimezone(new DateTimeZone($timezone));
2961
            $offsets[] = $offset = $now->getOffset();
2962
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2963
        }
2964
2965
        array_multisort($offsets, $timezones);
2966
    }
2967
2968
    return $timezones;
2969
}
2970
2971
/**
2972
 * Provide timezone offset
2973
 *
2974
 * @param int $offset Timezone offset
2975
 *
2976
 * @return string
2977
 */
2978
function format_GMT_offset($offset): string
2979
{
2980
    $hours = intval($offset / 3600);
2981
    $minutes = abs(intval($offset % 3600 / 60));
2982
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2983
}
2984
2985
/**
2986
 * Provides timezone name
2987
 *
2988
 * @param string $name Timezone name
2989
 *
2990
 * @return string
2991
 */
2992
function format_timezone_name($name): string
2993
{
2994
    $name = str_replace('/', ', ', $name);
2995
    $name = str_replace('_', ' ', $name);
2996
2997
    return str_replace('St ', 'St. ', $name);
2998
}
2999
3000
/**
3001
 * Provides info if user should use MFA based on roles
3002
 *
3003
 * @param string $userRolesIds  User roles ids
3004
 * @param string $mfaRoles      Roles for which MFA is requested
3005
 *
3006
 * @return bool
3007
 */
3008
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3009
{
3010
    if (empty($mfaRoles) === true) {
3011
        return true;
3012
    }
3013
3014
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3015
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3016
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3017
        return true;
3018
    }
3019
3020
    return false;
3021
}
3022
3023
/**
3024
 * Permits to clean a string for export purpose
3025
 *
3026
 * @param string $text
3027
 * @param bool $emptyCheckOnly
3028
 * 
3029
 * @return string
3030
 */
3031
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3032
{
3033
    if (is_null($text) === true || empty($text) === true) {
3034
        return '';
3035
    }
3036
    // only expected to check if $text was empty
3037
    elseif ($emptyCheckOnly === true) {
3038
        return $text;
3039
    }
3040
3041
    return strip_tags(
3042
        cleanString(
3043
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3044
            true)
3045
        );
3046
}
3047
3048
/**
3049
 * Permits to check if user ID is valid
3050
 *
3051
 * @param integer $post_user_id
3052
 * @return bool
3053
 */
3054
function isUserIdValid($userId): bool
3055
{
3056
    if (is_null($userId) === false
3057
        && isset($userId) === true
3058
        && empty($userId) === false
3059
    ) {
3060
        return true;
3061
    }
3062
    return false;
3063
}
3064
3065
/**
3066
 * Check if a key exists and if its value equal the one expected
3067
 *
3068
 * @param string $key
3069
 * @param integer|string $value
3070
 * @param array $array
3071
 * 
3072
 * @return boolean
3073
 */
3074
function isKeyExistingAndEqual(
3075
    string $key,
3076
    /*PHP8 - integer|string*/$value,
3077
    array $array
3078
): bool
3079
{
3080
    if (isset($array[$key]) === true
3081
        && (is_int($value) === true ?
3082
            (int) $array[$key] === $value :
3083
            (string) $array[$key] === $value)
3084
    ) {
3085
        return true;
3086
    }
3087
    return false;
3088
}
3089
3090
/**
3091
 * Check if a variable is not set or equal to a value
3092
 *
3093
 * @param string|null $var
3094
 * @param integer|string $value
3095
 * 
3096
 * @return boolean
3097
 */
3098
function isKeyNotSetOrEqual(
3099
    /*PHP8 - string|null*/$var,
3100
    /*PHP8 - integer|string*/$value
3101
): bool
3102
{
3103
    if (isset($var) === false
3104
        || (is_int($value) === true ?
3105
            (int) $var === $value :
3106
            (string) $var === $value)
3107
    ) {
3108
        return true;
3109
    }
3110
    return false;
3111
}
3112
3113
/**
3114
 * Check if a key exists and if its value < to the one expected
3115
 *
3116
 * @param string $key
3117
 * @param integer $value
3118
 * @param array $array
3119
 * 
3120
 * @return boolean
3121
 */
3122
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3123
{
3124
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3125
        return true;
3126
    }
3127
    return false;
3128
}
3129
3130
/**
3131
 * Check if a key exists and if its value > to the one expected
3132
 *
3133
 * @param string $key
3134
 * @param integer $value
3135
 * @param array $array
3136
 * 
3137
 * @return boolean
3138
 */
3139
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3140
{
3141
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3142
        return true;
3143
    }
3144
    return false;
3145
}
3146
3147
/**
3148
 * Check if values in array are set
3149
 * Return true if all set
3150
 * Return false if one of them is not set
3151
 *
3152
 * @param array $arrayOfValues
3153
 * @return boolean
3154
 */
3155
function isSetArrayOfValues(array $arrayOfValues): bool
3156
{
3157
    foreach($arrayOfValues as $value) {
3158
        if (isset($value) === false) {
3159
            return false;
3160
        }
3161
    }
3162
    return true;
3163
}
3164
3165
/**
3166
 * Check if values in array are set
3167
 * Return true if all set
3168
 * Return false if one of them is not set
3169
 *
3170
 * @param array $arrayOfValues
3171
 * @param integer|string $value
3172
 * @return boolean
3173
 */
3174
function isArrayOfVarsEqualToValue(
3175
    array $arrayOfVars,
3176
    /*PHP8 - integer|string*/$value
3177
) : bool
3178
{
3179
    foreach($arrayOfVars as $variable) {
3180
        if ($variable !== $value) {
3181
            return false;
3182
        }
3183
    }
3184
    return true;
3185
}
3186
3187
/**
3188
 * Checks if at least one variable in array is equal to value
3189
 *
3190
 * @param array $arrayOfValues
3191
 * @param integer|string $value
3192
 * @return boolean
3193
 */
3194
function isOneVarOfArrayEqualToValue(
3195
    array $arrayOfVars,
3196
    /*PHP8 - integer|string*/$value
3197
) : bool
3198
{
3199
    foreach($arrayOfVars as $variable) {
3200
        if ($variable === $value) {
3201
            return true;
3202
        }
3203
    }
3204
    return false;
3205
}
3206
3207
/**
3208
 * Checks is value is null, not set OR empty
3209
 *
3210
 * @param string|int|null $value
3211
 * @return boolean
3212
 */
3213
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3214
{
3215
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3216
        return true;
3217
    }
3218
    return false;
3219
}
3220
3221
/**
3222
 * Checks if value is set and if empty is equal to passed boolean
3223
 *
3224
 * @param string|int $value
3225
 * @param boolean $boolean
3226
 * @return boolean
3227
 */
3228
function isValueSetEmpty($value, $boolean = true) : bool
3229
{
3230
    if (isset($value) === true && empty($value) === $boolean) {
3231
        return true;
3232
    }
3233
    return false;
3234
}
3235
3236
/**
3237
 * Ensure Complexity is translated
3238
 *
3239
 * @return void
3240
 */
3241
function defineComplexity() : void
3242
{
3243
    // Load user's language
3244
    $lang = new Language();
3245
    
3246
    if (defined('TP_PW_COMPLEXITY') === false) {
3247
        define(
3248
            'TP_PW_COMPLEXITY',
3249
            [
3250
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3251
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3252
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3253
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3254
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3255
            ]
3256
        );
3257
    }
3258
}
3259
3260
/**
3261
 * Uses Sanitizer to perform data sanitization
3262
 *
3263
 * @param array     $data
3264
 * @param array     $filters
3265
 * @return array|string
3266
 */
3267
function dataSanitizer(array $data, array $filters): array|string
3268
{
3269
    // Load Sanitizer library
3270
    $sanitizer = new Sanitizer($data, $filters);
3271
3272
    // Load AntiXSS
3273
    $antiXss = new AntiXSS();
3274
3275
    // Sanitize post and get variables
3276
    return $antiXss->xss_clean($sanitizer->sanitize());
3277
}
3278
3279
/**
3280
 * Permits to manage the cache tree for a user
3281
 *
3282
 * @param integer $user_id
3283
 * @param string $data
3284
 * @param array $SETTINGS
3285
 * @param string $field_update
3286
 * @return void
3287
 */
3288
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3289
{
3290
    // Load class DB
3291
    loadClasses('DB');
3292
3293
    // Exists ?
3294
    $userCacheId = DB::queryfirstrow(
3295
        'SELECT increment_id
3296
        FROM ' . prefixTable('cache_tree') . '
3297
        WHERE user_id = %i',
3298
        $user_id
3299
    );
3300
    
3301
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3302
        // insert in table
3303
        DB::insert(
3304
            prefixTable('cache_tree'),
3305
            array(
3306
                'data' => $data,
3307
                'timestamp' => time(),
3308
                'user_id' => $user_id,
3309
                'visible_folders' => '',
3310
            )
3311
        );
3312
    } else {
3313
        if (empty($field_update) === true) {
3314
            DB::update(
3315
                prefixTable('cache_tree'),
3316
                [
3317
                    'timestamp' => time(),
3318
                    'data' => $data,
3319
                ],
3320
                'increment_id = %i',
3321
                $userCacheId['increment_id']
3322
            );
3323
        /* USELESS
3324
        } else {
3325
            DB::update(
3326
                prefixTable('cache_tree'),
3327
                [
3328
                    $field_update => $data,
3329
                ],
3330
                'increment_id = %i',
3331
                $userCacheId['increment_id']
3332
            );*/
3333
        }
3334
    }
3335
}
3336
3337
/**
3338
 * Permits to calculate a %
3339
 *
3340
 * @param float $nombre
3341
 * @param float $total
3342
 * @param float $pourcentage
3343
 * @return float
3344
 */
3345
function pourcentage(float $nombre, float $total, float $pourcentage): float
3346
{ 
3347
    $resultat = ($nombre/$total) * $pourcentage;
3348
    return round($resultat);
3349
}
3350
3351
/**
3352
 * Load the folders list from the cache
3353
 *
3354
 * @param string $fieldName
3355
 * @param string $sessionName
3356
 * @param boolean $forceRefresh
3357
 * @return array
3358
 */
3359
function loadFoldersListByCache(
3360
    string $fieldName,
3361
    string $sessionName,
3362
    bool $forceRefresh = false
3363
): array
3364
{
3365
    // Case when refresh is EXPECTED / MANDATORY
3366
    if ($forceRefresh === true) {
3367
        return [
3368
            'state' => false,
3369
            'data' => [],
3370
        ];
3371
    }
3372
    
3373
    $session = SessionManager::getSession();
3374
3375
    // Get last folder update
3376
    $lastFolderChange = DB::queryfirstrow(
3377
        'SELECT valeur FROM ' . prefixTable('misc') . '
3378
        WHERE type = %s AND intitule = %s',
3379
        'timestamp',
3380
        'last_folder_change'
3381
    );
3382
    if (DB::count() === 0) {
3383
        $lastFolderChange['valeur'] = 0;
3384
    }
3385
3386
    // Case when an update in the tree has been done
3387
    // Refresh is then mandatory
3388
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3389
        return [
3390
            'state' => false,
3391
            'data' => [],
3392
        ];
3393
    }
3394
3395
    // Does this user has the tree structure in session?
3396
    // If yes then use it
3397
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3398
        return [
3399
            'state' => true,
3400
            'data' => json_encode($session->get('user-folders_list')),
3401
        ];
3402
    }
3403
3404
    // Does this user has a tree cache
3405
    $userCacheTree = DB::queryfirstrow(
3406
        'SELECT '.$fieldName.'
3407
        FROM ' . prefixTable('cache_tree') . '
3408
        WHERE user_id = %i',
3409
        $session->get('user-id')
3410
    );
3411
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3412
        SessionManager::addRemoveFromSessionAssociativeArray(
3413
            'user-folders_list',
3414
            [$userCacheTree[$fieldName]],
3415
            'add'
3416
        );
3417
        return [
3418
            'state' => true,
3419
            'data' => $userCacheTree[$fieldName],
3420
        ];
3421
    }
3422
3423
    return [
3424
        'state' => false,
3425
        'data' => [],
3426
    ];
3427
}
3428
3429
3430
/**
3431
 * Permits to refresh the categories of folders
3432
 *
3433
 * @param array $folderIds
3434
 * @return void
3435
 */
3436
function handleFoldersCategories(
3437
    array $folderIds
3438
)
3439
{
3440
    // Load class DB
3441
    loadClasses('DB');
3442
3443
    $arr_data = array();
3444
3445
    // force full list of folders
3446
    if (count($folderIds) === 0) {
3447
        $folderIds = DB::queryFirstColumn(
3448
            'SELECT id
3449
            FROM ' . prefixTable('nested_tree') . '
3450
            WHERE personal_folder=%i',
3451
            0
3452
        );
3453
    }
3454
3455
    // Get complexity
3456
    defineComplexity();
3457
3458
    // update
3459
    foreach ($folderIds as $folder) {
3460
        // Do we have Categories
3461
        // get list of associated Categories
3462
        $arrCatList = array();
3463
        $rows_tmp = DB::query(
3464
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3465
            f.id_category AS category_id
3466
            FROM ' . prefixTable('categories_folders') . ' AS f
3467
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3468
            WHERE id_folder=%i',
3469
            $folder
3470
        );
3471
        if (DB::count() > 0) {
3472
            foreach ($rows_tmp as $row) {
3473
                $arrCatList[$row['id']] = array(
3474
                    'id' => $row['id'],
3475
                    'title' => $row['title'],
3476
                    'level' => $row['level'],
3477
                    'type' => $row['type'],
3478
                    'masked' => $row['masked'],
3479
                    'order' => $row['order'],
3480
                    'encrypted_data' => $row['encrypted_data'],
3481
                    'role_visibility' => $row['role_visibility'],
3482
                    'is_mandatory' => $row['is_mandatory'],
3483
                    'category_id' => $row['category_id'],
3484
                );
3485
            }
3486
        }
3487
        $arr_data['categories'] = $arrCatList;
3488
3489
        // Now get complexity
3490
        $valTemp = '';
3491
        $data = DB::queryFirstRow(
3492
            'SELECT valeur
3493
            FROM ' . prefixTable('misc') . '
3494
            WHERE type = %s AND intitule=%i',
3495
            'complex',
3496
            $folder
3497
        );
3498
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3499
            $valTemp = array(
3500
                'value' => $data['valeur'],
3501
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3502
            );
3503
        }
3504
        $arr_data['complexity'] = $valTemp;
3505
3506
        // Now get Roles
3507
        $valTemp = '';
3508
        $rows_tmp = DB::query(
3509
            'SELECT t.title
3510
            FROM ' . prefixTable('roles_values') . ' as v
3511
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3512
            WHERE v.folder_id = %i
3513
            GROUP BY title',
3514
            $folder
3515
        );
3516
        foreach ($rows_tmp as $record) {
3517
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3518
        }
3519
        $arr_data['visibilityRoles'] = $valTemp;
3520
3521
        // now save in DB
3522
        DB::update(
3523
            prefixTable('nested_tree'),
3524
            array(
3525
                'categories' => json_encode($arr_data),
3526
            ),
3527
            'id = %i',
3528
            $folder
3529
        );
3530
    }
3531
}
3532
3533
/**
3534
 * List all users that have specific roles
3535
 *
3536
 * @param array $roles
3537
 * @return array
3538
 */
3539
function getUsersWithRoles(
3540
    array $roles
3541
): array
3542
{
3543
    $session = SessionManager::getSession();
3544
    $arrUsers = array();
3545
3546
    foreach ($roles as $role) {
3547
        // loop on users and check if user has this role
3548
        $rows = DB::query(
3549
            'SELECT id, fonction_id
3550
            FROM ' . prefixTable('users') . '
3551
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3552
            $session->get('user-id')
3553
        );
3554
        foreach ($rows as $user) {
3555
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3556
            if (in_array($role, $userRoles, true) === true) {
3557
                array_push($arrUsers, $user['id']);
3558
            }
3559
        }
3560
    }
3561
3562
    return $arrUsers;
3563
}
3564
3565
3566
/**
3567
 * Get all users informations
3568
 *
3569
 * @param integer $userId
3570
 * @return array
3571
 */
3572
function getFullUserInfos(
3573
    int $userId
3574
): array
3575
{
3576
    if (empty($userId) === true) {
3577
        return array();
3578
    }
3579
3580
    $val = DB::queryfirstrow(
3581
        'SELECT *
3582
        FROM ' . prefixTable('users') . '
3583
        WHERE id = %i',
3584
        $userId
3585
    );
3586
3587
    return $val;
3588
}
3589
3590
/**
3591
 * Is required an upgrade
3592
 *
3593
 * @return boolean
3594
 */
3595
function upgradeRequired(): bool
3596
{
3597
    // Get settings.php
3598
    include_once __DIR__. '/../includes/config/settings.php';
3599
3600
    // Get timestamp in DB
3601
    $val = DB::queryfirstrow(
3602
        'SELECT valeur
3603
        FROM ' . prefixTable('misc') . '
3604
        WHERE type = %s AND intitule = %s',
3605
        'admin',
3606
        'upgrade_timestamp'
3607
    );
3608
    
3609
    // if not exists then error
3610
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3611
3612
    // if empty or too old then error
3613
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3614
        return true;
3615
    }
3616
3617
    return false;
3618
}
3619
3620
/**
3621
 * Permits to change the user keys on his demand
3622
 *
3623
 * @param integer $userId
3624
 * @param string $passwordClear
3625
 * @param integer $nbItemsToTreat
3626
 * @param string $encryptionKey
3627
 * @param boolean $deleteExistingKeys
3628
 * @param boolean $sendEmailToUser
3629
 * @param boolean $encryptWithUserPassword
3630
 * @param boolean $generate_user_new_password
3631
 * @param string $emailBody
3632
 * @param boolean $user_self_change
3633
 * @param string $recovery_public_key
3634
 * @param string $recovery_private_key
3635
 * @return string
3636
 */
3637
function handleUserKeys(
3638
    int $userId,
3639
    string $passwordClear,
3640
    int $nbItemsToTreat,
3641
    string $encryptionKey = '',
3642
    bool $deleteExistingKeys = false,
3643
    bool $sendEmailToUser = true,
3644
    bool $encryptWithUserPassword = false,
3645
    bool $generate_user_new_password = false,
3646
    string $emailBody = '',
3647
    bool $user_self_change = false,
3648
    string $recovery_public_key = '',
3649
    string $recovery_private_key = ''
3650
): string
3651
{
3652
    $session = SessionManager::getSession();
3653
    $lang = new Language();
3654
3655
    // prepapre background tasks for item keys generation        
3656
    $userTP = DB::queryFirstRow(
3657
        'SELECT pw, public_key, private_key
3658
        FROM ' . prefixTable('users') . '
3659
        WHERE id = %i',
3660
        TP_USER_ID
3661
    );
3662
    if (DB::count() === 0) {
3663
        return prepareExchangedData(
3664
            array(
3665
                'error' => true,
3666
                'message' => 'User not exists',
3667
            ),
3668
            'encode'
3669
        );
3670
    }
3671
3672
    // Do we need to generate new user password
3673
    if ($generate_user_new_password === true) {
3674
        // Generate a new password
3675
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3676
    }
3677
3678
    // Hash the password
3679
    $pwdlib = new PasswordLib();
3680
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3681
    if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3682
        return prepareExchangedData(
3683
            array(
3684
                'error' => true,
3685
                'message' => $lang->get('pw_hash_not_correct'),
3686
            ),
3687
            'encode'
3688
        );
3689
    }
3690
3691
    // Generate new keys
3692
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3693
        $userKeys = [
3694
            'public_key' => $recovery_public_key,
3695
            'private_key_clear' => $recovery_private_key,
3696
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3697
        ];
3698
    } else {
3699
        $userKeys = generateUserKeys($passwordClear);
3700
    }
3701
3702
    // Save in DB
3703
    DB::update(
3704
        prefixTable('users'),
3705
        array(
3706
            'pw' => $hashedPassword,
3707
            'public_key' => $userKeys['public_key'],
3708
            'private_key' => $userKeys['private_key'],
3709
        ),
3710
        'id=%i',
3711
        $userId
3712
    );
3713
3714
    // update session too
3715
    if ($userId === $session->get('user-id')) {
3716
        $session->set('user-private_key', $userKeys['private_key_clear']);
3717
        $session->set('user-public_key', $userKeys['public_key']);
3718
    }
3719
3720
    // Manage empty encryption key
3721
    // Let's take the user's password if asked and if no encryption key provided
3722
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3723
3724
    // Create process
3725
    DB::insert(
3726
        prefixTable('processes'),
3727
        array(
3728
            'created_at' => time(),
3729
            'process_type' => 'create_user_keys',
3730
            'arguments' => json_encode([
3731
                'new_user_id' => (int) $userId,
3732
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3733
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3734
                'owner_id' => (int) TP_USER_ID,
3735
                'creator_pwd' => $userTP['pw'],
3736
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3737
                'otp_provided_new_value' => 1,
3738
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3739
                'user_self_change' => $user_self_change === true ? 1 : 0,
3740
            ]),
3741
            'updated_at' => '',
3742
            'finished_at' => '',
3743
            'output' => '',
3744
        )
3745
    );
3746
    $processId = DB::insertId();
3747
3748
    // Delete existing keys
3749
    if ($deleteExistingKeys === true) {
3750
        deleteUserObjetsKeys(
3751
            (int) $userId,
3752
        );
3753
    }
3754
3755
    // Create tasks
3756
    createUserTasks($processId, $nbItemsToTreat);
3757
3758
    // update user's new status
3759
    DB::update(
3760
        prefixTable('users'),
3761
        [
3762
            'is_ready_for_usage' => 0,
3763
            'otp_provided' => 1,
3764
            'ongoing_process_id' => $processId,
3765
            'special' => 'generate-keys',
3766
        ],
3767
        'id=%i',
3768
        $userId
3769
    );
3770
3771
    return prepareExchangedData(
3772
        array(
3773
            'error' => false,
3774
            'message' => '',
3775
        ),
3776
        'encode'
3777
    );
3778
}
3779
3780
/**
3781
 * Permits to generate a new password for a user
3782
 *
3783
 * @param integer $processId
3784
 * @param integer $nbItemsToTreat
3785
 * @return void
3786
 
3787
 */
3788
function createUserTasks($processId, $nbItemsToTreat): void
3789
{
3790
    DB::insert(
3791
        prefixTable('processes_tasks'),
3792
        array(
3793
            'process_id' => $processId,
3794
            'created_at' => time(),
3795
            'task' => json_encode([
3796
                'step' => 'step0',
3797
                'index' => 0,
3798
                'nb' => $nbItemsToTreat,
3799
            ]),
3800
        )
3801
    );
3802
3803
    DB::insert(
3804
        prefixTable('processes_tasks'),
3805
        array(
3806
            'process_id' => $processId,
3807
            'created_at' => time(),
3808
            'task' => json_encode([
3809
                'step' => 'step10',
3810
                'index' => 0,
3811
                'nb' => $nbItemsToTreat,
3812
            ]),
3813
        )
3814
    );
3815
3816
    DB::insert(
3817
        prefixTable('processes_tasks'),
3818
        array(
3819
            'process_id' => $processId,
3820
            'created_at' => time(),
3821
            'task' => json_encode([
3822
                'step' => 'step20',
3823
                'index' => 0,
3824
                'nb' => $nbItemsToTreat,
3825
            ]),
3826
        )
3827
    );
3828
3829
    DB::insert(
3830
        prefixTable('processes_tasks'),
3831
        array(
3832
            'process_id' => $processId,
3833
            'created_at' => time(),
3834
            'task' => json_encode([
3835
                'step' => 'step30',
3836
                'index' => 0,
3837
                'nb' => $nbItemsToTreat,
3838
            ]),
3839
        )
3840
    );
3841
3842
    DB::insert(
3843
        prefixTable('processes_tasks'),
3844
        array(
3845
            'process_id' => $processId,
3846
            'created_at' => time(),
3847
            'task' => json_encode([
3848
                'step' => 'step40',
3849
                'index' => 0,
3850
                'nb' => $nbItemsToTreat,
3851
            ]),
3852
        )
3853
    );
3854
3855
    DB::insert(
3856
        prefixTable('processes_tasks'),
3857
        array(
3858
            'process_id' => $processId,
3859
            'created_at' => time(),
3860
            'task' => json_encode([
3861
                'step' => 'step50',
3862
                'index' => 0,
3863
                'nb' => $nbItemsToTreat,
3864
            ]),
3865
        )
3866
    );
3867
3868
    DB::insert(
3869
        prefixTable('processes_tasks'),
3870
        array(
3871
            'process_id' => $processId,
3872
            'created_at' => time(),
3873
            'task' => json_encode([
3874
                'step' => 'step60',
3875
                'index' => 0,
3876
                'nb' => $nbItemsToTreat,
3877
            ]),
3878
        )
3879
    );
3880
}
3881
3882
/**
3883
 * Permeits to check the consistency of date versus columns definition
3884
 *
3885
 * @param string $table
3886
 * @param array $dataFields
3887
 * @return array
3888
 */
3889
function validateDataFields(
3890
    string $table,
3891
    array $dataFields
3892
): array
3893
{
3894
    // Get table structure
3895
    $result = DB::query(
3896
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3897
        DB_NAME,
3898
        $table
3899
    );
3900
3901
    foreach ($result as $row) {
3902
        $field = $row['COLUMN_NAME'];
3903
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3904
3905
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3906
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3907
                return [
3908
                    'state' => false,
3909
                    'field' => $field,
3910
                    'maxLength' => $maxLength,
3911
                    'currentLength' => strlen((string) $dataFields[$field]),
3912
                ];
3913
            }
3914
        }
3915
    }
3916
    
3917
    return [
3918
        'state' => true,
3919
        'message' => '',
3920
    ];
3921
}
3922
3923
/**
3924
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3925
 *
3926
 * @param string $string
3927
 * @return string
3928
 */
3929
function filterVarBack(string $string): string
3930
{
3931
    $arr = [
3932
        '&#060;' => '<',
3933
        '&#062;' => '>',
3934
        '&#034;' => '"',
3935
        '&#039;' => "'",
3936
        '&#038;' => '&',
3937
    ];
3938
3939
    foreach ($arr as $key => $value) {
3940
        $string = str_replace($key, $value, $string);
3941
    }
3942
3943
    return $string;
3944
}
3945
3946
/**
3947
 * 
3948
 */
3949
function storeTask(
3950
    string $taskName,
3951
    int $user_id,
3952
    int $is_personal_folder,
3953
    int $folder_destination_id,
3954
    int $item_id,
3955
    string $object_keys
3956
)
3957
{
3958
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3959
        // Create process
3960
        DB::insert(
3961
            prefixTable('processes'),
3962
            array(
3963
                'created_at' => time(),
3964
                'process_type' => $taskName,
3965
                'arguments' => json_encode([
3966
                    'item_id' => $item_id,
3967
                    'object_key' => $object_keys,
3968
                ]),
3969
                'updated_at' => '',
3970
                'finished_at' => '',
3971
                'output' => '',
3972
                'item_id' => $item_id,
3973
            )
3974
        );
3975
        $processId = DB::insertId();
3976
3977
        // Create tasks
3978
        // 1- Create password sharekeys for users of this new ITEM
3979
        DB::insert(
3980
            prefixTable('processes_tasks'),
3981
            array(
3982
                'process_id' => $processId,
3983
                'created_at' => time(),
3984
                'task' => json_encode([
3985
                    'step' => 'create_users_pwd_key',
3986
                    'index' => 0,
3987
                ]),
3988
            )
3989
        );
3990
3991
        // 2- Create fields sharekeys for users of this new ITEM
3992
        DB::insert(
3993
            prefixTable('processes_tasks'),
3994
            array(
3995
                'process_id' => $processId,
3996
                'created_at' => time(),
3997
                'task' => json_encode([
3998
                    'step' => 'create_users_fields_key',
3999
                    'index' => 0,
4000
                ]),
4001
            )
4002
        );
4003
4004
        // 3- Create files sharekeys for users of this new ITEM
4005
        DB::insert(
4006
            prefixTable('processes_tasks'),
4007
            array(
4008
                'process_id' => $processId,
4009
                'created_at' => time(),
4010
                'task' => json_encode([
4011
                    'step' => 'create_users_files_key',
4012
                    'index' => 0,
4013
                ]),
4014
            )
4015
        );
4016
    }
4017
}
4018
4019
4020
function createTaskForItemUpdate(
4021
    string $taskName,
4022
    int $itemId,
4023
    int $userId,
4024
    string $objectKey,
4025
    int $parentId = -1
4026
)
4027
{
4028
    // 1- Create main process
4029
    // ---
4030
    
4031
    // Create process
4032
    DB::insert(
4033
        prefixTable('processes'),
4034
        array(
4035
            'created_at' => time(),
4036
            'process_type' => 'item_update_create_keys',
4037
            'arguments' => json_encode([
4038
                'all_users_except_id' => (int) $userId,
4039
                'item_id' => (int) $itemId,
4040
                'pwd' => $objectKey,
4041
                'author' => (int) $userId,
4042
            ]),
4043
            'updated_at' => '',
4044
            'finished_at' => '',
4045
            'output' => '',
4046
            'item_id' => (int) $parentId !== -1 ?  $parentId : null,
4047
        )
4048
    );
4049
    $processId = DB::insertId();
4050
4051
    // 2- Create expected tasks
4052
    // ---
4053
    switch ($taskName) {
4054
        case 'item_password':
4055
            
4056
            DB::insert(
4057
                prefixTable('processes_tasks'),
4058
                array(
4059
                    'process_id' => $processId,
4060
                    'created_at' => time(),
4061
                    'task' => json_encode([
4062
                        'step' => 'create_users_pwd_key',
4063
                        'index' => 0,
4064
                    ]),
4065
                )
4066
            );
4067
4068
            break;
4069
        case 'item_field':
4070
            
4071
            DB::insert(
4072
                prefixTable('processes_tasks'),
4073
                array(
4074
                    'process_id' => $processId,
4075
                    'created_at' => time(),
4076
                    'task' => json_encode([
4077
                        'step' => 'create_users_fields_key',
4078
                        'index' => 0,
4079
                    ]),
4080
                )
4081
            );
4082
4083
            break;
4084
        case 'item_file':
4085
            # code...
4086
            break;
4087
        default:
4088
            # code...
4089
            break;
4090
    }
4091
}
4092
4093
4094
function deleteProcessAndRelatedTasks(int $processId)
4095
{
4096
    // Delete process
4097
    DB::delete(
4098
        prefixTable('processes'),
4099
        'id=%i',
4100
        $processId
4101
    );
4102
4103
    // Delete tasks
4104
    DB::delete(
4105
        prefixTable('processes_tasks'),
4106
        'process_id=%i',
4107
        $processId
4108
    );
4109
4110
}
4111
4112
/**
4113
 * Return PHP binary path
4114
 *
4115
 * @return string
4116
 */
4117
function getPHPBinary(): string
4118
{
4119
    // Get PHP binary path
4120
    $phpBinaryFinder = new PhpExecutableFinder();
4121
    $phpBinaryPath = $phpBinaryFinder->find();
4122
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4123
}
4124
4125
4126
4127
/**
4128
 * Delete unnecessary keys for personal items
4129
 *
4130
 * @param boolean $allUsers
4131
 * @param integer $user_id
4132
 * @return void
4133
 */
4134
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4135
{
4136
    if ($allUsers === true) {
4137
        // Load class DB
4138
        if (class_exists('DB') === false) {
4139
            loadClasses('DB');
4140
        }
4141
4142
        $users = DB::query(
4143
            'SELECT id
4144
            FROM ' . prefixTable('users') . '
4145
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4146
            ORDER BY login ASC'
4147
        );
4148
        foreach ($users as $user) {
4149
            purgeUnnecessaryKeysForUser((int) $user['id']);
4150
        }
4151
    } else {
4152
        purgeUnnecessaryKeysForUser((int) $user_id);
4153
    }
4154
}
4155
4156
/**
4157
 * Delete unnecessary keys for personal items
4158
 *
4159
 * @param integer $user_id
4160
 * @return void
4161
 */
4162
function purgeUnnecessaryKeysForUser(int $user_id=0)
4163
{
4164
    if ($user_id === 0) {
4165
        return;
4166
    }
4167
4168
    // Load class DB
4169
    loadClasses('DB');
4170
4171
    $personalItems = DB::queryFirstColumn(
4172
        'SELECT id
4173
        FROM ' . prefixTable('items') . ' AS i
4174
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4175
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4176
        $user_id
4177
    );
4178
    if (count($personalItems) > 0) {
4179
        // Item keys
4180
        DB::delete(
4181
            prefixTable('sharekeys_items'),
4182
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4183
            $personalItems,
4184
            $user_id
4185
        );
4186
        // Files keys
4187
        DB::delete(
4188
            prefixTable('sharekeys_files'),
4189
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4190
            $personalItems,
4191
            $user_id
4192
        );
4193
        // Fields keys
4194
        DB::delete(
4195
            prefixTable('sharekeys_fields'),
4196
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4197
            $personalItems,
4198
            $user_id
4199
        );
4200
        // Logs keys
4201
        DB::delete(
4202
            prefixTable('sharekeys_logs'),
4203
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4204
            $personalItems,
4205
            $user_id
4206
        );
4207
    }
4208
}
4209
4210
/**
4211
 * Generate recovery keys file
4212
 *
4213
 * @param integer $userId
4214
 * @param array $SETTINGS
4215
 * @return string
4216
 */
4217
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4218
{
4219
    $session = SessionManager::getSession();
4220
    // Check if user exists
4221
    $userInfo = DB::queryFirstRow(
4222
        'SELECT pw, public_key, private_key, login, name
4223
        FROM ' . prefixTable('users') . '
4224
        WHERE id = %i',
4225
        $userId
4226
    );
4227
4228
    if (DB::count() > 0) {
4229
        $now = (int) time();
4230
4231
        // Prepare file content
4232
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4233
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4234
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4235
            "Public Key:\n".$userInfo['public_key']."\n\n".
4236
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4237
4238
        // Update user's keys_recovery_time
4239
        DB::update(
4240
            prefixTable('users'),
4241
            [
4242
                'keys_recovery_time' => $now,
4243
            ],
4244
            'id=%i',
4245
            $userId
4246
        );
4247
        $session->set('user-keys_recovery_time', $now);
4248
4249
        //Log into DB the user's disconnection
4250
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4251
        
4252
        // Return data
4253
        return prepareExchangedData(
4254
            array(
4255
                'error' => false,
4256
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4257
                'timestamp' => $now,
4258
                'content' => base64_encode($export_value),
4259
                'login' => $userInfo['login'],
4260
            ),
4261
            'encode'
4262
        );
4263
    }
4264
4265
    return prepareExchangedData(
4266
        array(
4267
            'error' => true,
4268
            'datetime' => '',
4269
        ),
4270
        'encode'
4271
    );
4272
}
4273
4274
/**
4275
 * Permits to load expected classes
4276
 *
4277
 * @param string $className
4278
 * @return void
4279
 */
4280
function loadClasses(string $className = ''): void
4281
{
4282
    require_once __DIR__. '/../includes/config/include.php';
4283
    require_once __DIR__. '/../includes/config/settings.php';
4284
    require_once __DIR__.'/../vendor/autoload.php';
4285
4286
    if (defined('DB_PASSWD_CLEAR') === false) {
4287
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4288
    }
4289
4290
    if (empty($className) === false) {
4291
        // Load class DB
4292
        if ((string) $className === 'DB') {
4293
            //Connect to DB
4294
            DB::$host = DB_HOST;
4295
            DB::$user = DB_USER;
4296
            DB::$password = DB_PASSWD_CLEAR;
4297
            DB::$dbName = DB_NAME;
4298
            DB::$port = DB_PORT;
4299
            DB::$encoding = DB_ENCODING;
4300
            DB::$ssl = DB_SSL;
4301
            DB::$connect_options = DB_CONNECT_OPTIONS;
4302
        }
4303
    }
4304
4305
}
4306
4307
/**
4308
 * Returns the page the user is visiting.
4309
 *
4310
 * @return string The page name
4311
 */
4312
function getCurrectPage($SETTINGS)
4313
{
4314
    
4315
    $request = SymfonyRequest::createFromGlobals();
4316
4317
    // Parse the url
4318
    parse_str(
4319
        substr(
4320
            (string) $request->getRequestUri(),
4321
            strpos((string) $request->getRequestUri(), '?') + 1
4322
        ),
4323
        $result
4324
    );
4325
4326
    return $result['page'];
4327
}
4328
4329
/**
4330
 * Permits to return value if set
4331
 *
4332
 * @param string|int $value
4333
 * @param string|int|null $retFalse
4334
 * @param string|int $retTrue
4335
 * @return mixed
4336
 */
4337
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4338
{
4339
4340
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4341
}