Passed
Pull Request — master (#4304)
by
unknown
12:05
created

buildEmail()   F

Complexity

Conditions 10
Paths 804

Size

Total Lines 85
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 10
eloc 44
c 3
b 0
f 0
nc 804
nop 7
dl 0
loc 85
rs 3.7722

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.functions.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use LdapRecord\Connection;
33
use ForceUTF8\Encoding;
34
use Elegant\Sanitizer\Sanitizer;
35
use voku\helper\AntiXSS;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use TeampassClasses\SessionManager\SessionManager;
39
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
40
use TeampassClasses\Language\Language;
41
use TeampassClasses\NestedTree\NestedTree;
42
use Defuse\Crypto\Key;
43
use Defuse\Crypto\Crypto;
44
use Defuse\Crypto\KeyProtectedByPassword;
45
use Defuse\Crypto\File as CryptoFile;
46
use Defuse\Crypto\Exception as CryptoException;
47
use Elegant\Sanitizer\Filters\Uppercase;
48
use PHPMailer\PHPMailer\PHPMailer;
49
use 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
66
/**
67
 * genHash().
68
 *
69
 * Generate a hash for user login
70
 *
71
 * @param string $password What password
72
 * @param string $cost     What cost
73
 *
74
 * @return string|void
75
 */
76
/* TODO - Remove this function
77
function bCrypt(
78
    string $password,
79
    string $cost
80
): ?string
81
{
82
    $salt = sprintf('$2y$%02d$', $cost);
83
    if (function_exists('openssl_random_pseudo_bytes')) {
84
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
85
    } else {
86
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
87
        for ($i = 0; $i < 22; ++$i) {
88
            $salt .= $chars[mt_rand(0, 63)];
89
        }
90
    }
91
92
    return crypt($password, $salt);
93
}
94
*/
95
96
/**
97
 * Checks if a string is hex encoded
98
 *
99
 * @param string $str
100
 * @return boolean
101
 */
102
function isHex(string $str): bool
103
{
104
    if (str_starts_with(strtolower($str), '0x')) {
105
        $str = substr($str, 2);
106
    }
107
108
    return ctype_xdigit($str);
109
}
110
111
/**
112
 * Defuse cryption function.
113
 *
114
 * @param string $message   what to de/crypt
115
 * @param string $ascii_key key to use
116
 * @param string $type      operation to perform
117
 * @param array  $SETTINGS  Teampass settings
118
 *
119
 * @return array
120
 */
121
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
122
{
123
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
124
    $err = false;
125
    
126
    // convert KEY
127
    $key = Key::loadFromAsciiSafeString($ascii_key);
128
    try {
129
        if ($type === 'encrypt') {
130
            $text = Crypto::encrypt($message, $key);
131
        } elseif ($type === 'decrypt') {
132
            $text = Crypto::decrypt($message, $key);
133
        }
134
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
135
        $err = 'an attack! either the wrong key was loaded, or the ciphertext has changed since it was created either corrupted in the database or intentionally modified by someone trying to carry out an attack.';
136
    } catch (CryptoException\BadFormatException $ex) {
137
        $err = $ex;
138
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
139
        $err = $ex;
140
    } catch (CryptoException\CryptoException $ex) {
141
        $err = $ex;
142
    } catch (CryptoException\IOException $ex) {
143
        $err = $ex;
144
    }
145
146
    return [
147
        'string' => $text ?? '',
148
        'error' => $err,
149
    ];
150
}
151
152
/**
153
 * Generating a defuse key.
154
 *
155
 * @return string
156
 */
157
function defuse_generate_key()
158
{
159
    $key = Key::createNewRandomKey();
160
    $key = $key->saveToAsciiSafeString();
161
    return $key;
162
}
163
164
/**
165
 * Generate a Defuse personal key.
166
 *
167
 * @param string $psk psk used
168
 *
169
 * @return string
170
 */
171
function defuse_generate_personal_key(string $psk): string
172
{
173
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
174
    return $protected_key->saveToAsciiSafeString(); // save this in user table
175
}
176
177
/**
178
 * Validate persoanl key with defuse.
179
 *
180
 * @param string $psk                   the user's psk
181
 * @param string $protected_key_encoded special key
182
 *
183
 * @return string
184
 */
185
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
186
{
187
    try {
188
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
189
        $user_key = $protected_key_encoded->unlockKey($psk);
190
        $user_key_encoded = $user_key->saveToAsciiSafeString();
191
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
192
        return 'Error - Major issue as the encryption is broken.';
193
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
194
        return 'Error - The saltkey is not the correct one.';
195
    }
196
197
    return $user_key_encoded;
198
    // store it in session once user has entered his psk
199
}
200
201
/**
202
 * Decrypt a defuse string if encrypted.
203
 *
204
 * @param string $value Encrypted string
205
 *
206
 * @return string Decrypted string
207
 */
208
function defuseReturnDecrypted(string $value, $SETTINGS): string
209
{
210
    if (substr($value, 0, 3) === 'def') {
211
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
212
    }
213
214
    return $value;
215
}
216
217
/**
218
 * Trims a string depending on a specific string.
219
 *
220
 * @param string|array $chaine  what to trim
221
 * @param string       $element trim on what
222
 *
223
 * @return string
224
 */
225
function trimElement($chaine, string $element): string
226
{
227
    if (! empty($chaine)) {
228
        if (is_array($chaine) === true) {
229
            $chaine = implode(';', $chaine);
230
        }
231
        $chaine = trim($chaine);
232
        if (substr($chaine, 0, 1) === $element) {
233
            $chaine = substr($chaine, 1);
234
        }
235
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
236
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
237
        }
238
    }
239
240
    return $chaine;
241
}
242
243
/**
244
 * Permits to suppress all "special" characters from string.
245
 *
246
 * @param string $string  what to clean
247
 * @param bool   $special use of special chars?
248
 *
249
 * @return string
250
 */
251
function cleanString(string $string, bool $special = false): string
252
{
253
    // Create temporary table for special characters escape
254
    $tabSpecialChar = [];
255
    for ($i = 0; $i <= 31; ++$i) {
256
        $tabSpecialChar[] = chr($i);
257
    }
258
    array_push($tabSpecialChar, '<br />');
259
    if ((int) $special === 1) {
260
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
261
    }
262
263
    return str_replace($tabSpecialChar, "\n", $string);
264
}
265
266
/**
267
 * Erro manager for DB.
268
 *
269
 * @param array $params output from query
270
 *
271
 * @return void
272
 */
273
function db_error_handler(array $params): void
274
{
275
    echo 'Error: ' . $params['error'] . "<br>\n";
276
    echo 'Query: ' . $params['query'] . "<br>\n";
277
    throw new Exception('Error - Query', 1);
278
}
279
280
/**
281
 * Identify user's rights
282
 *
283
 * @param string|array $groupesVisiblesUser  [description]
284
 * @param string|array $groupesInterditsUser [description]
285
 * @param string       $isAdmin              [description]
286
 * @param string       $idFonctions          [description]
287
 *
288
 * @return bool
289
 */
290
function identifyUserRights(
291
    $groupesVisiblesUser,
292
    $groupesInterditsUser,
293
    $isAdmin,
294
    $idFonctions,
295
    $SETTINGS
296
) {
297
    $session = SessionManager::getSession();
298
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
299
300
    // Check if user is ADMINISTRATOR    
301
    (int) $isAdmin === 1 ?
302
        identAdmin(
303
            $idFonctions,
304
            $SETTINGS, /** @scrutinizer ignore-type */
305
            $tree
306
        )
307
        :
308
        identUser(
309
            $groupesVisiblesUser,
310
            $groupesInterditsUser,
311
            $idFonctions,
312
            $SETTINGS, /** @scrutinizer ignore-type */
313
            $tree
314
        );
315
316
    // update user's timestamp
317
    DB::update(
318
        prefixTable('users'),
319
        [
320
            'timestamp' => time(),
321
        ],
322
        'id=%i',
323
        $session->get('user-id')
324
    );
325
326
    return true;
327
}
328
329
/**
330
 * Identify administrator.
331
 *
332
 * @param string $idFonctions Roles of user
333
 * @param array  $SETTINGS    Teampass settings
334
 * @param object $tree        Tree of folders
335
 *
336
 * @return bool
337
 */
338
function identAdmin($idFonctions, $SETTINGS, $tree)
339
{
340
    
341
    $session = SessionManager::getSession();
342
    $groupesVisibles = [];
343
    $session->set('user-personal_folders', []);
344
    $session->set('user-accessible_folders', []);
345
    $session->set('user-no_access_folders', []);
346
    $session->set('user-personal_visible_folders', []);
347
    $session->set('user-read_only_folders', []);
348
    $session->set('system-list_restricted_folders_for_items', []);
349
    $session->set('system-list_folders_editable_by_role', []);
350
    $session->set('user-list_folders_limited', []);
351
    $session->set('user-forbiden_personal_folders', []);
352
    $globalsUserId = $session->get('user-id');
353
    $globalsVisibleFolders = $session->get('user-accessible_folders');
354
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
355
    // Get list of Folders
356
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
357
    foreach ($rows as $record) {
358
        array_push($groupesVisibles, $record['id']);
359
    }
360
    $session->set('user-accessible_folders', $groupesVisibles);
361
    $session->set('user-all_non_personal_folders', $groupesVisibles);
362
    // Exclude all PF
363
    $where = new WhereClause('and');
364
    // create a WHERE statement of pieces joined by ANDs
365
    $where->add('personal_folder=%i', 1);
366
    if (
367
        isset($SETTINGS['enable_pf_feature']) === true
368
        && (int) $SETTINGS['enable_pf_feature'] === 1
369
    ) {
370
        $where->add('title=%s', $globalsUserId);
371
        $where->negateLast();
372
    }
373
    // Get ID of personal folder
374
    $persfld = DB::queryfirstrow(
375
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
376
        $globalsUserId
377
    );
378
    if (empty($persfld['id']) === false) {
379
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
380
            array_push($globalsVisibleFolders, $persfld['id']);
381
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
382
            // get all descendants
383
            $tree->rebuild();
384
            $tst = $tree->getDescendants($persfld['id']);
385
            foreach ($tst as $t) {
386
                array_push($globalsVisibleFolders, $t->id);
387
                array_push($globalsPersonalVisibleFolders, $t->id);
388
            }
389
        }
390
    }
391
392
    // get complete list of ROLES
393
    $tmp = explode(';', $idFonctions);
394
    $rows = DB::query(
395
        'SELECT * FROM ' . prefixTable('roles_title') . '
396
        ORDER BY title ASC'
397
    );
398
    foreach ($rows as $record) {
399
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
400
            array_push($tmp, $record['id']);
401
        }
402
    }
403
    $session->set('user-roles', implode(';', $tmp));
404
    $session->set('user-admin', 1);
405
    // Check if admin has created Folders and Roles
406
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
407
    $session->set('user-nb_folders', DB::count());
408
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
409
    $session->set('user-nb_roles', DB::count());
410
411
    return true;
412
}
413
414
/**
415
 * Permits to convert an element to array.
416
 *
417
 * @param string|array $element Any value to be returned as array
418
 *
419
 * @return array
420
 */
421
function convertToArray($element): array
422
{
423
    if (is_string($element) === true) {
424
        if (empty($element) === true) {
425
            return [];
426
        }
427
        return explode(
428
            ';',
429
            trimElement($element, ';')
430
        );
431
    }
432
    return $element;
433
}
434
435
/**
436
 * Defines the rights the user has.
437
 *
438
 * @param string|array $allowedFolders  Allowed folders
439
 * @param string|array $noAccessFolders Not allowed folders
440
 * @param string|array $userRoles       Roles of user
441
 * @param array        $SETTINGS        Teampass settings
442
 * @param object       $tree            Tree of folders
443
 * 
444
 * @return bool
445
 */
446
function identUser(
447
    $allowedFolders,
448
    $noAccessFolders,
449
    $userRoles,
450
    array $SETTINGS,
451
    object $tree
452
) {
453
    
454
    $session = SessionManager::getSession();
455
    // Init
456
    $session->set('user-accessible_folders', []);
457
    $session->set('user-personal_folders', []);
458
    $session->set('user-no_access_folders', []);
459
    $session->set('user-personal_visible_folders', []);
460
    $session->set('user-read_only_folders', []);
461
    $session->set('user-user-roles', $userRoles);
462
    $session->set('user-admin', 0);
463
    // init
464
    $personalFolders = [];
465
    $readOnlyFolders = [];
466
    $noAccessPersonalFolders = [];
467
    $restrictedFoldersForItems = [];
468
    $foldersLimited = [];
469
    $foldersLimitedFull = [];
470
    $allowedFoldersByRoles = [];
471
    $globalsUserId = $session->get('user-id');
472
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
473
    // Ensure consistency in array format
474
    $noAccessFolders = convertToArray($noAccessFolders);
475
    $userRoles = convertToArray($userRoles);
476
    $allowedFolders = convertToArray($allowedFolders);
477
    
478
    // Get list of folders depending on Roles
479
    $arrays = identUserGetFoldersFromRoles(
480
        $userRoles,
481
        $allowedFoldersByRoles,
482
        $readOnlyFolders,
483
        $allowedFolders
484
    );
485
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
486
    $readOnlyFolders = $arrays['readOnlyFolders'];
487
488
    // Does this user is allowed to see other items
489
    $inc = 0;
490
    $rows = DB::query(
491
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
492
            WHERE restricted_to LIKE %ss AND inactif = %s'.
493
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
494
        $globalsUserId,
495
        '0'
496
    );
497
    foreach ($rows as $record) {
498
        // Exclude restriction on item if folder is fully accessible
499
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
500
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
501
            ++$inc;
502
        //}
503
    }
504
505
    // Check for the users roles if some specific rights exist on items
506
    $rows = DB::query(
507
        'SELECT i.id_tree, r.item_id
508
        FROM ' . prefixTable('items') . ' as i
509
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
510
        WHERE i.id_tree <> "" '.
511
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
512
        'ORDER BY i.id_tree ASC',
513
        $userRoles
514
    );
515
    $inc = 0;
516
    foreach ($rows as $record) {
517
        //if (isset($record['id_tree'])) {
518
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
519
            array_push($foldersLimitedFull, $record['id_tree']);
520
            ++$inc;
521
        //}
522
    }
523
524
    // Get list of Personal Folders
525
    $arrays = identUserGetPFList(
526
        $globalsPersonalFolders,
527
        $allowedFolders,
528
        $globalsUserId,
529
        $personalFolders,
530
        $noAccessPersonalFolders,
531
        $foldersLimitedFull,
532
        $allowedFoldersByRoles,
533
        array_keys($restrictedFoldersForItems),
534
        $readOnlyFolders,
535
        $noAccessFolders,
536
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
537
        $tree
538
    );
539
    $allowedFolders = $arrays['allowedFolders'];
540
    $personalFolders = $arrays['personalFolders'];
541
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
542
543
    // Return data
544
    $session->set('user-all_non_personal_folders', $allowedFolders);
545
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
546
    $session->set('user-read_only_folders', $readOnlyFolders);
547
    $session->set('user-no_access_folders', $noAccessFolders);
548
    $session->set('user-personal_folders', $personalFolders);
549
    $session->set('user-list_folders_limited', $foldersLimited);
550
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
551
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
552
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
553
    $session->set(
554
        'all_folders_including_no_access',
555
        array_unique(array_merge(
556
            $allowedFolders,
557
            $personalFolders,
558
            $noAccessFolders,
559
            $readOnlyFolders
560
        ), SORT_NUMERIC)
561
    );
562
    // Folders and Roles numbers
563
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
564
    $session->set('user-nb_folders', DB::count());
565
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
566
    $session->set('user-nb_roles', DB::count());
567
    // check if change proposals on User's items
568
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
569
        $countNewItems = DB::query(
570
            'SELECT COUNT(*)
571
            FROM ' . prefixTable('items_change') . ' AS c
572
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
573
            WHERE i.action = %s AND i.id_user = %i',
574
            'at_creation',
575
            $globalsUserId
576
        );
577
        $session->set('user-nb_item_change_proposals', $countNewItems);
578
    } else {
579
        $session->set('user-nb_item_change_proposals', 0);
580
    }
581
582
    return true;
583
}
584
585
/**
586
 * Get list of folders depending on Roles
587
 * 
588
 * @param array $userRoles
589
 * @param array $allowedFoldersByRoles
590
 * @param array $readOnlyFolders
591
 * @param array $allowedFolders
592
 * 
593
 * @return array
594
 */
595
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
596
{
597
    $rows = DB::query(
598
        'SELECT *
599
        FROM ' . prefixTable('roles_values') . '
600
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
601
        ['W', 'ND', 'NE', 'NDNE', 'R'],
602
        $userRoles,
603
    );
604
    foreach ($rows as $record) {
605
        if ($record['type'] === 'R') {
606
            array_push($readOnlyFolders, $record['folder_id']);
607
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
608
            array_push($allowedFoldersByRoles, $record['folder_id']);
609
        }
610
    }
611
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
612
    $readOnlyFolders = array_unique($readOnlyFolders);
613
    
614
    // Clean arrays
615
    foreach ($allowedFoldersByRoles as $value) {
616
        $key = array_search($value, $readOnlyFolders);
617
        if ($key !== false) {
618
            unset($readOnlyFolders[$key]);
619
        }
620
    }
621
    return [
622
        'readOnlyFolders' => $readOnlyFolders,
623
        'allowedFoldersByRoles' => $allowedFoldersByRoles
624
    ];
625
}
626
627
/**
628
 * Get list of Personal Folders
629
 * 
630
 * @param int $globalsPersonalFolders
631
 * @param array $allowedFolders
632
 * @param int $globalsUserId
633
 * @param array $personalFolders
634
 * @param array $noAccessPersonalFolders
635
 * @param array $foldersLimitedFull
636
 * @param array $allowedFoldersByRoles
637
 * @param array $restrictedFoldersForItems
638
 * @param array $readOnlyFolders
639
 * @param array $noAccessFolders
640
 * @param int $enablePfFeature
641
 * @param object $tree
642
 * 
643
 * @return array
644
 */
645
function identUserGetPFList(
646
    $globalsPersonalFolders,
647
    $allowedFolders,
648
    $globalsUserId,
649
    $personalFolders,
650
    $noAccessPersonalFolders,
651
    $foldersLimitedFull,
652
    $allowedFoldersByRoles,
653
    $restrictedFoldersForItems,
654
    $readOnlyFolders,
655
    $noAccessFolders,
656
    $enablePfFeature,
657
    $tree
658
)
659
{
660
    if (
661
        (int) $enablePfFeature === 1
662
        && (int) $globalsPersonalFolders === 1
663
    ) {
664
        $persoFld = DB::queryfirstrow(
665
            'SELECT id
666
            FROM ' . prefixTable('nested_tree') . '
667
            WHERE title = %s AND personal_folder = %i'.
668
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
669
            $globalsUserId,
670
            1
671
        );
672
        if (empty($persoFld['id']) === false) {
673
            array_push($personalFolders, $persoFld['id']);
674
            array_push($allowedFolders, $persoFld['id']);
675
            // get all descendants
676
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
677
            foreach ($ids as $id) {
678
                //array_push($allowedFolders, $id);
679
                array_push($personalFolders, $id);
680
            }
681
        }
682
    }
683
    
684
    // Exclude all other PF
685
    $where = new WhereClause('and');
686
    $where->add('personal_folder=%i', 1);
687
    if (count($personalFolders) > 0) {
688
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
689
    }
690
    if (
691
        (int) $enablePfFeature === 1
692
        && (int) $globalsPersonalFolders === 1
693
    ) {
694
        $where->add('title=%s', $globalsUserId);
695
        $where->negateLast();
696
    }
697
    $persoFlds = DB::query(
698
        'SELECT id
699
        FROM ' . prefixTable('nested_tree') . '
700
        WHERE %l',
701
        $where
702
    );
703
    foreach ($persoFlds as $persoFldId) {
704
        array_push($noAccessPersonalFolders, $persoFldId['id']);
705
    }
706
707
    // All folders visibles
708
    $allowedFolders = array_unique(array_merge(
709
        $allowedFolders,
710
        $foldersLimitedFull,
711
        $allowedFoldersByRoles,
712
        $restrictedFoldersForItems,
713
        $readOnlyFolders
714
    ), SORT_NUMERIC);
715
    // Exclude from allowed folders all the specific user forbidden folders
716
    if (count($noAccessFolders) > 0) {
717
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
718
    }
719
720
    return [
721
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
722
        'personalFolders' => $personalFolders,
723
        'noAccessPersonalFolders' => $noAccessPersonalFolders
724
    ];
725
}
726
727
728
/**
729
 * Update the CACHE table.
730
 *
731
 * @param string $action   What to do
732
 * @param array  $SETTINGS Teampass settings
733
 * @param int    $ident    Ident format
734
 * 
735
 * @return void
736
 */
737
function updateCacheTable(string $action, ?int $ident = null): void
738
{
739
    if ($action === 'reload') {
740
        // Rebuild full cache table
741
        cacheTableRefresh();
742
    } elseif ($action === 'update_value' && is_null($ident) === false) {
743
        // UPDATE an item
744
        cacheTableUpdate($ident);
745
    } elseif ($action === 'add_value' && is_null($ident) === false) {
746
        // ADD an item
747
        cacheTableAdd($ident);
748
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
749
        // DELETE an item
750
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
751
    }
752
}
753
754
/**
755
 * Cache table - refresh.
756
 *
757
 * @return void
758
 */
759
function cacheTableRefresh(): void
760
{
761
    // Load class DB
762
    loadClasses('DB');
763
764
    //Load Tree
765
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
766
    // truncate table
767
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
768
    // reload date
769
    $rows = DB::query(
770
        'SELECT *
771
        FROM ' . prefixTable('items') . ' as i
772
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
773
        AND l.action = %s
774
        AND i.inactif = %i',
775
        'at_creation',
776
        0
777
    );
778
    foreach ($rows as $record) {
779
        if (empty($record['id_tree']) === false) {
780
            // Get all TAGS
781
            $tags = '';
782
            $itemTags = DB::query(
783
                'SELECT tag
784
                FROM ' . prefixTable('tags') . '
785
                WHERE item_id = %i AND tag != ""',
786
                $record['id']
787
            );
788
            foreach ($itemTags as $itemTag) {
789
                $tags .= $itemTag['tag'] . ' ';
790
            }
791
792
            // Get renewal period
793
            $resNT = DB::queryfirstrow(
794
                'SELECT renewal_period
795
                FROM ' . prefixTable('nested_tree') . '
796
                WHERE id = %i',
797
                $record['id_tree']
798
            );
799
            // form id_tree to full foldername
800
            $folder = [];
801
            $arbo = $tree->getPath($record['id_tree'], true);
802
            foreach ($arbo as $elem) {
803
                // Check if title is the ID of a user
804
                if (is_numeric($elem->title) === true) {
805
                    // Is this a User id?
806
                    $user = DB::queryfirstrow(
807
                        'SELECT id, login
808
                        FROM ' . prefixTable('users') . '
809
                        WHERE id = %i',
810
                        $elem->title
811
                    );
812
                    if (count($user) > 0) {
813
                        $elem->title = $user['login'];
814
                    }
815
                }
816
                // Build path
817
                array_push($folder, stripslashes($elem->title));
818
            }
819
            // store data
820
            DB::insert(
821
                prefixTable('cache'),
822
                [
823
                    'id' => $record['id'],
824
                    'label' => $record['label'],
825
                    'description' => $record['description'] ?? '',
826
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
827
                    'tags' => $tags,
828
                    'id_tree' => $record['id_tree'],
829
                    'perso' => $record['perso'],
830
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
831
                    'login' => $record['login'] ?? '',
832
                    'folder' => implode(' > ', $folder),
833
                    'author' => $record['id_user'],
834
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
835
                    'timestamp' => $record['date'],
836
                ]
837
            );
838
        }
839
    }
840
}
841
842
/**
843
 * Cache table - update existing value.
844
 *
845
 * @param int    $ident    Ident format
846
 * 
847
 * @return void
848
 */
849
function cacheTableUpdate(?int $ident = null): void
850
{
851
    $session = SessionManager::getSession();
852
    loadClasses('DB');
853
854
    //Load Tree
855
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
856
    // get new value from db
857
    $data = DB::queryfirstrow(
858
        'SELECT label, description, id_tree, perso, restricted_to, login, url
859
        FROM ' . prefixTable('items') . '
860
        WHERE id=%i',
861
        $ident
862
    );
863
    // Get all TAGS
864
    $tags = '';
865
    $itemTags = DB::query(
866
        'SELECT tag
867
            FROM ' . prefixTable('tags') . '
868
            WHERE item_id = %i AND tag != ""',
869
        $ident
870
    );
871
    foreach ($itemTags as $itemTag) {
872
        $tags .= $itemTag['tag'] . ' ';
873
    }
874
    // form id_tree to full foldername
875
    $folder = [];
876
    $arbo = $tree->getPath($data['id_tree'], true);
877
    foreach ($arbo as $elem) {
878
        // Check if title is the ID of a user
879
        if (is_numeric($elem->title) === true) {
880
            // Is this a User id?
881
            $user = DB::queryfirstrow(
882
                'SELECT id, login
883
                FROM ' . prefixTable('users') . '
884
                WHERE id = %i',
885
                $elem->title
886
            );
887
            if (count($user) > 0) {
888
                $elem->title = $user['login'];
889
            }
890
        }
891
        // Build path
892
        array_push($folder, stripslashes($elem->title));
893
    }
894
    // finaly update
895
    DB::update(
896
        prefixTable('cache'),
897
        [
898
            'label' => $data['label'],
899
            'description' => $data['description'],
900
            'tags' => $tags,
901
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
902
            'id_tree' => $data['id_tree'],
903
            'perso' => $data['perso'],
904
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
905
            'login' => $data['login'] ?? '',
906
            'folder' => implode(' » ', $folder),
907
            'author' => $session->get('user-id'),
908
        ],
909
        'id = %i',
910
        $ident
911
    );
912
}
913
914
/**
915
 * Cache table - add new value.
916
 *
917
 * @param int    $ident    Ident format
918
 * 
919
 * @return void
920
 */
921
function cacheTableAdd(?int $ident = null): void
922
{
923
    $session = SessionManager::getSession();
924
    $globalsUserId = $session->get('user-id');
925
926
    // Load class DB
927
    loadClasses('DB');
928
929
    //Load Tree
930
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
931
    // get new value from db
932
    $data = DB::queryFirstRow(
933
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
934
        FROM ' . prefixTable('items') . ' as i
935
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
936
        WHERE i.id = %i
937
        AND l.action = %s',
938
        $ident,
939
        'at_creation'
940
    );
941
    // Get all TAGS
942
    $tags = '';
943
    $itemTags = DB::query(
944
        'SELECT tag
945
            FROM ' . prefixTable('tags') . '
946
            WHERE item_id = %i AND tag != ""',
947
        $ident
948
    );
949
    foreach ($itemTags as $itemTag) {
950
        $tags .= $itemTag['tag'] . ' ';
951
    }
952
    // form id_tree to full foldername
953
    $folder = [];
954
    $arbo = $tree->getPath($data['id_tree'], true);
955
    foreach ($arbo as $elem) {
956
        // Check if title is the ID of a user
957
        if (is_numeric($elem->title) === true) {
958
            // Is this a User id?
959
            $user = DB::queryfirstrow(
960
                'SELECT id, login
961
                FROM ' . prefixTable('users') . '
962
                WHERE id = %i',
963
                $elem->title
964
            );
965
            if (count($user) > 0) {
966
                $elem->title = $user['login'];
967
            }
968
        }
969
        // Build path
970
        array_push($folder, stripslashes($elem->title));
971
    }
972
    // finaly update
973
    DB::insert(
974
        prefixTable('cache'),
975
        [
976
            'id' => $data['id'],
977
            'label' => $data['label'],
978
            'description' => $data['description'],
979
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
980
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
981
            'id_tree' => $data['id_tree'],
982
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
983
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
984
            'login' => $data['login'] ?? '',
985
            'folder' => implode(' » ', $folder),
986
            'author' => $globalsUserId,
987
            'timestamp' => $data['date'],
988
        ]
989
    );
990
}
991
992
/**
993
 * Do statistics.
994
 *
995
 * @param array $SETTINGS Teampass settings
996
 *
997
 * @return array
998
 */
999
function getStatisticsData(array $SETTINGS): array
1000
{
1001
    DB::query(
1002
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1003
        0
1004
    );
1005
    $counter_folders = DB::count();
1006
    DB::query(
1007
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1008
        1
1009
    );
1010
    $counter_folders_perso = DB::count();
1011
    DB::query(
1012
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1013
        0
1014
    );
1015
    $counter_items = DB::count();
1016
        DB::query(
1017
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1018
        1
1019
    );
1020
    $counter_items_perso = DB::count();
1021
        DB::query(
1022
        'SELECT id FROM ' . prefixTable('users') . ''
1023
    );
1024
    $counter_users = DB::count();
1025
        DB::query(
1026
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1027
        1
1028
    );
1029
    $admins = DB::count();
1030
    DB::query(
1031
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1032
        1
1033
    );
1034
    $managers = DB::count();
1035
    DB::query(
1036
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1037
        1
1038
    );
1039
    $readOnly = DB::count();
1040
    // list the languages
1041
    $usedLang = [];
1042
    $tp_languages = DB::query(
1043
        'SELECT name FROM ' . prefixTable('languages')
1044
    );
1045
    foreach ($tp_languages as $tp_language) {
1046
        DB::query(
1047
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1048
            $tp_language['name']
1049
        );
1050
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1051
    }
1052
1053
    // get list of ips
1054
    $usedIp = [];
1055
    $tp_ips = DB::query(
1056
        'SELECT user_ip FROM ' . prefixTable('users')
1057
    );
1058
    foreach ($tp_ips as $ip) {
1059
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1060
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1061
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1062
            $usedIp[$ip['user_ip']] = 1;
1063
        }
1064
    }
1065
1066
    return [
1067
        'error' => '',
1068
        'stat_phpversion' => phpversion(),
1069
        'stat_folders' => $counter_folders,
1070
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1071
        'stat_items' => $counter_items,
1072
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1073
        'stat_users' => $counter_users,
1074
        'stat_admins' => $admins,
1075
        'stat_managers' => $managers,
1076
        'stat_ro' => $readOnly,
1077
        'stat_kb' => $SETTINGS['enable_kb'],
1078
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1079
        'stat_fav' => $SETTINGS['enable_favourites'],
1080
        'stat_teampassversion' => TP_VERSION,
1081
        'stat_ldap' => $SETTINGS['ldap_mode'],
1082
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1083
        'stat_duo' => $SETTINGS['duo'],
1084
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1085
        'stat_api' => $SETTINGS['api'],
1086
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1087
        'stat_syslog' => $SETTINGS['syslog_enable'],
1088
        'stat_2fa' => $SETTINGS['google_authentication'],
1089
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1090
        'stat_mysqlversion' => DB::serverVersion(),
1091
        'stat_languages' => $usedLang,
1092
        'stat_country' => $usedIp,
1093
    ];
1094
}
1095
1096
/**
1097
 * Permits to prepare the way to send the email
1098
 * 
1099
 * @param string $subject       email subject
1100
 * @param string $body          email message
1101
 * @param string $email         email
1102
 * @param string $receiverName  Receiver name
1103
 * @param array  $SETTINGS      settings
1104
 *
1105
 * @return void
1106
 */
1107
function prepareSendingEmail(
1108
    $subject,
1109
    $body,
1110
    $email,
1111
    $receiverName = ''
1112
): void 
1113
{
1114
    DB::insert(
1115
        prefixTable('background_tasks'),
1116
        array(
1117
            'created_at' => time(),
1118
            'process_type' => 'send_email',
1119
            'arguments' => json_encode([
1120
                'subject' => $subject,
1121
                'receivers' => $email,
1122
                'body' => $body,
1123
                'receiver_name' => $receiverName,
1124
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1125
        )
1126
    );
1127
}
1128
1129
/**
1130
 * Permits to send an email.
1131
 *
1132
 * @param string $subject     email subject
1133
 * @param string $textMail    email message
1134
 * @param string $email       email
1135
 * @param array  $SETTINGS    settings
1136
 * @param string $textMailAlt email message alt
1137
 * @param bool   $silent      no errors
1138
 *
1139
 * @return string some json info
1140
 */
1141
function sendEmail(
1142
    $subject,
1143
    $textMail,
1144
    $email,
1145
    $SETTINGS,
1146
    $textMailAlt = null,
1147
    $silent = true,
1148
    $cron = false
1149
) {
1150
    $session = SessionManager::getSession();
1151
    $lang = new Language($session->get('user-language') ?? 'english');
1152
1153
    // CAse where email not defined
1154
    if ($email === 'none' || empty($email) === true) {
1155
        return json_encode(
1156
            [
1157
                'error' => true,
1158
                'message' => $lang->get('forgot_my_pw_email_sent'),
1159
            ]
1160
        );
1161
    }
1162
1163
    // Build and send email
1164
    $email = buildEmail(
1165
        $subject,
1166
        $textMail,
1167
        $email,
1168
        $SETTINGS,
1169
        $textMailAlt = null,
1170
        $silent = true,
1171
        $cron
1172
    );
1173
1174
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1175
        return json_encode(
1176
            [
1177
                'error' => false,
1178
                'message' => $lang->get('forgot_my_pw_email_sent'),
1179
            ]
1180
        );
1181
    }
1182
    // Debug purpose
1183
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1184
        return json_encode(
1185
            [
1186
                'error' => true,
1187
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1188
            ]
1189
        );
1190
    }
1191
    return json_encode(
1192
        [
1193
            'error' => false,
1194
            'message' => $lang->get('share_sent_ok'),
1195
        ]
1196
    );
1197
}
1198
1199
1200
function buildEmail(
1201
    $subject,
1202
    $textMail,
1203
    $email,
1204
    $SETTINGS,
1205
    $textMailAlt = null,
1206
    $silent = true,
1207
    $cron = false
1208
)
1209
{
1210
    // Load PHPMailer
1211
    $mail = new PHPMailer(true);
1212
    $languageDir = $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/';
1213
    // Load AntiXSS
1214
    $antiXss = new AntiXSS();
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
        // Check the email content to make sure it is safe
1258
        $textMailClean = $antiXss->xss_clean($textMail);
1259
        $textMailClean = htmlspecialchars($textMailClean, ENT_QUOTES, 'UTF-8');
0 ignored issues
show
Bug introduced by
It seems like $textMailClean can also be of type string[]; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1259
        $textMailClean = htmlspecialchars(/** @scrutinizer ignore-type */ $textMailClean, ENT_QUOTES, 'UTF-8');
Loading history...
1260
        if ($antiXss->isXssFound()) {
1261
            $textMail = $textMailClean;
1262
        }
1263
        // Prepare HTML and AltBody
1264
        $text_html = emailBody($textMail);
1265
        $mail->WordWrap = 80;
1266
        $mail->isHtml(true);
1267
        $mail->Subject = $subject;
1268
        $mail->Body = $text_html;
0 ignored issues
show
Security File Manipulation introduced by
$Body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264

Used in path-write context

  1. $text_html is assigned to property PHPMailer::$Body
    in sources/main.functions.php on line 1262
  2. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  3. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  4. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  5. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3135
  6. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1622
  7. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  8. PHPMailer::sendmailSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  9. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1726
  10. fwrite() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1803

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security File Manipulation introduced by
$Body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4401
  10. sendEmail() is called
    in sources/main.functions.php on line 4418
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1264

Used in path-write context

  1. $text_html is assigned to property PHPMailer::$Body
    in sources/main.functions.php on line 1262
  2. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  3. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  4. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  5. file_put_contents() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3092

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

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

4409
            array_keys(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4410
            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

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