Passed
Push — master ( 82c165...9440db )
by Nils
10:41
created

storeUsersShareKey()   D

Complexity

Conditions 18
Paths 16

Size

Total Lines 102
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

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

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

4387
            array_keys(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4388
            array_values($post_replace),
0 ignored issues
show
Bug introduced by
$post_replace of type null is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

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

4388
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4389
            $post_body
4390
        );
4391
    }
4392
4393
    if ($immediate_email === true) {
4394
        $ret = sendEmail(
4395
            $post_subject,
4396
            $post_body,
4397
            $post_receipt,
4398
            $SETTINGS,
4399
            '',
4400
            false
4401
        );
4402
    
4403
        $ret = json_decode($ret, true);
4404
    
4405
        return prepareExchangedData(
4406
            array(
4407
                'error' => empty($ret['error']) === true ? false : true,
4408
                'message' => $ret['message'],
4409
            ),
4410
            'encode'
4411
        );
4412
    } else {
4413
        // Send through task handler
4414
        prepareSendingEmail(
4415
            $post_subject,
4416
            $post_body,
4417
            $post_receipt,
4418
            ""
4419
        );
4420
    }
4421
4422
    return null;
4423
}
4424
4425
/**
4426
 * Converts a password strengh value to zxcvbn level
4427
 * 
4428
 * @param integer $passwordStrength
4429
 * 
4430
 * @return integer
4431
 */
4432
function convertPasswordStrength($passwordStrength): int
4433
{
4434
    if ($passwordStrength < TP_PW_STRENGTH_2) {
4435
        return 0;
4436
    } elseif ($passwordStrength < TP_PW_STRENGTH_3) {
4437
        return 1;
4438
    } elseif ($passwordStrength < TP_PW_STRENGTH_4) {
4439
        return 2;
4440
    } elseif ($passwordStrength < TP_PW_STRENGTH_5) {
4441
        return 3;
4442
    } else {
4443
        return 4;
4444
    }
4445
}