Passed
Pull Request — master (#4268)
by
unknown
06:31
created

handleUserKeys()   D

Complexity

Conditions 18
Paths 77

Size

Total Lines 163
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 18
eloc 88
c 6
b 0
f 0
nc 77
nop 12
dl 0
loc 163
rs 4.8666

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

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

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

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

4421
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4422
            $post_body
4423
        );
4424
    }
4425
4426
    if ($immediate_email === true) {
4427
        $ret = sendEmail(
4428
            $post_subject,
4429
            $post_body,
4430
            $post_receipt,
4431
            $SETTINGS,
4432
            '',
4433
            false
4434
        );
4435
    
4436
        $ret = json_decode($ret, true);
4437
    
4438
        return prepareExchangedData(
4439
            array(
4440
                'error' => empty($ret['error']) === true ? false : true,
4441
                'message' => $ret['message'],
4442
            ),
4443
            'encode'
4444
        );
4445
    } else {
4446
        // Send through task handler
4447
        prepareSendingEmail(
4448
            $post_subject,
4449
            $post_body,
4450
            $post_receipt,
4451
            ""
4452
        );
4453
    }
4454
4455
    return null;
4456
}
4457
4458
/**
4459
 * Converts a password strengh value to zxcvbn level
4460
 * 
4461
 * @param integer $passwordStrength
4462
 * 
4463
 * @return integer
4464
 */
4465
function convertPasswordStrength($passwordStrength): int
4466
{
4467
    if ($passwordStrength === 0) {
4468
        return TP_PW_STRENGTH_1;
4469
    } else if ($passwordStrength === 1) {
4470
        return TP_PW_STRENGTH_2;
4471
    } else if ($passwordStrength === 2) {
4472
        return TP_PW_STRENGTH_3;
4473
    } else if ($passwordStrength === 3) {
4474
        return TP_PW_STRENGTH_4;
4475
    } else {
4476
        return TP_PW_STRENGTH_5;
4477
    }
4478
}