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