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