Passed
Branch wip_sessions (2e0cc8)
by Nils
04:59
created

handleUserKeys()   D

Complexity

Conditions 15
Paths 67

Size

Total Lines 221
Code Lines 134

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 15
eloc 134
nc 67
nop 12
dl 0
loc 221
rs 4.7333
c 3
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 * @file      main.functions.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use LdapRecord\Connection;
28
use ForceUTF8\Encoding;
29
use Elegant\Sanitizer\Sanitizer;
30
use voku\helper\AntiXSS;
31
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
32
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
33
use Symfony\Component\HttpFoundation\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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