Passed
Push — master ( 56f423...74a82c )
by Nils
04:43
created

handleUserKeys()   D

Complexity

Conditions 16
Paths 67

Size

Total Lines 141
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 16
eloc 78
c 5
b 0
f 0
nc 67
nop 12
dl 0
loc 141
rs 4.9333

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('processes'),
1116
        array(
1117
            'created_at' => time(),
1118
            'process_type' => 'send_email',
1119
            'arguments' => json_encode([
1120
                'subject' => $subject,
1121
                'receivers' => $email,
1122
                'body' => $body,
1123
                'receiver_name' => $receiverName,
1124
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1125
            'updated_at' => '',
1126
            'finished_at' => '',
1127
            'output' => '',
1128
        )
1129
    );
1130
}
1131
1132
/**
1133
 * Permits to send an email.
1134
 *
1135
 * @param string $subject     email subject
1136
 * @param string $textMail    email message
1137
 * @param string $email       email
1138
 * @param array  $SETTINGS    settings
1139
 * @param string $textMailAlt email message alt
1140
 * @param bool   $silent      no errors
1141
 *
1142
 * @return string some json info
1143
 */
1144
function sendEmail(
1145
    $subject,
1146
    $textMail,
1147
    $email,
1148
    $SETTINGS,
1149
    $textMailAlt = null,
1150
    $silent = true,
1151
    $cron = false
1152
) {
1153
    $lang = new Language();
1154
1155
    // CAse where email not defined
1156
    if ($email === 'none' || empty($email) === true) {
1157
        return json_encode(
1158
            [
1159
                'error' => true,
1160
                'message' => $lang->get('forgot_my_pw_email_sent'),
1161
            ]
1162
        );
1163
    }
1164
1165
    // Build and send email
1166
    $email = buildEmail(
1167
        $subject,
1168
        $textMail,
1169
        $email,
1170
        $SETTINGS,
1171
        $textMailAlt = null,
1172
        $silent = true,
1173
        $cron
1174
    );
1175
1176
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1177
        return json_encode(
1178
            [
1179
                'error' => false,
1180
                'message' => $lang->get('forgot_my_pw_email_sent'),
1181
            ]
1182
        );
1183
    }
1184
    // Debug purpose
1185
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1186
        return json_encode(
1187
            [
1188
                'error' => true,
1189
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1190
            ]
1191
        );
1192
    }
1193
    return json_encode(
1194
        [
1195
            'error' => false,
1196
            'message' => $lang->get('share_sent_ok'),
1197
        ]
1198
    );
1199
}
1200
1201
1202
function buildEmail(
1203
    $subject,
1204
    $textMail,
1205
    $email,
1206
    $SETTINGS,
1207
    $textMailAlt = null,
1208
    $silent = true,
1209
    $cron = false
1210
)
1211
{
1212
    // Load PHPMailer
1213
    $mail = new PHPMailer(true);
1214
    $languageDir = $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/';
1215
1216
    try {
1217
        // Set language and SMTPDebug
1218
        $mail->setLanguage('en', $languageDir);
1219
        $mail->SMTPDebug = ($cron || $silent) ? 0 : $SETTINGS['email_debug_level'];
1220
        
1221
        /*
1222
        // Define custom Debug output function
1223
        $mail->Debugoutput = function($str, $level) {
1224
            // Path to your log file
1225
            $logFilePath = '/var/log/phpmailer.log';
1226
            file_put_contents($logFilePath, gmdate('Y-m-d H:i:s'). "\t$level\t$str\n", FILE_APPEND | LOCK_EX);
1227
        };
1228
        */
1229
1230
        // Configure SMTP
1231
        $mail->isSMTP();
1232
        $mail->Host = $SETTINGS['email_smtp_server'];
1233
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1;
1234
        $mail->Username = $SETTINGS['email_auth_username'];
1235
        $mail->Password = $SETTINGS['email_auth_pwd'];
1236
        $mail->Port = (int) $SETTINGS['email_port'];
1237
        $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1238
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none';
1239
        $mail->SMTPOptions = [
1240
            'ssl' => [
1241
                'verify_peer' => false,
1242
                'verify_peer_name' => false,
1243
                'allow_self_signed' => true,
1244
            ],
1245
        ];
1246
1247
        // Set From and FromName
1248
        $mail->From = $SETTINGS['email_from'];
1249
        $mail->FromName = $SETTINGS['email_from_name'];
1250
1251
        // Prepare recipients
1252
        foreach (array_filter(explode(',', $email)) as $dest) {
1253
            $mail->addAddress($dest);
1254
        }
1255
        
1256
        // Prepare HTML and AltBody
1257
        $text_html = emailBody($textMail);
1258
        $mail->WordWrap = 80;
1259
        $mail->isHtml(true);
1260
        $mail->Subject = $subject;
1261
        $mail->Body = $text_html;
1262
        $mail->AltBody = is_null($textMailAlt) ? '' : $textMailAlt;
1263
        
1264
        // Send email
1265
        $mail->send();
1266
        $mail->smtpClose();
1267
        
1268
        return '';
1269
    } catch (Exception $e) {
1270
        if (!$silent || (int) $SETTINGS['email_debug_level'] !== 0) {
1271
            return json_encode([
1272
                'error' => true,
1273
                'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1274
            ]);
1275
        }
1276
        return '';
1277
    }
1278
}
1279
1280
/**
1281
 * Returns the email body.
1282
 *
1283
 * @param string $textMail Text for the email
1284
 */
1285
function emailBody(string $textMail): string
1286
{
1287
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1288
    w3.org/TR/html4/loose.dtd"><html>
1289
    <head><title>Email Template</title>
1290
    <style type="text/css">
1291
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1292
    </style></head>
1293
    <body style="-ms-text-size-adjust: none; size-adjust: none; margin: 0; padding: 10px 0; background-color: #f0f0f0;" bgcolor="#f0f0f0" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
1294
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1295
    <tr><td style="border-collapse: collapse;"><br>
1296
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1297
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1298
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1299
        </td></tr></table></td>
1300
    </tr>
1301
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1302
        <table width="600" cellpadding="0" cellspacing="0" border="0" class="container" bgcolor="#ffffff" style="border-spacing: 0; border-bottom: 1px solid #e0e0e0; box-shadow: 0 0 3px #ddd; color: #434343; font-family: Helvetica, Verdana, sans-serif;">
1303
        <tr><td class="container-padding" bgcolor="#ffffff" style="border-collapse: collapse; border-left: 1px solid #e0e0e0; background-color: #ffffff; padding-left: 30px; padding-right: 30px;">
1304
        <br><div style="float:right;">' .
1305
        $textMail .
1306
        '<br><br></td></tr></table>
1307
    </td></tr></table>
1308
    <br></body></html>';
1309
}
1310
1311
/**
1312
 * Convert date to timestamp.
1313
 *
1314
 * @param string $date        The date
1315
 * @param string $date_format Date format
1316
 *
1317
 * @return int
1318
 */
1319
function dateToStamp(string $date, string $date_format): int
1320
{
1321
    $date = date_parse_from_format($date_format, $date);
1322
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1323
        return mktime(
1324
            empty($date['hour']) === false ? $date['hour'] : 23,
1325
            empty($date['minute']) === false ? $date['minute'] : 59,
1326
            empty($date['second']) === false ? $date['second'] : 59,
1327
            $date['month'],
1328
            $date['day'],
1329
            $date['year']
1330
        );
1331
    }
1332
    return 0;
1333
}
1334
1335
/**
1336
 * Is this a date.
1337
 *
1338
 * @param string $date Date
1339
 *
1340
 * @return bool
1341
 */
1342
function isDate(string $date): bool
1343
{
1344
    return strtotime($date) !== false;
1345
}
1346
1347
/**
1348
 * Check if isUTF8().
1349
 *
1350
 * @param string|array $string Is the string
1351
 *
1352
 * @return int is the string in UTF8 format
1353
 */
1354
function isUTF8($string): int
1355
{
1356
    if (is_array($string) === true) {
1357
        $string = $string['string'];
1358
    }
1359
1360
    return preg_match(
1361
        '%^(?:
1362
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1363
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1364
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1365
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1366
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1367
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1368
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1369
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1370
        )*$%xs',
1371
        $string
1372
    );
1373
}
1374
1375
/**
1376
 * Prepare an array to UTF8 format before JSON_encode.
1377
 *
1378
 * @param array $array Array of values
1379
 *
1380
 * @return array
1381
 */
1382
function utf8Converter(array $array): array
1383
{
1384
    array_walk_recursive(
1385
        $array,
1386
        static function (&$item): void {
1387
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1388
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1389
            }
1390
        }
1391
    );
1392
    return $array;
1393
}
1394
1395
/**
1396
 * Permits to prepare data to be exchanged.
1397
 *
1398
 * @param array|string $data Text
1399
 * @param string       $type Parameter
1400
 * @param string       $key  Optional key
1401
 *
1402
 * @return string|array
1403
 */
1404
function prepareExchangedData($data, string $type, ?string $key = null)
1405
{
1406
    $session = SessionManager::getSession();
1407
    
1408
    // Perform
1409
    if ($type === 'encode' && is_array($data) === true) {
1410
        // Now encode
1411
        return Encryption::encrypt(
1412
            json_encode(
1413
                $data,
1414
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1415
            ),
1416
            $session->get('key')
1417
        );
1418
    }
1419
    if ($type === 'decode' && is_array($data) === false) {
1420
        // check if key exists
1421
        return json_decode(
1422
            (string) Encryption::decrypt(
1423
                (string) $data,
1424
                $session->get('key')
1425
            ),
1426
            true
1427
        );
1428
    }
1429
    return '';
1430
}
1431
1432
1433
/**
1434
 * Create a thumbnail.
1435
 *
1436
 * @param string  $src           Source
1437
 * @param string  $dest          Destination
1438
 * @param int $desired_width Size of width
1439
 * 
1440
 * @return void|string|bool
1441
 */
1442
function makeThumbnail(string $src, string $dest, int $desired_width)
1443
{
1444
    /* read the source image */
1445
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1446
        $source_image = imagecreatefrompng($src);
1447
        if ($source_image === false) {
1448
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1449
        }
1450
    } else {
1451
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1452
    }
1453
1454
    // Get height and width
1455
    $width = imagesx($source_image);
1456
    $height = imagesy($source_image);
1457
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1458
    $desired_height = (int) floor($height * $desired_width / $width);
1459
    /* create a new, "virtual" image */
1460
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1461
    if ($virtual_image === false) {
1462
        return false;
1463
    }
1464
    /* copy source image at a resized size */
1465
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1466
    /* create the physical thumbnail image to its destination */
1467
    imagejpeg($virtual_image, $dest);
1468
}
1469
1470
/**
1471
 * Check table prefix in SQL query.
1472
 *
1473
 * @param string $table Table name
1474
 * 
1475
 * @return string
1476
 */
1477
function prefixTable(string $table): string
1478
{
1479
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1480
    if (empty($safeTable) === false) {
1481
        // sanitize string
1482
        return $safeTable;
1483
    }
1484
    // stop error no table
1485
    return 'table_not_exists';
1486
}
1487
1488
/**
1489
 * GenerateCryptKey
1490
 *
1491
 * @param int     $size      Length
1492
 * @param bool $secure Secure
1493
 * @param bool $numerals Numerics
1494
 * @param bool $uppercase Uppercase letters
1495
 * @param bool $symbols Symbols
1496
 * @param bool $lowercase Lowercase
1497
 * @param array   $SETTINGS  SETTINGS
1498
 * 
1499
 * @return string
1500
 */
1501
function GenerateCryptKey(
1502
    int $size = 20,
1503
    bool $secure = false,
1504
    bool $numerals = false,
1505
    bool $uppercase = false,
1506
    bool $symbols = false,
1507
    bool $lowercase = false,
1508
    array $SETTINGS = []
1509
): string {
1510
    $generator = new ComputerPasswordGenerator();
1511
    $generator->setRandomGenerator(new Php7RandomGenerator());
1512
    
1513
    // Manage size
1514
    $generator->setLength((int) $size);
1515
    if ($secure === true) {
1516
        $generator->setSymbols(true);
1517
        $generator->setLowercase(true);
1518
        $generator->setUppercase(true);
1519
        $generator->setNumbers(true);
1520
    } else {
1521
        $generator->setLowercase($lowercase);
1522
        $generator->setUppercase($uppercase);
1523
        $generator->setNumbers($numerals);
1524
        $generator->setSymbols($symbols);
1525
    }
1526
1527
    return $generator->generatePasswords()[0];
1528
}
1529
1530
/**
1531
 * Send sysLOG message
1532
 *
1533
 * @param string    $message
1534
 * @param string    $host
1535
 * @param int       $port
1536
 * @param string    $component
1537
 * 
1538
 * @return void
1539
*/
1540
function send_syslog($message, $host, $port, $component = 'teampass'): void
1541
{
1542
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1543
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1544
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1545
    socket_close($sock);
1546
}
1547
1548
/**
1549
 * Permits to log events into DB
1550
 *
1551
 * @param array  $SETTINGS Teampass settings
1552
 * @param string $type     Type
1553
 * @param string $label    Label
1554
 * @param string $who      Who
1555
 * @param string $login    Login
1556
 * @param string|int $field_1  Field
1557
 * 
1558
 * @return void
1559
 */
1560
function logEvents(
1561
    array $SETTINGS, 
1562
    string $type, 
1563
    string $label, 
1564
    string $who, 
1565
    ?string $login = null, 
1566
    $field_1 = null
1567
): void
1568
{
1569
    if (empty($who)) {
1570
        $who = getClientIpServer();
1571
    }
1572
1573
    // Load class DB
1574
    loadClasses('DB');
1575
1576
    DB::insert(
1577
        prefixTable('log_system'),
1578
        [
1579
            'type' => $type,
1580
            'date' => time(),
1581
            'label' => $label,
1582
            'qui' => $who,
1583
            'field_1' => $field_1 === null ? '' : $field_1,
1584
        ]
1585
    );
1586
    // If SYSLOG
1587
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1588
        if ($type === 'user_mngt') {
1589
            send_syslog(
1590
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1591
                $SETTINGS['syslog_host'],
1592
                $SETTINGS['syslog_port'],
1593
                'teampass'
1594
            );
1595
        } else {
1596
            send_syslog(
1597
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1598
                $SETTINGS['syslog_host'],
1599
                $SETTINGS['syslog_port'],
1600
                'teampass'
1601
            );
1602
        }
1603
    }
1604
}
1605
1606
/**
1607
 * Log events.
1608
 *
1609
 * @param array  $SETTINGS        Teampass settings
1610
 * @param int    $item_id         Item id
1611
 * @param string $item_label      Item label
1612
 * @param int    $id_user         User id
1613
 * @param string $action          Code for reason
1614
 * @param string $login           User login
1615
 * @param string $raison          Code for reason
1616
 * @param string $encryption_type Encryption on
1617
 * @param string $time Encryption Time
1618
 * @param string $old_value       Old value
1619
 * 
1620
 * @return void
1621
 */
1622
function logItems(
1623
    array $SETTINGS,
1624
    int $item_id,
1625
    string $item_label,
1626
    int $id_user,
1627
    string $action,
1628
    ?string $login = null,
1629
    ?string $raison = null,
1630
    ?string $encryption_type = null,
1631
    ?string $time = null,
1632
    ?string $old_value = null
1633
): void {
1634
    // Load class DB
1635
    loadClasses('DB');
1636
1637
    // Insert log in DB
1638
    DB::insert(
1639
        prefixTable('log_items'),
1640
        [
1641
            'id_item' => $item_id,
1642
            'date' => is_null($time) === true ? time() : $time,
1643
            'id_user' => $id_user,
1644
            'action' => $action,
1645
            'raison' => $raison,
1646
            'old_value' => $old_value,
1647
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1648
        ]
1649
    );
1650
    // Timestamp the last change
1651
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1652
        DB::update(
1653
            prefixTable('misc'),
1654
            [
1655
                'valeur' => time(),
1656
            ],
1657
            'type = %s AND intitule = %s',
1658
            'timestamp',
1659
            'last_item_change'
1660
        );
1661
    }
1662
1663
    // SYSLOG
1664
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1665
        // Extract reason
1666
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1667
        // Get item info if not known
1668
        if (empty($item_label) === true) {
1669
            $dataItem = DB::queryfirstrow(
1670
                'SELECT id, id_tree, label
1671
                FROM ' . prefixTable('items') . '
1672
                WHERE id = %i',
1673
                $item_id
1674
            );
1675
            $item_label = $dataItem['label'];
1676
        }
1677
1678
        send_syslog(
1679
            'action=' . str_replace('at_', '', $action) .
1680
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1681
                ' itemno=' . $item_id .
1682
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1683
                ' itemname="' . addslashes($item_label) . '"',
1684
            $SETTINGS['syslog_host'],
1685
            $SETTINGS['syslog_port'],
1686
            'teampass'
1687
        );
1688
    }
1689
1690
    // send notification if enabled
1691
    //notifyOnChange($item_id, $action, $SETTINGS);
1692
}
1693
1694
/**
1695
 * Prepare notification email to subscribers.
1696
 *
1697
 * @param int    $item_id  Item id
1698
 * @param string $label    Item label
1699
 * @param array  $changes  List of changes
1700
 * @param array  $SETTINGS Teampass settings
1701
 * 
1702
 * @return void
1703
 */
1704
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1705
{
1706
    $session = SessionManager::getSession();
1707
    $lang = new Language();
1708
    $globalsUserId = $session->get('user-id');
1709
    $globalsLastname = $session->get('user-lastname');
1710
    $globalsName = $session->get('user-name');
1711
    // send email to user that what to be notified
1712
    $notification = DB::queryOneColumn(
1713
        'email',
1714
        'SELECT *
1715
        FROM ' . prefixTable('notification') . ' AS n
1716
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1717
        WHERE n.item_id = %i AND n.user_id != %i',
1718
        $item_id,
1719
        $globalsUserId
1720
    );
1721
    if (DB::count() > 0) {
1722
        // Prepare path
1723
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1724
        // Get list of changes
1725
        $htmlChanges = '<ul>';
1726
        foreach ($changes as $change) {
1727
            $htmlChanges .= '<li>' . $change . '</li>';
1728
        }
1729
        $htmlChanges .= '</ul>';
1730
        // send email
1731
        DB::insert(
1732
            prefixTable('emails'),
1733
            [
1734
                'timestamp' => time(),
1735
                'subject' => $lang->get('email_subject_item_updated'),
1736
                'body' => str_replace(
1737
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1738
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1739
                    $lang->get('email_body_item_updated')
1740
                ),
1741
                'receivers' => implode(',', $notification),
1742
                'status' => '',
1743
            ]
1744
        );
1745
    }
1746
}
1747
1748
/**
1749
 * Returns the Item + path.
1750
 *
1751
 * @param int    $id_tree  Node id
1752
 * @param string $label    Label
1753
 * @param array  $SETTINGS TP settings
1754
 * 
1755
 * @return string
1756
 */
1757
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1758
{
1759
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1760
    $arbo = $tree->getPath($id_tree, true);
1761
    $path = '';
1762
    foreach ($arbo as $elem) {
1763
        if (empty($path) === true) {
1764
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1765
        } else {
1766
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1767
        }
1768
    }
1769
1770
    // Build text to show user
1771
    if (empty($label) === false) {
1772
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1773
    }
1774
    return empty($path) === true ? '' : $path;
1775
}
1776
1777
/**
1778
 * Get the client ip address.
1779
 *
1780
 * @return string IP address
1781
 */
1782
function getClientIpServer(): string
1783
{
1784
    if (getenv('HTTP_CLIENT_IP')) {
1785
        $ipaddress = getenv('HTTP_CLIENT_IP');
1786
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1787
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1788
    } elseif (getenv('HTTP_X_FORWARDED')) {
1789
        $ipaddress = getenv('HTTP_X_FORWARDED');
1790
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1791
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1792
    } elseif (getenv('HTTP_FORWARDED')) {
1793
        $ipaddress = getenv('HTTP_FORWARDED');
1794
    } elseif (getenv('REMOTE_ADDR')) {
1795
        $ipaddress = getenv('REMOTE_ADDR');
1796
    } else {
1797
        $ipaddress = 'UNKNOWN';
1798
    }
1799
1800
    return $ipaddress;
1801
}
1802
1803
/**
1804
 * Escape all HTML, JavaScript, and CSS.
1805
 *
1806
 * @param string $input    The input string
1807
 * @param string $encoding Which character encoding are we using?
1808
 * 
1809
 * @return string
1810
 */
1811
function noHTML(string $input, string $encoding = 'UTF-8'): string
1812
{
1813
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1814
}
1815
1816
/**
1817
 * Permits to handle the Teampass config file
1818
 * $action accepts "rebuild" and "update"
1819
 *
1820
 * @param string $action   Action to perform
1821
 * @param array  $SETTINGS Teampass settings
1822
 * @param string $field    Field to refresh
1823
 * @param string $value    Value to set
1824
 *
1825
 * @return string|bool
1826
 */
1827
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1828
{
1829
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1830
1831
    // Load class DB
1832
    loadClasses('DB');
1833
1834
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1835
        // perform a copy
1836
        if (file_exists($tp_config_file)) {
1837
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1838
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1839
            }
1840
        }
1841
1842
        // regenerate
1843
        $data = [];
1844
        $data[0] = "<?php\n";
1845
        $data[1] = "global \$SETTINGS;\n";
1846
        $data[2] = "\$SETTINGS = array (\n";
1847
        $rows = DB::query(
1848
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1849
            'admin'
1850
        );
1851
        foreach ($rows as $record) {
1852
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
1853
        }
1854
        array_push($data, ");\n");
1855
        $data = array_unique($data);
1856
    // ---
1857
    } elseif ($action === 'update' && empty($field) === false) {
1858
        $data = file($tp_config_file);
1859
        $inc = 0;
1860
        $bFound = false;
1861
        foreach ($data as $line) {
1862
            if (stristr($line, ');')) {
1863
                break;
1864
            }
1865
1866
            if (stristr($line, "'" . $field . "' => '")) {
1867
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
1868
                $bFound = true;
1869
                break;
1870
            }
1871
            ++$inc;
1872
        }
1873
        if ($bFound === false) {
1874
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
1875
        }
1876
    }
1877
1878
    // update file
1879
    file_put_contents($tp_config_file, implode('', $data ?? []));
1880
    return true;
1881
}
1882
1883
/**
1884
 * Permits to replace &#92; to permit correct display
1885
 *
1886
 * @param string $input Some text
1887
 * 
1888
 * @return string
1889
 */
1890
function handleBackslash(string $input): string
1891
{
1892
    return str_replace('&amp;#92;', '&#92;', $input);
1893
}
1894
1895
/**
1896
 * Permits to load settings
1897
 * 
1898
 * @return void
1899
*/
1900
function loadSettings(): void
1901
{
1902
    global $SETTINGS;
1903
    /* LOAD CPASSMAN SETTINGS */
1904
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1905
        $SETTINGS = [];
1906
        $SETTINGS['duplicate_folder'] = 0;
1907
        //by default, this is set to 0;
1908
        $SETTINGS['duplicate_item'] = 0;
1909
        //by default, this is set to 0;
1910
        $SETTINGS['number_of_used_pw'] = 5;
1911
        //by default, this value is set to 5;
1912
        $settings = [];
1913
        $rows = DB::query(
1914
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1915
            [
1916
                'type' => 'admin',
1917
                'type2' => 'settings',
1918
            ]
1919
        );
1920
        foreach ($rows as $record) {
1921
            if ($record['type'] === 'admin') {
1922
                $SETTINGS[$record['intitule']] = $record['valeur'];
1923
            } else {
1924
                $settings[$record['intitule']] = $record['valeur'];
1925
            }
1926
        }
1927
        $SETTINGS['loaded'] = 1;
1928
        $SETTINGS['default_session_expiration_time'] = 5;
1929
    }
1930
}
1931
1932
/**
1933
 * check if folder has custom fields.
1934
 * Ensure that target one also has same custom fields
1935
 * 
1936
 * @param int $source_id
1937
 * @param int $target_id 
1938
 * 
1939
 * @return bool
1940
*/
1941
function checkCFconsistency(int $source_id, int $target_id): bool
1942
{
1943
    $source_cf = [];
1944
    $rows = DB::QUERY(
1945
        'SELECT id_category
1946
            FROM ' . prefixTable('categories_folders') . '
1947
            WHERE id_folder = %i',
1948
        $source_id
1949
    );
1950
    foreach ($rows as $record) {
1951
        array_push($source_cf, $record['id_category']);
1952
    }
1953
1954
    $target_cf = [];
1955
    $rows = DB::QUERY(
1956
        'SELECT id_category
1957
            FROM ' . prefixTable('categories_folders') . '
1958
            WHERE id_folder = %i',
1959
        $target_id
1960
    );
1961
    foreach ($rows as $record) {
1962
        array_push($target_cf, $record['id_category']);
1963
    }
1964
1965
    $cf_diff = array_diff($source_cf, $target_cf);
1966
    if (count($cf_diff) > 0) {
1967
        return false;
1968
    }
1969
1970
    return true;
1971
}
1972
1973
/**
1974
 * Will encrypte/decrypt a fil eusing Defuse.
1975
 *
1976
 * @param string $type        can be either encrypt or decrypt
1977
 * @param string $source_file path to source file
1978
 * @param string $target_file path to target file
1979
 * @param array  $SETTINGS    Settings
1980
 * @param string $password    A password
1981
 *
1982
 * @return string|bool
1983
 */
1984
function prepareFileWithDefuse(
1985
    string $type,
1986
    string $source_file,
1987
    string $target_file,
1988
    array $SETTINGS,
1989
    string $password = null
1990
) {
1991
    // Load AntiXSS
1992
    $antiXss = new AntiXSS();
1993
    // Protect against bad inputs
1994
    if (is_array($source_file) === true || is_array($target_file) === true) {
1995
        return 'error_cannot_be_array';
1996
    }
1997
1998
    // Sanitize
1999
    $source_file = $antiXss->xss_clean($source_file);
2000
    $target_file = $antiXss->xss_clean($target_file);
2001
    if (empty($password) === true || is_null($password) === true) {
2002
        // get KEY to define password
2003
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2004
        $password = Key::loadFromAsciiSafeString($ascii_key);
2005
    }
2006
2007
    $err = '';
2008
    if ($type === 'decrypt') {
2009
        // Decrypt file
2010
        $err = defuseFileDecrypt(
2011
            $source_file,
2012
            $target_file,
2013
            $SETTINGS, /** @scrutinizer ignore-type */
2014
            $password
2015
        );
2016
    } elseif ($type === 'encrypt') {
2017
        // Encrypt file
2018
        $err = defuseFileEncrypt(
2019
            $source_file,
2020
            $target_file,
2021
            $SETTINGS, /** @scrutinizer ignore-type */
2022
            $password
2023
        );
2024
    }
2025
2026
    // return error
2027
    return $err === true ? $err : '';
2028
}
2029
2030
/**
2031
 * Encrypt a file with Defuse.
2032
 *
2033
 * @param string $source_file path to source file
2034
 * @param string $target_file path to target file
2035
 * @param array  $SETTINGS    Settings
2036
 * @param string $password    A password
2037
 *
2038
 * @return string|bool
2039
 */
2040
function defuseFileEncrypt(
2041
    string $source_file,
2042
    string $target_file,
2043
    array $SETTINGS,
2044
    string $password = null
2045
) {
2046
    try {
2047
        CryptoFile::encryptFileWithPassword(
2048
            $source_file,
2049
            $target_file,
2050
            $password
2051
        );
2052
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2053
        $err = 'wrong_key';
2054
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2055
        $err = $ex;
2056
    } catch (CryptoException\IOException $ex) {
2057
        $err = $ex;
2058
    }
2059
2060
    // return error
2061
    return empty($err) === false ? $err : true;
2062
}
2063
2064
/**
2065
 * Decrypt a file with Defuse.
2066
 *
2067
 * @param string $source_file path to source file
2068
 * @param string $target_file path to target file
2069
 * @param array  $SETTINGS    Settings
2070
 * @param string $password    A password
2071
 *
2072
 * @return string|bool
2073
 */
2074
function defuseFileDecrypt(
2075
    string $source_file,
2076
    string $target_file,
2077
    array $SETTINGS,
2078
    string $password = null
2079
) {
2080
    try {
2081
        CryptoFile::decryptFileWithPassword(
2082
            $source_file,
2083
            $target_file,
2084
            $password
2085
        );
2086
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2087
        $err = 'wrong_key';
2088
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2089
        $err = $ex;
2090
    } catch (CryptoException\IOException $ex) {
2091
        $err = $ex;
2092
    }
2093
2094
    // return error
2095
    return empty($err) === false ? $err : true;
2096
}
2097
2098
/*
2099
* NOT TO BE USED
2100
*/
2101
/**
2102
 * Undocumented function.
2103
 *
2104
 * @param string $text Text to debug
2105
 */
2106
function debugTeampass(string $text): void
2107
{
2108
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2109
    if ($debugFile !== false) {
2110
        fputs($debugFile, $text);
2111
        fclose($debugFile);
2112
    }
2113
}
2114
2115
/**
2116
 * DELETE the file with expected command depending on server type.
2117
 *
2118
 * @param string $file     Path to file
2119
 * @param array  $SETTINGS Teampass settings
2120
 *
2121
 * @return void
2122
 */
2123
function fileDelete(string $file, array $SETTINGS): void
2124
{
2125
    // Load AntiXSS
2126
    $antiXss = new AntiXSS();
2127
    $file = $antiXss->xss_clean($file);
2128
    if (is_file($file)) {
2129
        unlink($file);
2130
    }
2131
}
2132
2133
/**
2134
 * Permits to extract the file extension.
2135
 *
2136
 * @param string $file File name
2137
 *
2138
 * @return string
2139
 */
2140
function getFileExtension(string $file): string
2141
{
2142
    if (strpos($file, '.') === false) {
2143
        return $file;
2144
    }
2145
2146
    return substr($file, strrpos($file, '.') + 1);
2147
}
2148
2149
/**
2150
 * Chmods files and folders with different permissions.
2151
 *
2152
 * This is an all-PHP alternative to using: \n
2153
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2154
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2155
 *
2156
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2157
  *
2158
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2159
 * @param int    $filePerm The permissions any found files should get.
2160
 * @param int    $dirPerm  The permissions any found folder should get.
2161
 *
2162
 * @return bool Returns TRUE if the path if found and FALSE if not.
2163
 *
2164
 * @warning The permission levels has to be entered in octal format, which
2165
 * normally means adding a zero ("0") in front of the permission level. \n
2166
 * More info at: http://php.net/chmod.
2167
*/
2168
2169
function recursiveChmod(
2170
    string $path,
2171
    int $filePerm = 0644,
2172
    int  $dirPerm = 0755
2173
) {
2174
    // Check if the path exists
2175
    $path = basename($path);
2176
    if (! file_exists($path)) {
2177
        return false;
2178
    }
2179
2180
    // See whether this is a file
2181
    if (is_file($path)) {
2182
        // Chmod the file with our given filepermissions
2183
        try {
2184
            chmod($path, $filePerm);
2185
        } catch (Exception $e) {
2186
            return false;
2187
        }
2188
    // If this is a directory...
2189
    } elseif (is_dir($path)) {
2190
        // Then get an array of the contents
2191
        $foldersAndFiles = scandir($path);
2192
        // Remove "." and ".." from the list
2193
        $entries = array_slice($foldersAndFiles, 2);
2194
        // Parse every result...
2195
        foreach ($entries as $entry) {
2196
            // And call this function again recursively, with the same permissions
2197
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2198
        }
2199
2200
        // When we are done with the contents of the directory, we chmod the directory itself
2201
        try {
2202
            chmod($path, $filePerm);
2203
        } catch (Exception $e) {
2204
            return false;
2205
        }
2206
    }
2207
2208
    // Everything seemed to work out well, return true
2209
    return true;
2210
}
2211
2212
/**
2213
 * Check if user can access to this item.
2214
 *
2215
 * @param int   $item_id ID of item
2216
 * @param array $SETTINGS
2217
 *
2218
 * @return bool|string
2219
 */
2220
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2221
{
2222
    
2223
    $session = SessionManager::getSession();
2224
    $session_groupes_visibles = $session->get('user-accessible_folders');
2225
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2226
    // Load item data
2227
    $data = DB::queryFirstRow(
2228
        'SELECT id_tree
2229
        FROM ' . prefixTable('items') . '
2230
        WHERE id = %i',
2231
        $item_id
2232
    );
2233
    // Check if user can access this folder
2234
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2235
        // Now check if this folder is restricted to user
2236
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2237
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2238
        ) {
2239
            return 'ERR_FOLDER_NOT_ALLOWED';
2240
        }
2241
    }
2242
2243
    return true;
2244
}
2245
2246
/**
2247
 * Creates a unique key.
2248
 *
2249
 * @param int $lenght Key lenght
2250
 *
2251
 * @return string
2252
 */
2253
function uniqidReal(int $lenght = 13): string
2254
{
2255
    if (function_exists('random_bytes')) {
2256
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2257
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2258
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2259
    } else {
2260
        throw new Exception('no cryptographically secure random function available');
2261
    }
2262
2263
    return substr(bin2hex($bytes), 0, $lenght);
2264
}
2265
2266
/**
2267
 * Obfuscate an email.
2268
 *
2269
 * @param string $email Email address
2270
 *
2271
 * @return string
2272
 */
2273
function obfuscateEmail(string $email): string
2274
{
2275
    $email = explode("@", $email);
2276
    $name = $email[0];
2277
    if (strlen($name) > 3) {
2278
        $name = substr($name, 0, 2);
2279
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2280
            $name .= "*";
2281
        }
2282
        $name .= substr($email[0], -1, 1);
2283
    }
2284
    $host = explode(".", $email[1])[0];
2285
    if (strlen($host) > 3) {
2286
        $host = substr($host, 0, 1);
2287
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2288
            $host .= "*";
2289
        }
2290
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2291
    }
2292
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2293
    return $email;
2294
}
2295
2296
/**
2297
 * Perform a Query.
2298
 *
2299
 * @param array  $SETTINGS Teamapss settings
2300
 * @param string $fields   Fields to use
2301
 * @param string $table    Table to use
2302
 *
2303
 * @return array
2304
 */
2305
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2306
{
2307
    // include librairies & connect to DB
2308
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2309
2310
    // Load class DB
2311
    loadClasses('DB');
2312
    
2313
    // Insert log in DB
2314
    return DB::query(
2315
        'SELECT ' . $fields . '
2316
        FROM ' . prefixTable($table)
2317
    );
2318
}
2319
2320
/**
2321
 * Undocumented function.
2322
 *
2323
 * @param int $bytes Size of file
2324
 *
2325
 * @return string
2326
 */
2327
function formatSizeUnits(int $bytes): string
2328
{
2329
    if ($bytes >= 1073741824) {
2330
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2331
    } elseif ($bytes >= 1048576) {
2332
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2333
    } elseif ($bytes >= 1024) {
2334
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2335
    } elseif ($bytes > 1) {
2336
        $bytes .= ' bytes';
2337
    } elseif ($bytes === 1) {
2338
        $bytes .= ' byte';
2339
    } else {
2340
        $bytes = '0 bytes';
2341
    }
2342
2343
    return $bytes;
2344
}
2345
2346
/**
2347
 * Generate user pair of keys.
2348
 *
2349
 * @param string $userPwd User password
2350
 *
2351
 * @return array
2352
 */
2353
function generateUserKeys(string $userPwd): array
2354
{
2355
    // Sanitize
2356
    $antiXss = new AntiXSS();
2357
    $userPwd = $antiXss->xss_clean($userPwd);
2358
    // Load classes
2359
    $rsa = new Crypt_RSA();
2360
    $cipher = new Crypt_AES();
2361
    // Create the private and public key
2362
    $res = $rsa->createKey(4096);
2363
    // Encrypt the privatekey
2364
    $cipher->setPassword($userPwd);
2365
    $privatekey = $cipher->encrypt($res['privatekey']);
2366
    return [
2367
        'private_key' => base64_encode($privatekey),
2368
        'public_key' => base64_encode($res['publickey']),
2369
        'private_key_clear' => base64_encode($res['privatekey']),
2370
    ];
2371
}
2372
2373
/**
2374
 * Permits to decrypt the user's privatekey.
2375
 *
2376
 * @param string $userPwd        User password
2377
 * @param string $userPrivateKey User private key
2378
 *
2379
 * @return string|object
2380
 */
2381
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2382
{
2383
    // Sanitize
2384
    $antiXss = new AntiXSS();
2385
    $userPwd = $antiXss->xss_clean($userPwd);
2386
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2387
2388
    if (empty($userPwd) === false) {
2389
        // Load classes
2390
        $cipher = new Crypt_AES();
2391
        // Encrypt the privatekey
2392
        $cipher->setPassword($userPwd);
2393
        try {
2394
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2395
        } catch (Exception $e) {
2396
            return $e;
2397
        }
2398
    }
2399
    return '';
2400
}
2401
2402
/**
2403
 * Permits to encrypt the user's privatekey.
2404
 *
2405
 * @param string $userPwd        User password
2406
 * @param string $userPrivateKey User private key
2407
 *
2408
 * @return string
2409
 */
2410
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2411
{
2412
    // Sanitize
2413
    $antiXss = new AntiXSS();
2414
    $userPwd = $antiXss->xss_clean($userPwd);
2415
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2416
2417
    if (empty($userPwd) === false) {
2418
        // Load classes
2419
        $cipher = new Crypt_AES();
2420
        // Encrypt the privatekey
2421
        $cipher->setPassword($userPwd);        
2422
        try {
2423
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2424
        } catch (Exception $e) {
2425
            return $e;
2426
        }
2427
    }
2428
    return '';
2429
}
2430
2431
/**
2432
 * Encrypts a string using AES.
2433
 *
2434
 * @param string $data String to encrypt
2435
 * @param string $key
2436
 *
2437
 * @return array
2438
 */
2439
function doDataEncryption(string $data, string $key = NULL): array
2440
{
2441
    // Sanitize
2442
    $antiXss = new AntiXSS();
2443
    $data = $antiXss->xss_clean($data);
2444
    
2445
    // Load classes
2446
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2447
    // Generate an object key
2448
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2449
    // Set it as password
2450
    $cipher->setPassword($objectKey);
2451
    return [
2452
        'encrypted' => base64_encode($cipher->encrypt($data)),
2453
        'objectKey' => base64_encode($objectKey),
2454
    ];
2455
}
2456
2457
/**
2458
 * Decrypts a string using AES.
2459
 *
2460
 * @param string $data Encrypted data
2461
 * @param string $key  Key to uncrypt
2462
 *
2463
 * @return string
2464
 */
2465
function doDataDecryption(string $data, string $key): string
2466
{
2467
    // Sanitize
2468
    $antiXss = new AntiXSS();
2469
    $data = $antiXss->xss_clean($data);
2470
    $key = $antiXss->xss_clean($key);
2471
2472
    // Load classes
2473
    $cipher = new Crypt_AES();
2474
    // Set the object key
2475
    $cipher->setPassword(base64_decode($key));
2476
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2477
}
2478
2479
/**
2480
 * Encrypts using RSA a string using a public key.
2481
 *
2482
 * @param string $key       Key to be encrypted
2483
 * @param string $publicKey User public key
2484
 *
2485
 * @return string
2486
 */
2487
function encryptUserObjectKey(string $key, string $publicKey): string
2488
{
2489
    // Sanitize
2490
    $antiXss = new AntiXSS();
2491
    $publicKey = $antiXss->xss_clean($publicKey);
2492
    // Load classes
2493
    $rsa = new Crypt_RSA();
2494
    // Load the public key
2495
    $decodedPublicKey = base64_decode($publicKey, true);
2496
    if ($decodedPublicKey === false) {
2497
        throw new InvalidArgumentException("Error while decoding key.");
2498
    }
2499
    $rsa->loadKey($decodedPublicKey);
2500
    // Encrypt
2501
    $encrypted = $rsa->encrypt(base64_decode($key));
2502
    if ($encrypted === false) {
0 ignored issues
show
introduced by
The condition $encrypted === false is always false.
Loading history...
2503
        throw new RuntimeException("Error while encrypting key.");
2504
    }
2505
    // Return
2506
    return base64_encode($encrypted);
2507
}
2508
2509
/**
2510
 * Decrypts using RSA an encrypted string using a private key.
2511
 *
2512
 * @param string $key        Encrypted key
2513
 * @param string $privateKey User private key
2514
 *
2515
 * @return string
2516
 */
2517
function decryptUserObjectKey(string $key, string $privateKey): string
2518
{
2519
    // Sanitize
2520
    $antiXss = new AntiXSS();
2521
    $privateKey = $antiXss->xss_clean($privateKey);
2522
2523
    // Load classes
2524
    $rsa = new Crypt_RSA();
2525
    // Load the private key
2526
    $decodedPrivateKey = base64_decode($privateKey, true);
2527
    if ($decodedPrivateKey === false) {
2528
        throw new InvalidArgumentException("Error while decoding private key.");
2529
    }
2530
2531
    $rsa->loadKey($decodedPrivateKey);
2532
2533
    // Decrypt
2534
    try {
2535
        $decodedKey = base64_decode($key, true);
2536
        if ($decodedKey === false) {
2537
            throw new InvalidArgumentException("Error while decoding key.");
2538
        }
2539
2540
        $tmpValue = $rsa->decrypt($decodedKey);
2541
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2542
            return base64_encode($tmpValue);
2543
        } else {
2544
            return '';
2545
        }
2546
    } catch (Exception $e) {
2547
        error_log('TEAMPASS Error - ldap - '.$e->getMessage());
2548
        return 'Exception: could not decrypt object';
2549
    }
2550
}
2551
2552
/**
2553
 * Encrypts a file.
2554
 *
2555
 * @param string $fileInName File name
2556
 * @param string $fileInPath Path to file
2557
 *
2558
 * @return array
2559
 */
2560
function encryptFile(string $fileInName, string $fileInPath): array
2561
{
2562
    if (defined('FILE_BUFFER_SIZE') === false) {
2563
        define('FILE_BUFFER_SIZE', 128 * 1024);
2564
    }
2565
2566
    // Load classes
2567
    $cipher = new Crypt_AES();
2568
2569
    // Generate an object key
2570
    $objectKey = uniqidReal(32);
2571
    // Set it as password
2572
    $cipher->setPassword($objectKey);
2573
    // Prevent against out of memory
2574
    $cipher->enableContinuousBuffer();
2575
2576
    // Encrypt the file content
2577
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2578
    $fileContent = file_get_contents($filePath);
2579
    $plaintext = $fileContent;
2580
    $ciphertext = $cipher->encrypt($plaintext);
2581
2582
    // Save new file
2583
    // deepcode ignore InsecureHash: is simply used to get a unique name
2584
    $hash = md5($plaintext);
2585
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2586
    file_put_contents($fileOut, $ciphertext);
2587
    unlink($fileInPath . '/' . $fileInName);
2588
    return [
2589
        'fileHash' => base64_encode($hash),
2590
        'objectKey' => base64_encode($objectKey),
2591
    ];
2592
}
2593
2594
/**
2595
 * Decrypt a file.
2596
 *
2597
 * @param string $fileName File name
2598
 * @param string $filePath Path to file
2599
 * @param string $key      Key to use
2600
 *
2601
 * @return string
2602
 */
2603
function decryptFile(string $fileName, string $filePath, string $key): string
2604
{
2605
    if (! defined('FILE_BUFFER_SIZE')) {
2606
        define('FILE_BUFFER_SIZE', 128 * 1024);
2607
    }
2608
    
2609
    // Load classes
2610
    $cipher = new Crypt_AES();
2611
    $antiXSS = new AntiXSS();
2612
    
2613
    // Get file name
2614
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2615
2616
    // Set the object key
2617
    $cipher->setPassword(base64_decode($key));
2618
    // Prevent against out of memory
2619
    $cipher->enableContinuousBuffer();
2620
    $cipher->disablePadding();
2621
    // Get file content
2622
    $safeFilePath = realpath($filePath . '/' . TP_FILE_PREFIX . $safeFileName);
2623
    $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2624
2625
    if (WIP) error_log('DEBUG: File image url -> '.filter_var($safeFilePath, FILTER_SANITIZE_URL));
2626
2627
    // Decrypt file content and return
2628
    return base64_encode($cipher->decrypt($ciphertext));
2629
}
2630
2631
/**
2632
 * Generate a simple password
2633
 *
2634
 * @param int $length Length of string
2635
 * @param bool $symbolsincluded Allow symbols
2636
 *
2637
 * @return string
2638
 */
2639
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2640
{
2641
    // Generate new user password
2642
    $small_letters = range('a', 'z');
2643
    $big_letters = range('A', 'Z');
2644
    $digits = range(0, 9);
2645
    $symbols = $symbolsincluded === true ?
2646
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2647
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2648
    $count = count($res);
2649
    // first variant
2650
2651
    $random_string = '';
2652
    for ($i = 0; $i < $length; ++$i) {
2653
        $random_string .= $res[random_int(0, $count - 1)];
2654
    }
2655
2656
    return $random_string;
2657
}
2658
2659
/**
2660
 * Permit to store the sharekey of an object for users.
2661
 *
2662
 * @param string $object_name             Type for table selection
2663
 * @param int    $post_folder_is_personal Personal
2664
 * @param int    $post_folder_id          Folder
2665
 * @param int    $post_object_id          Object
2666
 * @param string $objectKey               Object key
2667
 * @param array  $SETTINGS                Teampass settings
2668
 * @param int    $user_id                 User ID if needed
2669
 * @param bool   $onlyForUser                 User ID if needed
2670
 * @param bool   $deleteAll                 User ID if needed
2671
 * @param array  $objectKeyArray                 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
): void {
2686
    
2687
    $session = SessionManager::getSession();
2688
    loadClasses('DB');
2689
2690
    // Delete existing entries for this object
2691
    if ($deleteAll === true) {
2692
        DB::delete(
2693
            $object_name,
2694
            'object_id = %i',
2695
            $post_object_id
2696
        );
2697
    }
2698
    
2699
    if (
2700
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2701
    ) {
2702
        // Only create the sharekey for a user
2703
        $user = DB::queryFirstRow(
2704
            'SELECT public_key
2705
            FROM ' . prefixTable('users') . '
2706
            WHERE id = ' . (int) $session->get('user-id') . '
2707
            AND public_key != ""'
2708
        );
2709
2710
        if (empty($objectKey) === false) {
2711
            DB::insert(
2712
                $object_name,
2713
                [
2714
                    'object_id' => (int) $post_object_id,
2715
                    'user_id' => (int) $session->get('user-id'),
2716
                    'share_key' => encryptUserObjectKey(
2717
                        $objectKey,
2718
                        $user['public_key']
2719
                    ),
2720
                ]
2721
            );
2722
        } else if (count($objectKeyArray) > 0) {
2723
            foreach ($objectKeyArray as $object) {
2724
                DB::insert(
2725
                    $object_name,
2726
                    [
2727
                        'object_id' => (int) $object['objectId'],
2728
                        'user_id' => (int) $session->get('user-id'),
2729
                        'share_key' => encryptUserObjectKey(
2730
                            $object['objectKey'],
2731
                            $user['public_key']
2732
                        ),
2733
                    ]
2734
                );
2735
            }
2736
        }
2737
    } else {
2738
        // Create sharekey for each user
2739
        //error_log('Building QUERY - all_users_except_id: '. $all_users_except_id);
2740
        //DB::debugmode(true);
2741
        $users = DB::query(
2742
            'SELECT id, public_key
2743
            FROM ' . prefixTable('users') . '
2744
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2745
                'id IN ("' . TP_USER_ID . '","' . $session->get('user-id') . '") ' : 
2746
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '"'.($all_users_except_id === -1 ? '' : ', "'.$all_users_except_id.'"').') ') . '
2747
            AND public_key != ""'
2748
        );
2749
        //DB::debugmode(false);
2750
        foreach ($users as $user) {
2751
            // Insert in DB the new object key for this item by user
2752
            if (count($objectKeyArray) === 0) {
2753
                if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case1 - ' . $object_name . ' - ' . $post_object_id . ' - ' . $user['id'] . ' - ' . $objectKey);
2754
                DB::insert(
2755
                    $object_name,
2756
                    [
2757
                        'object_id' => $post_object_id,
2758
                        'user_id' => (int) $user['id'],
2759
                        'share_key' => encryptUserObjectKey(
2760
                            $objectKey,
2761
                            $user['public_key']
2762
                        ),
2763
                    ]
2764
                );
2765
            } else {
2766
                foreach ($objectKeyArray as $object) {
2767
                    if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case2 - ' . $object_name . ' - ' . $object['objectId'] . ' - ' . $user['id'] . ' - ' . $object['objectKey']);
2768
                    DB::insert(
2769
                        $object_name,
2770
                        [
2771
                            'object_id' => (int) $object['objectId'],
2772
                            'user_id' => (int) $user['id'],
2773
                            'share_key' => encryptUserObjectKey(
2774
                                $object['objectKey'],
2775
                                $user['public_key']
2776
                            ),
2777
                        ]
2778
                    );
2779
                }
2780
            }
2781
        }
2782
    }
2783
}
2784
2785
/**
2786
 * Is this string base64 encoded?
2787
 *
2788
 * @param string $str Encoded string?
2789
 *
2790
 * @return bool
2791
 */
2792
function isBase64(string $str): bool
2793
{
2794
    $str = (string) trim($str);
2795
    if (! isset($str[0])) {
2796
        return false;
2797
    }
2798
2799
    $base64String = (string) base64_decode($str, true);
2800
    if ($base64String && base64_encode($base64String) === $str) {
2801
        return true;
2802
    }
2803
2804
    return false;
2805
}
2806
2807
/**
2808
 * Undocumented function
2809
 *
2810
 * @param string $field Parameter
2811
 *
2812
 * @return array|bool|resource|string
2813
 */
2814
function filterString(string $field)
2815
{
2816
    // Sanitize string
2817
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2818
    if (empty($field) === false) {
2819
        // Load AntiXSS
2820
        $antiXss = new AntiXSS();
2821
        // Return
2822
        return $antiXss->xss_clean($field);
2823
    }
2824
2825
    return false;
2826
}
2827
2828
/**
2829
 * CHeck if provided credentials are allowed on server
2830
 *
2831
 * @param string $login    User Login
2832
 * @param string $password User Pwd
2833
 * @param array  $SETTINGS Teampass settings
2834
 *
2835
 * @return bool
2836
 */
2837
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2838
{
2839
    // Build ldap configuration array
2840
    $config = [
2841
        // Mandatory Configuration Options
2842
        'hosts' => [$SETTINGS['ldap_hosts']],
2843
        'base_dn' => $SETTINGS['ldap_bdn'],
2844
        'username' => $SETTINGS['ldap_username'],
2845
        'password' => $SETTINGS['ldap_password'],
2846
2847
        // Optional Configuration Options
2848
        'port' => $SETTINGS['ldap_port'],
2849
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2850
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2851
        'version' => 3,
2852
        'timeout' => 5,
2853
        'follow_referrals' => false,
2854
2855
        // Custom LDAP Options
2856
        'options' => [
2857
            // See: http://php.net/ldap_set_option
2858
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2859
        ],
2860
    ];
2861
    
2862
    $connection = new Connection($config);
2863
    // Connect to LDAP
2864
    try {
2865
        $connection->connect();
2866
    } catch (\LdapRecord\Auth\BindException $e) {
2867
        $error = $e->getDetailedError();
2868
        error_log('TEAMPASS Error - Auth - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2869
        // deepcode ignore ServerLeak: No important data is sent
2870
        echo 'An error occurred.';
2871
        return false;
2872
    }
2873
2874
    // Authenticate user
2875
    try {
2876
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2877
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2878
        } else {
2879
            $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);
2880
        }
2881
    } catch (\LdapRecord\Auth\BindException $e) {
2882
        $error = $e->getDetailedError();
2883
        error_log('TEAMPASS Error - Auth - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2884
        // deepcode ignore ServerLeak: No important data is sent
2885
        echo 'An error occurred.';
2886
        return false;
2887
    }
2888
2889
    return true;
2890
}
2891
2892
/**
2893
 * Removes from DB all sharekeys of this user
2894
 *
2895
 * @param int $userId User's id
2896
 * @param array   $SETTINGS Teampass settings
2897
 *
2898
 * @return bool
2899
 */
2900
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2901
{
2902
    // Load class DB
2903
    loadClasses('DB');
2904
2905
    // Remove all item sharekeys items
2906
    // expect if personal item
2907
    DB::delete(
2908
        prefixTable('sharekeys_items'),
2909
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2910
        $userId
2911
    );
2912
    // Remove all item sharekeys files
2913
    DB::delete(
2914
        prefixTable('sharekeys_files'),
2915
        'user_id = %i AND object_id NOT IN (
2916
            SELECT f.id 
2917
            FROM ' . prefixTable('items') . ' AS i 
2918
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2919
            WHERE i.perso = 1
2920
        )',
2921
        $userId
2922
    );
2923
    // Remove all item sharekeys fields
2924
    DB::delete(
2925
        prefixTable('sharekeys_fields'),
2926
        'user_id = %i AND object_id NOT IN (
2927
            SELECT c.id 
2928
            FROM ' . prefixTable('items') . ' AS i 
2929
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2930
            WHERE i.perso = 1
2931
        )',
2932
        $userId
2933
    );
2934
    // Remove all item sharekeys logs
2935
    DB::delete(
2936
        prefixTable('sharekeys_logs'),
2937
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2938
        $userId
2939
    );
2940
    // Remove all item sharekeys suggestions
2941
    DB::delete(
2942
        prefixTable('sharekeys_suggestions'),
2943
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2944
        $userId
2945
    );
2946
    return false;
2947
}
2948
2949
/**
2950
 * Manage list of timezones   $SETTINGS Teampass settings
2951
 *
2952
 * @return array
2953
 */
2954
function timezone_list()
2955
{
2956
    static $timezones = null;
2957
    if ($timezones === null) {
2958
        $timezones = [];
2959
        $offsets = [];
2960
        $now = new DateTime('now', new DateTimeZone('UTC'));
2961
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2962
            $now->setTimezone(new DateTimeZone($timezone));
2963
            $offsets[] = $offset = $now->getOffset();
2964
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2965
        }
2966
2967
        array_multisort($offsets, $timezones);
2968
    }
2969
2970
    return $timezones;
2971
}
2972
2973
/**
2974
 * Provide timezone offset
2975
 *
2976
 * @param int $offset Timezone offset
2977
 *
2978
 * @return string
2979
 */
2980
function format_GMT_offset($offset): string
2981
{
2982
    $hours = intval($offset / 3600);
2983
    $minutes = abs(intval($offset % 3600 / 60));
2984
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2985
}
2986
2987
/**
2988
 * Provides timezone name
2989
 *
2990
 * @param string $name Timezone name
2991
 *
2992
 * @return string
2993
 */
2994
function format_timezone_name($name): string
2995
{
2996
    $name = str_replace('/', ', ', $name);
2997
    $name = str_replace('_', ' ', $name);
2998
2999
    return str_replace('St ', 'St. ', $name);
3000
}
3001
3002
/**
3003
 * Provides info if user should use MFA based on roles
3004
 *
3005
 * @param string $userRolesIds  User roles ids
3006
 * @param string $mfaRoles      Roles for which MFA is requested
3007
 *
3008
 * @return bool
3009
 */
3010
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3011
{
3012
    if (empty($mfaRoles) === true) {
3013
        return true;
3014
    }
3015
3016
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3017
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3018
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3019
        return true;
3020
    }
3021
3022
    return false;
3023
}
3024
3025
/**
3026
 * Permits to clean a string for export purpose
3027
 *
3028
 * @param string $text
3029
 * @param bool $emptyCheckOnly
3030
 * 
3031
 * @return string
3032
 */
3033
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3034
{
3035
    if (is_null($text) === true || empty($text) === true) {
3036
        return '';
3037
    }
3038
    // only expected to check if $text was empty
3039
    elseif ($emptyCheckOnly === true) {
3040
        return $text;
3041
    }
3042
3043
    return strip_tags(
3044
        cleanString(
3045
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3046
            true)
3047
        );
3048
}
3049
3050
/**
3051
 * Permits to check if user ID is valid
3052
 *
3053
 * @param integer $post_user_id
3054
 * @return bool
3055
 */
3056
function isUserIdValid($userId): bool
3057
{
3058
    if (is_null($userId) === false
3059
        && isset($userId) === true
3060
        && empty($userId) === false
3061
    ) {
3062
        return true;
3063
    }
3064
    return false;
3065
}
3066
3067
/**
3068
 * Check if a key exists and if its value equal the one expected
3069
 *
3070
 * @param string $key
3071
 * @param integer|string $value
3072
 * @param array $array
3073
 * 
3074
 * @return boolean
3075
 */
3076
function isKeyExistingAndEqual(
3077
    string $key,
3078
    /*PHP8 - integer|string*/$value,
3079
    array $array
3080
): bool
3081
{
3082
    if (isset($array[$key]) === true
3083
        && (is_int($value) === true ?
3084
            (int) $array[$key] === $value :
3085
            (string) $array[$key] === $value)
3086
    ) {
3087
        return true;
3088
    }
3089
    return false;
3090
}
3091
3092
/**
3093
 * Check if a variable is not set or equal to a value
3094
 *
3095
 * @param string|null $var
3096
 * @param integer|string $value
3097
 * 
3098
 * @return boolean
3099
 */
3100
function isKeyNotSetOrEqual(
3101
    /*PHP8 - string|null*/$var,
3102
    /*PHP8 - integer|string*/$value
3103
): bool
3104
{
3105
    if (isset($var) === false
3106
        || (is_int($value) === true ?
3107
            (int) $var === $value :
3108
            (string) $var === $value)
3109
    ) {
3110
        return true;
3111
    }
3112
    return false;
3113
}
3114
3115
/**
3116
 * Check if a key exists and if its value < to the one expected
3117
 *
3118
 * @param string $key
3119
 * @param integer $value
3120
 * @param array $array
3121
 * 
3122
 * @return boolean
3123
 */
3124
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3125
{
3126
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3127
        return true;
3128
    }
3129
    return false;
3130
}
3131
3132
/**
3133
 * Check if a key exists and if its value > to the one expected
3134
 *
3135
 * @param string $key
3136
 * @param integer $value
3137
 * @param array $array
3138
 * 
3139
 * @return boolean
3140
 */
3141
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3142
{
3143
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3144
        return true;
3145
    }
3146
    return false;
3147
}
3148
3149
/**
3150
 * Check if values in array are set
3151
 * Return true if all set
3152
 * Return false if one of them is not set
3153
 *
3154
 * @param array $arrayOfValues
3155
 * @return boolean
3156
 */
3157
function isSetArrayOfValues(array $arrayOfValues): bool
3158
{
3159
    foreach($arrayOfValues as $value) {
3160
        if (isset($value) === false) {
3161
            return false;
3162
        }
3163
    }
3164
    return true;
3165
}
3166
3167
/**
3168
 * Check if values in array are set
3169
 * Return true if all set
3170
 * Return false if one of them is not set
3171
 *
3172
 * @param array $arrayOfValues
3173
 * @param integer|string $value
3174
 * @return boolean
3175
 */
3176
function isArrayOfVarsEqualToValue(
3177
    array $arrayOfVars,
3178
    /*PHP8 - integer|string*/$value
3179
) : bool
3180
{
3181
    foreach($arrayOfVars as $variable) {
3182
        if ($variable !== $value) {
3183
            return false;
3184
        }
3185
    }
3186
    return true;
3187
}
3188
3189
/**
3190
 * Checks if at least one variable in array is equal to value
3191
 *
3192
 * @param array $arrayOfValues
3193
 * @param integer|string $value
3194
 * @return boolean
3195
 */
3196
function isOneVarOfArrayEqualToValue(
3197
    array $arrayOfVars,
3198
    /*PHP8 - integer|string*/$value
3199
) : bool
3200
{
3201
    foreach($arrayOfVars as $variable) {
3202
        if ($variable === $value) {
3203
            return true;
3204
        }
3205
    }
3206
    return false;
3207
}
3208
3209
/**
3210
 * Checks is value is null, not set OR empty
3211
 *
3212
 * @param string|int|null $value
3213
 * @return boolean
3214
 */
3215
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3216
{
3217
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3218
        return true;
3219
    }
3220
    return false;
3221
}
3222
3223
/**
3224
 * Checks if value is set and if empty is equal to passed boolean
3225
 *
3226
 * @param string|int $value
3227
 * @param boolean $boolean
3228
 * @return boolean
3229
 */
3230
function isValueSetEmpty($value, $boolean = true) : bool
3231
{
3232
    if (isset($value) === true && empty($value) === $boolean) {
3233
        return true;
3234
    }
3235
    return false;
3236
}
3237
3238
/**
3239
 * Ensure Complexity is translated
3240
 *
3241
 * @return void
3242
 */
3243
function defineComplexity() : void
3244
{
3245
    // Load user's language
3246
    $lang = new Language();
3247
    
3248
    if (defined('TP_PW_COMPLEXITY') === false) {
3249
        define(
3250
            'TP_PW_COMPLEXITY',
3251
            [
3252
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3253
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3254
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3255
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3256
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3257
            ]
3258
        );
3259
    }
3260
}
3261
3262
/**
3263
 * Uses Sanitizer to perform data sanitization
3264
 *
3265
 * @param array     $data
3266
 * @param array     $filters
3267
 * @return array|string
3268
 */
3269
function dataSanitizer(array $data, array $filters): array|string
3270
{
3271
    // Load Sanitizer library
3272
    $sanitizer = new Sanitizer($data, $filters);
3273
3274
    // Load AntiXSS
3275
    $antiXss = new AntiXSS();
3276
3277
    // Sanitize post and get variables
3278
    return $antiXss->xss_clean($sanitizer->sanitize());
3279
}
3280
3281
/**
3282
 * Permits to manage the cache tree for a user
3283
 *
3284
 * @param integer $user_id
3285
 * @param string $data
3286
 * @param array $SETTINGS
3287
 * @param string $field_update
3288
 * @return void
3289
 */
3290
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3291
{
3292
    // Load class DB
3293
    loadClasses('DB');
3294
3295
    // Exists ?
3296
    $userCacheId = DB::queryfirstrow(
3297
        'SELECT increment_id
3298
        FROM ' . prefixTable('cache_tree') . '
3299
        WHERE user_id = %i',
3300
        $user_id
3301
    );
3302
    
3303
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3304
        // insert in table
3305
        DB::insert(
3306
            prefixTable('cache_tree'),
3307
            array(
3308
                'data' => $data,
3309
                'timestamp' => time(),
3310
                'user_id' => $user_id,
3311
                'visible_folders' => '',
3312
            )
3313
        );
3314
    } else {
3315
        if (empty($field_update) === true) {
3316
            DB::update(
3317
                prefixTable('cache_tree'),
3318
                [
3319
                    'timestamp' => time(),
3320
                    'data' => $data,
3321
                ],
3322
                'increment_id = %i',
3323
                $userCacheId['increment_id']
3324
            );
3325
        /* USELESS
3326
        } else {
3327
            DB::update(
3328
                prefixTable('cache_tree'),
3329
                [
3330
                    $field_update => $data,
3331
                ],
3332
                'increment_id = %i',
3333
                $userCacheId['increment_id']
3334
            );*/
3335
        }
3336
    }
3337
}
3338
3339
/**
3340
 * Permits to calculate a %
3341
 *
3342
 * @param float $nombre
3343
 * @param float $total
3344
 * @param float $pourcentage
3345
 * @return float
3346
 */
3347
function pourcentage(float $nombre, float $total, float $pourcentage): float
3348
{ 
3349
    $resultat = ($nombre/$total) * $pourcentage;
3350
    return round($resultat);
3351
}
3352
3353
/**
3354
 * Load the folders list from the cache
3355
 *
3356
 * @param string $fieldName
3357
 * @param string $sessionName
3358
 * @param boolean $forceRefresh
3359
 * @return array
3360
 */
3361
function loadFoldersListByCache(
3362
    string $fieldName,
3363
    string $sessionName,
3364
    bool $forceRefresh = false
3365
): array
3366
{
3367
    // Case when refresh is EXPECTED / MANDATORY
3368
    if ($forceRefresh === true) {
3369
        return [
3370
            'state' => false,
3371
            'data' => [],
3372
        ];
3373
    }
3374
    
3375
    $session = SessionManager::getSession();
3376
3377
    // Get last folder update
3378
    $lastFolderChange = DB::queryfirstrow(
3379
        'SELECT valeur FROM ' . prefixTable('misc') . '
3380
        WHERE type = %s AND intitule = %s',
3381
        'timestamp',
3382
        'last_folder_change'
3383
    );
3384
    if (DB::count() === 0) {
3385
        $lastFolderChange['valeur'] = 0;
3386
    }
3387
3388
    // Case when an update in the tree has been done
3389
    // Refresh is then mandatory
3390
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3391
        return [
3392
            'state' => false,
3393
            'data' => [],
3394
        ];
3395
    }
3396
3397
    // Does this user has the tree structure in session?
3398
    // If yes then use it
3399
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3400
        return [
3401
            'state' => true,
3402
            'data' => json_encode($session->get('user-folders_list')),
3403
        ];
3404
    }
3405
3406
    // Does this user has a tree cache
3407
    $userCacheTree = DB::queryfirstrow(
3408
        'SELECT '.$fieldName.'
3409
        FROM ' . prefixTable('cache_tree') . '
3410
        WHERE user_id = %i',
3411
        $session->get('user-id')
3412
    );
3413
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3414
        SessionManager::addRemoveFromSessionAssociativeArray(
3415
            'user-folders_list',
3416
            [$userCacheTree[$fieldName]],
3417
            'add'
3418
        );
3419
        return [
3420
            'state' => true,
3421
            'data' => $userCacheTree[$fieldName],
3422
        ];
3423
    }
3424
3425
    return [
3426
        'state' => false,
3427
        'data' => [],
3428
    ];
3429
}
3430
3431
3432
/**
3433
 * Permits to refresh the categories of folders
3434
 *
3435
 * @param array $folderIds
3436
 * @return void
3437
 */
3438
function handleFoldersCategories(
3439
    array $folderIds
3440
)
3441
{
3442
    // Load class DB
3443
    loadClasses('DB');
3444
3445
    $arr_data = array();
3446
3447
    // force full list of folders
3448
    if (count($folderIds) === 0) {
3449
        $folderIds = DB::queryFirstColumn(
3450
            'SELECT id
3451
            FROM ' . prefixTable('nested_tree') . '
3452
            WHERE personal_folder=%i',
3453
            0
3454
        );
3455
    }
3456
3457
    // Get complexity
3458
    defineComplexity();
3459
3460
    // update
3461
    foreach ($folderIds as $folder) {
3462
        // Do we have Categories
3463
        // get list of associated Categories
3464
        $arrCatList = array();
3465
        $rows_tmp = DB::query(
3466
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3467
            f.id_category AS category_id
3468
            FROM ' . prefixTable('categories_folders') . ' AS f
3469
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3470
            WHERE id_folder=%i',
3471
            $folder
3472
        );
3473
        if (DB::count() > 0) {
3474
            foreach ($rows_tmp as $row) {
3475
                $arrCatList[$row['id']] = array(
3476
                    'id' => $row['id'],
3477
                    'title' => $row['title'],
3478
                    'level' => $row['level'],
3479
                    'type' => $row['type'],
3480
                    'masked' => $row['masked'],
3481
                    'order' => $row['order'],
3482
                    'encrypted_data' => $row['encrypted_data'],
3483
                    'role_visibility' => $row['role_visibility'],
3484
                    'is_mandatory' => $row['is_mandatory'],
3485
                    'category_id' => $row['category_id'],
3486
                );
3487
            }
3488
        }
3489
        $arr_data['categories'] = $arrCatList;
3490
3491
        // Now get complexity
3492
        $valTemp = '';
3493
        $data = DB::queryFirstRow(
3494
            'SELECT valeur
3495
            FROM ' . prefixTable('misc') . '
3496
            WHERE type = %s AND intitule=%i',
3497
            'complex',
3498
            $folder
3499
        );
3500
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3501
            $valTemp = array(
3502
                'value' => $data['valeur'],
3503
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3504
            );
3505
        }
3506
        $arr_data['complexity'] = $valTemp;
3507
3508
        // Now get Roles
3509
        $valTemp = '';
3510
        $rows_tmp = DB::query(
3511
            'SELECT t.title
3512
            FROM ' . prefixTable('roles_values') . ' as v
3513
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3514
            WHERE v.folder_id = %i
3515
            GROUP BY title',
3516
            $folder
3517
        );
3518
        foreach ($rows_tmp as $record) {
3519
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3520
        }
3521
        $arr_data['visibilityRoles'] = $valTemp;
3522
3523
        // now save in DB
3524
        DB::update(
3525
            prefixTable('nested_tree'),
3526
            array(
3527
                'categories' => json_encode($arr_data),
3528
            ),
3529
            'id = %i',
3530
            $folder
3531
        );
3532
    }
3533
}
3534
3535
/**
3536
 * List all users that have specific roles
3537
 *
3538
 * @param array $roles
3539
 * @return array
3540
 */
3541
function getUsersWithRoles(
3542
    array $roles
3543
): array
3544
{
3545
    $session = SessionManager::getSession();
3546
    $arrUsers = array();
3547
3548
    foreach ($roles as $role) {
3549
        // loop on users and check if user has this role
3550
        $rows = DB::query(
3551
            'SELECT id, fonction_id
3552
            FROM ' . prefixTable('users') . '
3553
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3554
            $session->get('user-id')
3555
        );
3556
        foreach ($rows as $user) {
3557
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3558
            if (in_array($role, $userRoles, true) === true) {
3559
                array_push($arrUsers, $user['id']);
3560
            }
3561
        }
3562
    }
3563
3564
    return $arrUsers;
3565
}
3566
3567
3568
/**
3569
 * Get all users informations
3570
 *
3571
 * @param integer $userId
3572
 * @return array
3573
 */
3574
function getFullUserInfos(
3575
    int $userId
3576
): array
3577
{
3578
    if (empty($userId) === true) {
3579
        return array();
3580
    }
3581
3582
    $val = DB::queryfirstrow(
3583
        'SELECT *
3584
        FROM ' . prefixTable('users') . '
3585
        WHERE id = %i',
3586
        $userId
3587
    );
3588
3589
    return $val;
3590
}
3591
3592
/**
3593
 * Is required an upgrade
3594
 *
3595
 * @return boolean
3596
 */
3597
function upgradeRequired(): bool
3598
{
3599
    // Get settings.php
3600
    include_once __DIR__. '/../includes/config/settings.php';
3601
3602
    // Get timestamp in DB
3603
    $val = DB::queryfirstrow(
3604
        'SELECT valeur
3605
        FROM ' . prefixTable('misc') . '
3606
        WHERE type = %s AND intitule = %s',
3607
        'admin',
3608
        'upgrade_timestamp'
3609
    );
3610
    
3611
    // if not exists then error
3612
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3613
3614
    // if empty or too old then error
3615
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3616
        return true;
3617
    }
3618
3619
    return false;
3620
}
3621
3622
/**
3623
 * Permits to change the user keys on his demand
3624
 *
3625
 * @param integer $userId
3626
 * @param string $passwordClear
3627
 * @param integer $nbItemsToTreat
3628
 * @param string $encryptionKey
3629
 * @param boolean $deleteExistingKeys
3630
 * @param boolean $sendEmailToUser
3631
 * @param boolean $encryptWithUserPassword
3632
 * @param boolean $generate_user_new_password
3633
 * @param string $emailBody
3634
 * @param boolean $user_self_change
3635
 * @param string $recovery_public_key
3636
 * @param string $recovery_private_key
3637
 * @return string
3638
 */
3639
function handleUserKeys(
3640
    int $userId,
3641
    string $passwordClear,
3642
    int $nbItemsToTreat,
3643
    string $encryptionKey = '',
3644
    bool $deleteExistingKeys = false,
3645
    bool $sendEmailToUser = true,
3646
    bool $encryptWithUserPassword = false,
3647
    bool $generate_user_new_password = false,
3648
    string $emailBody = '',
3649
    bool $user_self_change = false,
3650
    string $recovery_public_key = '',
3651
    string $recovery_private_key = ''
3652
): string
3653
{
3654
    $session = SessionManager::getSession();
3655
    $lang = new Language();
3656
3657
    // prepapre background tasks for item keys generation        
3658
    $userTP = DB::queryFirstRow(
3659
        'SELECT pw, public_key, private_key
3660
        FROM ' . prefixTable('users') . '
3661
        WHERE id = %i',
3662
        TP_USER_ID
3663
    );
3664
    if (DB::count() === 0) {
3665
        return prepareExchangedData(
3666
            array(
3667
                'error' => true,
3668
                'message' => 'User not exists',
3669
            ),
3670
            'encode'
3671
        );
3672
    }
3673
3674
    // Do we need to generate new user password
3675
    if ($generate_user_new_password === true) {
3676
        // Generate a new password
3677
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3678
    }
3679
3680
    // Create password hash
3681
    $passwordManager = new PasswordManager();
3682
    $hashedPassword = $passwordManager->hashPassword($passwordClear);
3683
    if ($passwordManager->verifyPassword($hashedPassword, $passwordClear) === false) {
3684
        return prepareExchangedData(
3685
            array(
3686
                'error' => true,
3687
                'message' => $lang->get('pw_hash_not_correct'),
3688
            ),
3689
            'encode'
3690
        );
3691
    }
3692
3693
    // Generate new keys
3694
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3695
        $userKeys = [
3696
            'public_key' => $recovery_public_key,
3697
            'private_key_clear' => $recovery_private_key,
3698
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3699
        ];
3700
    } else {
3701
        $userKeys = generateUserKeys($passwordClear);
3702
    }
3703
3704
    // Save in DB
3705
    DB::update(
3706
        prefixTable('users'),
3707
        array(
3708
            'pw' => $hashedPassword,
3709
            'public_key' => $userKeys['public_key'],
3710
            'private_key' => $userKeys['private_key'],
3711
        ),
3712
        'id=%i',
3713
        $userId
3714
    );
3715
3716
    // update session too
3717
    if ($userId === $session->get('user-id')) {
3718
        $session->set('user-private_key', $userKeys['private_key_clear']);
3719
        $session->set('user-public_key', $userKeys['public_key']);
3720
    }
3721
3722
    // Manage empty encryption key
3723
    // Let's take the user's password if asked and if no encryption key provided
3724
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3725
3726
    // Create process
3727
    DB::insert(
3728
        prefixTable('processes'),
3729
        array(
3730
            'created_at' => time(),
3731
            'process_type' => 'create_user_keys',
3732
            'arguments' => json_encode([
3733
                'new_user_id' => (int) $userId,
3734
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3735
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3736
                'owner_id' => (int) TP_USER_ID,
3737
                'creator_pwd' => $userTP['pw'],
3738
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3739
                'otp_provided_new_value' => 1,
3740
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3741
                'user_self_change' => $user_self_change === true ? 1 : 0,
3742
            ]),
3743
            'updated_at' => '',
3744
            'finished_at' => '',
3745
            'output' => '',
3746
        )
3747
    );
3748
    $processId = DB::insertId();
3749
3750
    // Delete existing keys
3751
    if ($deleteExistingKeys === true) {
3752
        deleteUserObjetsKeys(
3753
            (int) $userId,
3754
        );
3755
    }
3756
3757
    // Create tasks
3758
    createUserTasks($processId, $nbItemsToTreat);
3759
3760
    // update user's new status
3761
    DB::update(
3762
        prefixTable('users'),
3763
        [
3764
            'is_ready_for_usage' => 0,
3765
            'otp_provided' => 1,
3766
            'ongoing_process_id' => $processId,
3767
            'special' => 'generate-keys',
3768
        ],
3769
        'id=%i',
3770
        $userId
3771
    );
3772
3773
    return prepareExchangedData(
3774
        array(
3775
            'error' => false,
3776
            'message' => '',
3777
            'user_password' => $generate_user_new_password === true ? $passwordClear : '',
3778
        ),
3779
        'encode'
3780
    );
3781
}
3782
3783
/**
3784
 * Permits to generate a new password for a user
3785
 *
3786
 * @param integer $processId
3787
 * @param integer $nbItemsToTreat
3788
 * @return void
3789
 
3790
 */
3791
function createUserTasks($processId, $nbItemsToTreat): void
3792
{
3793
    DB::insert(
3794
        prefixTable('processes_tasks'),
3795
        array(
3796
            'process_id' => $processId,
3797
            'created_at' => time(),
3798
            'task' => json_encode([
3799
                'step' => 'step0',
3800
                'index' => 0,
3801
                'nb' => $nbItemsToTreat,
3802
            ]),
3803
        )
3804
    );
3805
3806
    DB::insert(
3807
        prefixTable('processes_tasks'),
3808
        array(
3809
            'process_id' => $processId,
3810
            'created_at' => time(),
3811
            'task' => json_encode([
3812
                'step' => 'step10',
3813
                'index' => 0,
3814
                'nb' => $nbItemsToTreat,
3815
            ]),
3816
        )
3817
    );
3818
3819
    DB::insert(
3820
        prefixTable('processes_tasks'),
3821
        array(
3822
            'process_id' => $processId,
3823
            'created_at' => time(),
3824
            'task' => json_encode([
3825
                'step' => 'step20',
3826
                'index' => 0,
3827
                'nb' => $nbItemsToTreat,
3828
            ]),
3829
        )
3830
    );
3831
3832
    DB::insert(
3833
        prefixTable('processes_tasks'),
3834
        array(
3835
            'process_id' => $processId,
3836
            'created_at' => time(),
3837
            'task' => json_encode([
3838
                'step' => 'step30',
3839
                'index' => 0,
3840
                'nb' => $nbItemsToTreat,
3841
            ]),
3842
        )
3843
    );
3844
3845
    DB::insert(
3846
        prefixTable('processes_tasks'),
3847
        array(
3848
            'process_id' => $processId,
3849
            'created_at' => time(),
3850
            'task' => json_encode([
3851
                'step' => 'step40',
3852
                'index' => 0,
3853
                'nb' => $nbItemsToTreat,
3854
            ]),
3855
        )
3856
    );
3857
3858
    DB::insert(
3859
        prefixTable('processes_tasks'),
3860
        array(
3861
            'process_id' => $processId,
3862
            'created_at' => time(),
3863
            'task' => json_encode([
3864
                'step' => 'step50',
3865
                'index' => 0,
3866
                'nb' => $nbItemsToTreat,
3867
            ]),
3868
        )
3869
    );
3870
3871
    DB::insert(
3872
        prefixTable('processes_tasks'),
3873
        array(
3874
            'process_id' => $processId,
3875
            'created_at' => time(),
3876
            'task' => json_encode([
3877
                'step' => 'step60',
3878
                'index' => 0,
3879
                'nb' => $nbItemsToTreat,
3880
            ]),
3881
        )
3882
    );
3883
}
3884
3885
/**
3886
 * Permeits to check the consistency of date versus columns definition
3887
 *
3888
 * @param string $table
3889
 * @param array $dataFields
3890
 * @return array
3891
 */
3892
function validateDataFields(
3893
    string $table,
3894
    array $dataFields
3895
): array
3896
{
3897
    // Get table structure
3898
    $result = DB::query(
3899
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3900
        DB_NAME,
3901
        $table
3902
    );
3903
3904
    foreach ($result as $row) {
3905
        $field = $row['COLUMN_NAME'];
3906
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3907
3908
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3909
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3910
                return [
3911
                    'state' => false,
3912
                    'field' => $field,
3913
                    'maxLength' => $maxLength,
3914
                    'currentLength' => strlen((string) $dataFields[$field]),
3915
                ];
3916
            }
3917
        }
3918
    }
3919
    
3920
    return [
3921
        'state' => true,
3922
        'message' => '',
3923
    ];
3924
}
3925
3926
/**
3927
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3928
 *
3929
 * @param string $string
3930
 * @return string
3931
 */
3932
function filterVarBack(string $string): string
3933
{
3934
    $arr = [
3935
        '&#060;' => '<',
3936
        '&#062;' => '>',
3937
        '&#034;' => '"',
3938
        '&#039;' => "'",
3939
        '&#038;' => '&',
3940
    ];
3941
3942
    foreach ($arr as $key => $value) {
3943
        $string = str_replace($key, $value, $string);
3944
    }
3945
3946
    return $string;
3947
}
3948
3949
/**
3950
 * 
3951
 */
3952
function storeTask(
3953
    string $taskName,
3954
    int $user_id,
3955
    int $is_personal_folder,
3956
    int $folder_destination_id,
3957
    int $item_id,
3958
    string $object_keys,
3959
    array $fields_keys = [],
3960
    array $files_keys = []
3961
)
3962
{
3963
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3964
        // Create process
3965
        DB::insert(
3966
            prefixTable('processes'),
3967
            array(
3968
                'created_at' => time(),
3969
                'process_type' => $taskName,
3970
                'arguments' => json_encode([
3971
                    'item_id' => $item_id,
3972
                    'object_key' => $object_keys,
3973
                ]),
3974
                'updated_at' => '',
3975
                'finished_at' => '',
3976
                'output' => '',
3977
                'item_id' => $item_id,
3978
            )
3979
        );
3980
        $processId = DB::insertId();
3981
3982
        // Create tasks
3983
        // 1- Create password sharekeys for users of this new ITEM
3984
        DB::insert(
3985
            prefixTable('processes_tasks'),
3986
            array(
3987
                'process_id' => $processId,
3988
                'created_at' => time(),
3989
                'task' => json_encode([
3990
                    'step' => 'create_users_pwd_key',
3991
                    'index' => 0,
3992
                ]),
3993
            )
3994
        );
3995
3996
        // 2- Create fields sharekeys for users of this new ITEM
3997
        DB::insert(
3998
            prefixTable('processes_tasks'),
3999
            array(
4000
                'process_id' => $processId,
4001
                'created_at' => time(),
4002
                'task' => json_encode([
4003
                    'step' => 'create_users_fields_key',
4004
                    'index' => 0,
4005
                    'fields_keys' => $fields_keys,
4006
                ]),
4007
            )
4008
        );
4009
4010
        // 3- Create files sharekeys for users of this new ITEM
4011
        DB::insert(
4012
            prefixTable('processes_tasks'),
4013
            array(
4014
                'process_id' => $processId,
4015
                'created_at' => time(),
4016
                'task' => json_encode([
4017
                    'step' => 'create_users_files_key',
4018
                    'index' => 0,
4019
                    'files_keys' => $files_keys,
4020
                ]),
4021
            )
4022
        );
4023
    }
4024
}
4025
4026
/**
4027
 * 
4028
 */
4029
function createTaskForItem(
4030
    string $processType,
4031
    string|array $taskName,
4032
    int $itemId,
4033
    int $userId,
4034
    string $objectKey,
4035
    int $parentId = -1,
4036
    array $fields_keys = [],
4037
    array $files_keys = []
4038
)
4039
{
4040
    // 1- Create main process
4041
    // ---
4042
    
4043
    // Create process
4044
    DB::insert(
4045
        prefixTable('processes'),
4046
        array(
4047
            'created_at' => time(),
4048
            'process_type' => $processType,
4049
            'arguments' => json_encode([
4050
                'all_users_except_id' => (int) $userId,
4051
                'item_id' => (int) $itemId,
4052
                'object_key' => $objectKey,
4053
                'author' => (int) $userId,
4054
            ]),
4055
            'updated_at' => '',
4056
            'finished_at' => '',
4057
            'output' => '',
4058
            'item_id' => (int) $parentId !== -1 ?  $parentId : null,
4059
        )
4060
    );
4061
    $processId = DB::insertId();
4062
4063
    // 2- Create expected tasks
4064
    // ---
4065
    if (is_array($taskName) === false) {
0 ignored issues
show
introduced by
The condition is_array($taskName) === false is always false.
Loading history...
4066
        $taskName = [$taskName];
4067
    }
4068
    foreach($taskName as $task) {
4069
        error_log('createTaskForItem - task: '.$task);
4070
        switch ($task) {
4071
            case 'item_password':
4072
                
4073
                DB::insert(
4074
                    prefixTable('processes_tasks'),
4075
                    array(
4076
                        'process_id' => $processId,
4077
                        'created_at' => time(),
4078
                        'task' => json_encode([
4079
                            'step' => 'create_users_pwd_key',
4080
                            'index' => 0,
4081
                        ]),
4082
                    )
4083
                );
4084
4085
                break;
4086
            case 'item_field':
4087
                
4088
                DB::insert(
4089
                    prefixTable('processes_tasks'),
4090
                    array(
4091
                        'process_id' => $processId,
4092
                        'created_at' => time(),
4093
                        'task' => json_encode([
4094
                            'step' => 'create_users_fields_key',
4095
                            'index' => 0,
4096
                            'fields_keys' => $fields_keys,
4097
                        ]),
4098
                    )
4099
                );
4100
4101
                break;
4102
            case 'item_file':
4103
4104
                DB::insert(
4105
                    prefixTable('processes_tasks'),
4106
                    array(
4107
                        'process_id' => $processId,
4108
                        'created_at' => time(),
4109
                        'task' => json_encode([
4110
                            'step' => 'create_users_files_key',
4111
                            'index' => 0,
4112
                            'fields_keys' => $files_keys,
4113
                        ]),
4114
                    )
4115
                );
4116
                break;
4117
            default:
4118
                # code...
4119
                break;
4120
        }
4121
    }
4122
}
4123
4124
4125
function deleteProcessAndRelatedTasks(int $processId)
4126
{
4127
    // Delete process
4128
    DB::delete(
4129
        prefixTable('processes'),
4130
        'id=%i',
4131
        $processId
4132
    );
4133
4134
    // Delete tasks
4135
    DB::delete(
4136
        prefixTable('processes_tasks'),
4137
        'process_id=%i',
4138
        $processId
4139
    );
4140
4141
}
4142
4143
/**
4144
 * Return PHP binary path
4145
 *
4146
 * @return string
4147
 */
4148
function getPHPBinary(): string
4149
{
4150
    // Get PHP binary path
4151
    $phpBinaryFinder = new PhpExecutableFinder();
4152
    $phpBinaryPath = $phpBinaryFinder->find();
4153
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4154
}
4155
4156
4157
4158
/**
4159
 * Delete unnecessary keys for personal items
4160
 *
4161
 * @param boolean $allUsers
4162
 * @param integer $user_id
4163
 * @return void
4164
 */
4165
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4166
{
4167
    if ($allUsers === true) {
4168
        // Load class DB
4169
        if (class_exists('DB') === false) {
4170
            loadClasses('DB');
4171
        }
4172
4173
        $users = DB::query(
4174
            'SELECT id
4175
            FROM ' . prefixTable('users') . '
4176
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4177
            ORDER BY login ASC'
4178
        );
4179
        foreach ($users as $user) {
4180
            purgeUnnecessaryKeysForUser((int) $user['id']);
4181
        }
4182
    } else {
4183
        purgeUnnecessaryKeysForUser((int) $user_id);
4184
    }
4185
}
4186
4187
/**
4188
 * Delete unnecessary keys for personal items
4189
 *
4190
 * @param integer $user_id
4191
 * @return void
4192
 */
4193
function purgeUnnecessaryKeysForUser(int $user_id=0)
4194
{
4195
    if ($user_id === 0) {
4196
        return;
4197
    }
4198
4199
    // Load class DB
4200
    loadClasses('DB');
4201
4202
    $personalItems = DB::queryFirstColumn(
4203
        'SELECT id
4204
        FROM ' . prefixTable('items') . ' AS i
4205
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4206
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4207
        $user_id
4208
    );
4209
    if (count($personalItems) > 0) {
4210
        // Item keys
4211
        DB::delete(
4212
            prefixTable('sharekeys_items'),
4213
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4214
            $personalItems,
4215
            $user_id
4216
        );
4217
        // Files keys
4218
        DB::delete(
4219
            prefixTable('sharekeys_files'),
4220
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4221
            $personalItems,
4222
            $user_id
4223
        );
4224
        // Fields keys
4225
        DB::delete(
4226
            prefixTable('sharekeys_fields'),
4227
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4228
            $personalItems,
4229
            $user_id
4230
        );
4231
        // Logs keys
4232
        DB::delete(
4233
            prefixTable('sharekeys_logs'),
4234
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4235
            $personalItems,
4236
            $user_id
4237
        );
4238
    }
4239
}
4240
4241
/**
4242
 * Generate recovery keys file
4243
 *
4244
 * @param integer $userId
4245
 * @param array $SETTINGS
4246
 * @return string
4247
 */
4248
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4249
{
4250
    $session = SessionManager::getSession();
4251
    // Check if user exists
4252
    $userInfo = DB::queryFirstRow(
4253
        'SELECT pw, public_key, private_key, login, name
4254
        FROM ' . prefixTable('users') . '
4255
        WHERE id = %i',
4256
        $userId
4257
    );
4258
4259
    if (DB::count() > 0) {
4260
        $now = (int) time();
4261
4262
        // Prepare file content
4263
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4264
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4265
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4266
            "Public Key:\n".$userInfo['public_key']."\n\n".
4267
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4268
4269
        // Update user's keys_recovery_time
4270
        DB::update(
4271
            prefixTable('users'),
4272
            [
4273
                'keys_recovery_time' => $now,
4274
            ],
4275
            'id=%i',
4276
            $userId
4277
        );
4278
        $session->set('user-keys_recovery_time', $now);
4279
4280
        //Log into DB the user's disconnection
4281
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4282
        
4283
        // Return data
4284
        return prepareExchangedData(
4285
            array(
4286
                'error' => false,
4287
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4288
                'timestamp' => $now,
4289
                'content' => base64_encode($export_value),
4290
                'login' => $userInfo['login'],
4291
            ),
4292
            'encode'
4293
        );
4294
    }
4295
4296
    return prepareExchangedData(
4297
        array(
4298
            'error' => true,
4299
            'datetime' => '',
4300
        ),
4301
        'encode'
4302
    );
4303
}
4304
4305
/**
4306
 * Permits to load expected classes
4307
 *
4308
 * @param string $className
4309
 * @return void
4310
 */
4311
function loadClasses(string $className = ''): void
4312
{
4313
    require_once __DIR__. '/../includes/config/include.php';
4314
    require_once __DIR__. '/../includes/config/settings.php';
4315
    require_once __DIR__.'/../vendor/autoload.php';
4316
4317
    if (defined('DB_PASSWD_CLEAR') === false) {
4318
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4319
    }
4320
4321
    if (empty($className) === false) {
4322
        // Load class DB
4323
        if ((string) $className === 'DB') {
4324
            //Connect to DB
4325
            DB::$host = DB_HOST;
4326
            DB::$user = DB_USER;
4327
            DB::$password = DB_PASSWD_CLEAR;
4328
            DB::$dbName = DB_NAME;
4329
            DB::$port = DB_PORT;
4330
            DB::$encoding = DB_ENCODING;
4331
            DB::$ssl = DB_SSL;
4332
            DB::$connect_options = DB_CONNECT_OPTIONS;
4333
        }
4334
    }
4335
4336
}
4337
4338
/**
4339
 * Returns the page the user is visiting.
4340
 *
4341
 * @return string The page name
4342
 */
4343
function getCurrectPage($SETTINGS)
4344
{
4345
    
4346
    $request = SymfonyRequest::createFromGlobals();
4347
4348
    // Parse the url
4349
    parse_str(
4350
        substr(
4351
            (string) $request->getRequestUri(),
4352
            strpos((string) $request->getRequestUri(), '?') + 1
4353
        ),
4354
        $result
4355
    );
4356
4357
    return $result['page'];
4358
}
4359
4360
/**
4361
 * Permits to return value if set
4362
 *
4363
 * @param string|int $value
4364
 * @param string|int|null $retFalse
4365
 * @param string|int $retTrue
4366
 * @return mixed
4367
 */
4368
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4369
{
4370
4371
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4372
}