|
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
|
|
|
// Handle private key |
|
3569
|
|
|
insertPrivateKeyWithCurrentFlag( |
|
3570
|
|
|
$userId, |
|
3571
|
|
|
$userKeys['private_key'], |
|
3572
|
|
|
); |
|
3573
|
|
|
|
|
3574
|
|
|
// Save in DB |
|
3575
|
|
|
// TODO: remove private key field from Users table |
|
3576
|
|
|
DB::update( |
|
3577
|
|
|
prefixTable('users'), |
|
3578
|
|
|
array( |
|
3579
|
|
|
'pw' => $hashedPassword, |
|
3580
|
|
|
'public_key' => $userKeys['public_key'], |
|
3581
|
|
|
'private_key' => $userKeys['private_key'], |
|
3582
|
|
|
'keys_recovery_time' => NULL, |
|
3583
|
|
|
), |
|
3584
|
|
|
'id=%i', |
|
3585
|
|
|
$userId |
|
3586
|
|
|
); |
|
3587
|
|
|
|
|
3588
|
|
|
|
|
3589
|
|
|
// update session too |
|
3590
|
|
|
if ($userId === $session->get('user-id')) { |
|
3591
|
|
|
$session->set('user-private_key', $userKeys['private_key_clear']); |
|
3592
|
|
|
$session->set('user-public_key', $userKeys['public_key']); |
|
3593
|
|
|
// Notify user that he must re download his keys: |
|
3594
|
|
|
$session->set('user-keys_recovery_time', NULL); |
|
3595
|
|
|
} |
|
3596
|
|
|
|
|
3597
|
|
|
// Manage empty encryption key |
|
3598
|
|
|
// Let's take the user's password if asked and if no encryption key provided |
|
3599
|
|
|
$encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey; |
|
3600
|
|
|
|
|
3601
|
|
|
// Create process |
|
3602
|
|
|
DB::insert( |
|
3603
|
|
|
prefixTable('background_tasks'), |
|
3604
|
|
|
array( |
|
3605
|
|
|
'created_at' => time(), |
|
3606
|
|
|
'process_type' => 'create_user_keys', |
|
3607
|
|
|
'arguments' => json_encode([ |
|
3608
|
|
|
'new_user_id' => (int) $userId, |
|
3609
|
|
|
'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'], |
|
3610
|
|
|
'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'], |
|
3611
|
|
|
'owner_id' => (int) TP_USER_ID, |
|
3612
|
|
|
'creator_pwd' => $userTP['pw'], |
|
3613
|
|
|
'send_email' => $sendEmailToUser === true ? 1 : 0, |
|
3614
|
|
|
'otp_provided_new_value' => 1, |
|
3615
|
|
|
'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody), |
|
3616
|
|
|
'user_self_change' => $user_self_change === true ? 1 : 0, |
|
3617
|
|
|
]), |
|
3618
|
|
|
) |
|
3619
|
|
|
); |
|
3620
|
|
|
$processId = DB::insertId(); |
|
3621
|
|
|
|
|
3622
|
|
|
// Delete existing keys |
|
3623
|
|
|
if ($deleteExistingKeys === true) { |
|
3624
|
|
|
deleteUserObjetsKeys( |
|
3625
|
|
|
(int) $userId, |
|
3626
|
|
|
); |
|
3627
|
|
|
} |
|
3628
|
|
|
|
|
3629
|
|
|
// Create tasks |
|
3630
|
|
|
createUserTasks($processId, $nbItemsToTreat); |
|
3631
|
|
|
|
|
3632
|
|
|
// update user's new status |
|
3633
|
|
|
DB::update( |
|
3634
|
|
|
prefixTable('users'), |
|
3635
|
|
|
[ |
|
3636
|
|
|
'is_ready_for_usage' => 0, |
|
3637
|
|
|
'otp_provided' => 1, |
|
3638
|
|
|
'ongoing_process_id' => $processId, |
|
3639
|
|
|
'special' => 'generate-keys', |
|
3640
|
|
|
], |
|
3641
|
|
|
'id=%i', |
|
3642
|
|
|
$userId |
|
3643
|
|
|
); |
|
3644
|
|
|
|
|
3645
|
|
|
return prepareExchangedData( |
|
3646
|
|
|
array( |
|
3647
|
|
|
'error' => false, |
|
3648
|
|
|
'message' => '', |
|
3649
|
|
|
'user_password' => $generate_user_new_password === true ? $passwordClear : '', |
|
3650
|
|
|
), |
|
3651
|
|
|
'encode' |
|
3652
|
|
|
); |
|
3653
|
|
|
} |
|
3654
|
|
|
|
|
3655
|
|
|
/** |
|
3656
|
|
|
* Permits to generate a new password for a user |
|
3657
|
|
|
* |
|
3658
|
|
|
* @param integer $processId |
|
3659
|
|
|
* @param integer $nbItemsToTreat |
|
3660
|
|
|
* @return void |
|
3661
|
|
|
|
|
3662
|
|
|
*/ |
|
3663
|
|
|
function createUserTasks($processId, $nbItemsToTreat): void |
|
3664
|
|
|
{ |
|
3665
|
|
|
// Create subtask for step 0 |
|
3666
|
|
|
DB::insert( |
|
3667
|
|
|
prefixTable('background_subtasks'), |
|
3668
|
|
|
array( |
|
3669
|
|
|
'task_id' => $processId, |
|
3670
|
|
|
'created_at' => time(), |
|
3671
|
|
|
'task' => json_encode([ |
|
3672
|
|
|
'step' => 'step0', |
|
3673
|
|
|
'index' => 0, |
|
3674
|
|
|
'nb' => $nbItemsToTreat, |
|
3675
|
|
|
]), |
|
3676
|
|
|
) |
|
3677
|
|
|
); |
|
3678
|
|
|
|
|
3679
|
|
|
// Prepare the subtask queries |
|
3680
|
|
|
$queries = [ |
|
3681
|
|
|
'step20' => 'SELECT * FROM ' . prefixTable('items'), |
|
3682
|
|
|
|
|
3683
|
|
|
'step30' => 'SELECT * FROM ' . prefixTable('log_items') . |
|
3684
|
|
|
' WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"', |
|
3685
|
|
|
|
|
3686
|
|
|
'step40' => 'SELECT * FROM ' . prefixTable('categories_items') . |
|
3687
|
|
|
' WHERE encryption_type = "teampass_aes"', |
|
3688
|
|
|
|
|
3689
|
|
|
'step50' => 'SELECT * FROM ' . prefixTable('suggestion'), |
|
3690
|
|
|
|
|
3691
|
|
|
'step60' => 'SELECT * FROM ' . prefixTable('files') . ' AS f |
|
3692
|
|
|
INNER JOIN ' . prefixTable('items') . ' AS i ON i.id = f.id_item |
|
3693
|
|
|
WHERE f.status = "' . TP_ENCRYPTION_NAME . '"' |
|
3694
|
|
|
]; |
|
3695
|
|
|
|
|
3696
|
|
|
// Perform loop on $queries to create sub-tasks |
|
3697
|
|
|
foreach ($queries as $step => $query) { |
|
3698
|
|
|
DB::query($query); |
|
3699
|
|
|
createAllSubTasks($step, DB::count(), $nbItemsToTreat, $processId); |
|
3700
|
|
|
} |
|
3701
|
|
|
|
|
3702
|
|
|
// Create subtask for step 99 |
|
3703
|
|
|
DB::insert( |
|
3704
|
|
|
prefixTable('background_subtasks'), |
|
3705
|
|
|
array( |
|
3706
|
|
|
'task_id' => $processId, |
|
3707
|
|
|
'created_at' => time(), |
|
3708
|
|
|
'task' => json_encode([ |
|
3709
|
|
|
'step' => 'step99', |
|
3710
|
|
|
]), |
|
3711
|
|
|
) |
|
3712
|
|
|
); |
|
3713
|
|
|
} |
|
3714
|
|
|
|
|
3715
|
|
|
/** |
|
3716
|
|
|
* Create all subtasks for a given action |
|
3717
|
|
|
* @param string $action The action to be performed |
|
3718
|
|
|
* @param int $totalElements Total number of elements to process |
|
3719
|
|
|
* @param int $elementsPerIteration Number of elements per iteration |
|
3720
|
|
|
* @param int $taskId The ID of the task |
|
3721
|
|
|
*/ |
|
3722
|
|
|
function createAllSubTasks($action, $totalElements, $elementsPerIteration, $taskId) { |
|
3723
|
|
|
// Calculate the number of iterations |
|
3724
|
|
|
$iterations = ceil($totalElements / $elementsPerIteration); |
|
3725
|
|
|
|
|
3726
|
|
|
// Create the subtasks |
|
3727
|
|
|
for ($i = 0; $i < $iterations; $i++) { |
|
3728
|
|
|
DB::insert(prefixTable('background_subtasks'), [ |
|
3729
|
|
|
'task_id' => $taskId, |
|
3730
|
|
|
'created_at' => time(), |
|
3731
|
|
|
'task' => json_encode([ |
|
3732
|
|
|
"step" => $action, |
|
3733
|
|
|
"index" => $i * $elementsPerIteration, |
|
3734
|
|
|
"nb" => $elementsPerIteration, |
|
3735
|
|
|
]), |
|
3736
|
|
|
]); |
|
3737
|
|
|
} |
|
3738
|
|
|
} |
|
3739
|
|
|
|
|
3740
|
|
|
/** |
|
3741
|
|
|
* Permeits to check the consistency of date versus columns definition |
|
3742
|
|
|
* |
|
3743
|
|
|
* @param string $table |
|
3744
|
|
|
* @param array $dataFields |
|
3745
|
|
|
* @return array |
|
3746
|
|
|
*/ |
|
3747
|
|
|
function validateDataFields( |
|
3748
|
|
|
string $table, |
|
3749
|
|
|
array $dataFields |
|
3750
|
|
|
): array |
|
3751
|
|
|
{ |
|
3752
|
|
|
// Get table structure |
|
3753
|
|
|
$result = DB::query( |
|
3754
|
|
|
"SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';", |
|
3755
|
|
|
DB_NAME, |
|
3756
|
|
|
$table |
|
3757
|
|
|
); |
|
3758
|
|
|
|
|
3759
|
|
|
foreach ($result as $row) { |
|
3760
|
|
|
$field = $row['COLUMN_NAME']; |
|
3761
|
|
|
$maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : ''; |
|
3762
|
|
|
|
|
3763
|
|
|
if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) { |
|
3764
|
|
|
if (strlen((string) $dataFields[$field]) > $maxLength) { |
|
3765
|
|
|
return [ |
|
3766
|
|
|
'state' => false, |
|
3767
|
|
|
'field' => $field, |
|
3768
|
|
|
'maxLength' => $maxLength, |
|
3769
|
|
|
'currentLength' => strlen((string) $dataFields[$field]), |
|
3770
|
|
|
]; |
|
3771
|
|
|
} |
|
3772
|
|
|
} |
|
3773
|
|
|
} |
|
3774
|
|
|
|
|
3775
|
|
|
return [ |
|
3776
|
|
|
'state' => true, |
|
3777
|
|
|
'message' => '', |
|
3778
|
|
|
]; |
|
3779
|
|
|
} |
|
3780
|
|
|
|
|
3781
|
|
|
/** |
|
3782
|
|
|
* Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation |
|
3783
|
|
|
* |
|
3784
|
|
|
* @param string $string |
|
3785
|
|
|
* @return string |
|
3786
|
|
|
*/ |
|
3787
|
|
|
function filterVarBack(string $string): string |
|
3788
|
|
|
{ |
|
3789
|
|
|
$arr = [ |
|
3790
|
|
|
'<' => '<', |
|
3791
|
|
|
'>' => '>', |
|
3792
|
|
|
'"' => '"', |
|
3793
|
|
|
''' => "'", |
|
3794
|
|
|
'&' => '&', |
|
3795
|
|
|
]; |
|
3796
|
|
|
|
|
3797
|
|
|
foreach ($arr as $key => $value) { |
|
3798
|
|
|
$string = str_replace($key, $value, $string); |
|
3799
|
|
|
} |
|
3800
|
|
|
|
|
3801
|
|
|
return $string; |
|
3802
|
|
|
} |
|
3803
|
|
|
|
|
3804
|
|
|
/** |
|
3805
|
|
|
* |
|
3806
|
|
|
*/ |
|
3807
|
|
|
function storeTask( |
|
3808
|
|
|
string $taskName, |
|
3809
|
|
|
int $user_id, |
|
3810
|
|
|
int $is_personal_folder, |
|
3811
|
|
|
int $folder_destination_id, |
|
3812
|
|
|
int $item_id, |
|
3813
|
|
|
string $object_keys, |
|
3814
|
|
|
array $fields_keys = [], |
|
3815
|
|
|
array $files_keys = [] |
|
3816
|
|
|
) |
|
3817
|
|
|
{ |
|
3818
|
|
|
if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) { |
|
3819
|
|
|
// Create process |
|
3820
|
|
|
DB::insert( |
|
3821
|
|
|
prefixTable('background_tasks'), |
|
3822
|
|
|
array( |
|
3823
|
|
|
'created_at' => time(), |
|
3824
|
|
|
'process_type' => $taskName, |
|
3825
|
|
|
'arguments' => json_encode([ |
|
3826
|
|
|
'item_id' => $item_id, |
|
3827
|
|
|
'object_key' => $object_keys, |
|
3828
|
|
|
]), |
|
3829
|
|
|
'item_id' => $item_id, |
|
3830
|
|
|
) |
|
3831
|
|
|
); |
|
3832
|
|
|
$processId = DB::insertId(); |
|
3833
|
|
|
|
|
3834
|
|
|
// Create tasks |
|
3835
|
|
|
// 1- Create password sharekeys for users of this new ITEM |
|
3836
|
|
|
DB::insert( |
|
3837
|
|
|
prefixTable('background_subtasks'), |
|
3838
|
|
|
array( |
|
3839
|
|
|
'task_id' => $processId, |
|
3840
|
|
|
'created_at' => time(), |
|
3841
|
|
|
'task' => json_encode([ |
|
3842
|
|
|
'step' => 'create_users_pwd_key', |
|
3843
|
|
|
'index' => 0, |
|
3844
|
|
|
]), |
|
3845
|
|
|
) |
|
3846
|
|
|
); |
|
3847
|
|
|
|
|
3848
|
|
|
// 2- Create fields sharekeys for users of this new ITEM |
|
3849
|
|
|
DB::insert( |
|
3850
|
|
|
prefixTable('background_subtasks'), |
|
3851
|
|
|
array( |
|
3852
|
|
|
'task_id' => $processId, |
|
3853
|
|
|
'created_at' => time(), |
|
3854
|
|
|
'task' => json_encode([ |
|
3855
|
|
|
'step' => 'create_users_fields_key', |
|
3856
|
|
|
'index' => 0, |
|
3857
|
|
|
'fields_keys' => $fields_keys, |
|
3858
|
|
|
]), |
|
3859
|
|
|
) |
|
3860
|
|
|
); |
|
3861
|
|
|
|
|
3862
|
|
|
// 3- Create files sharekeys for users of this new ITEM |
|
3863
|
|
|
DB::insert( |
|
3864
|
|
|
prefixTable('background_subtasks'), |
|
3865
|
|
|
array( |
|
3866
|
|
|
'task_id' => $processId, |
|
3867
|
|
|
'created_at' => time(), |
|
3868
|
|
|
'task' => json_encode([ |
|
3869
|
|
|
'step' => 'create_users_files_key', |
|
3870
|
|
|
'index' => 0, |
|
3871
|
|
|
'files_keys' => $files_keys, |
|
3872
|
|
|
]), |
|
3873
|
|
|
) |
|
3874
|
|
|
); |
|
3875
|
|
|
} |
|
3876
|
|
|
} |
|
3877
|
|
|
|
|
3878
|
|
|
/** |
|
3879
|
|
|
* |
|
3880
|
|
|
*/ |
|
3881
|
|
|
function createTaskForItem( |
|
3882
|
|
|
string $processType, |
|
3883
|
|
|
string|array $taskName, |
|
3884
|
|
|
int $itemId, |
|
3885
|
|
|
int $userId, |
|
3886
|
|
|
string $objectKey, |
|
3887
|
|
|
int $parentId = -1, |
|
3888
|
|
|
array $fields_keys = [], |
|
3889
|
|
|
array $files_keys = [] |
|
3890
|
|
|
) |
|
3891
|
|
|
{ |
|
3892
|
|
|
// 1- Create main process |
|
3893
|
|
|
// --- |
|
3894
|
|
|
|
|
3895
|
|
|
// Create process |
|
3896
|
|
|
DB::insert( |
|
3897
|
|
|
prefixTable('background_tasks'), |
|
3898
|
|
|
array( |
|
3899
|
|
|
'created_at' => time(), |
|
3900
|
|
|
'process_type' => $processType, |
|
3901
|
|
|
'arguments' => json_encode([ |
|
3902
|
|
|
'all_users_except_id' => (int) $userId, |
|
3903
|
|
|
'item_id' => (int) $itemId, |
|
3904
|
|
|
'object_key' => $objectKey, |
|
3905
|
|
|
'author' => (int) $userId, |
|
3906
|
|
|
]), |
|
3907
|
|
|
'item_id' => (int) $parentId !== -1 ? $parentId : null, |
|
3908
|
|
|
) |
|
3909
|
|
|
); |
|
3910
|
|
|
$processId = DB::insertId(); |
|
3911
|
|
|
|
|
3912
|
|
|
// 2- Create expected tasks |
|
3913
|
|
|
// --- |
|
3914
|
|
|
if (is_array($taskName) === false) { |
|
|
|
|
|
|
3915
|
|
|
$taskName = [$taskName]; |
|
3916
|
|
|
} |
|
3917
|
|
|
foreach($taskName as $task) { |
|
3918
|
|
|
if (WIP === true) error_log('createTaskForItem - task: '.$task); |
|
3919
|
|
|
switch ($task) { |
|
3920
|
|
|
case 'item_password': |
|
3921
|
|
|
|
|
3922
|
|
|
DB::insert( |
|
3923
|
|
|
prefixTable('background_subtasks'), |
|
3924
|
|
|
array( |
|
3925
|
|
|
'task_id' => $processId, |
|
3926
|
|
|
'created_at' => time(), |
|
3927
|
|
|
'task' => json_encode([ |
|
3928
|
|
|
'step' => 'create_users_pwd_key', |
|
3929
|
|
|
'index' => 0, |
|
3930
|
|
|
]), |
|
3931
|
|
|
) |
|
3932
|
|
|
); |
|
3933
|
|
|
|
|
3934
|
|
|
break; |
|
3935
|
|
|
case 'item_field': |
|
3936
|
|
|
|
|
3937
|
|
|
DB::insert( |
|
3938
|
|
|
prefixTable('background_subtasks'), |
|
3939
|
|
|
array( |
|
3940
|
|
|
'task_id' => $processId, |
|
3941
|
|
|
'created_at' => time(), |
|
3942
|
|
|
'task' => json_encode([ |
|
3943
|
|
|
'step' => 'create_users_fields_key', |
|
3944
|
|
|
'index' => 0, |
|
3945
|
|
|
'fields_keys' => $fields_keys, |
|
3946
|
|
|
]), |
|
3947
|
|
|
) |
|
3948
|
|
|
); |
|
3949
|
|
|
|
|
3950
|
|
|
break; |
|
3951
|
|
|
case 'item_file': |
|
3952
|
|
|
|
|
3953
|
|
|
DB::insert( |
|
3954
|
|
|
prefixTable('background_subtasks'), |
|
3955
|
|
|
array( |
|
3956
|
|
|
'task_id' => $processId, |
|
3957
|
|
|
'created_at' => time(), |
|
3958
|
|
|
'task' => json_encode([ |
|
3959
|
|
|
'step' => 'create_users_files_key', |
|
3960
|
|
|
'index' => 0, |
|
3961
|
|
|
'fields_keys' => $files_keys, |
|
3962
|
|
|
]), |
|
3963
|
|
|
) |
|
3964
|
|
|
); |
|
3965
|
|
|
break; |
|
3966
|
|
|
default: |
|
3967
|
|
|
# code... |
|
3968
|
|
|
break; |
|
3969
|
|
|
} |
|
3970
|
|
|
} |
|
3971
|
|
|
} |
|
3972
|
|
|
|
|
3973
|
|
|
|
|
3974
|
|
|
function deleteProcessAndRelatedTasks(int $processId) |
|
3975
|
|
|
{ |
|
3976
|
|
|
// Delete process |
|
3977
|
|
|
DB::delete( |
|
3978
|
|
|
prefixTable('background_tasks'), |
|
3979
|
|
|
'id=%i', |
|
3980
|
|
|
$processId |
|
3981
|
|
|
); |
|
3982
|
|
|
|
|
3983
|
|
|
// Delete tasks |
|
3984
|
|
|
DB::delete( |
|
3985
|
|
|
prefixTable('background_subtasks'), |
|
3986
|
|
|
'task_id=%i', |
|
3987
|
|
|
$processId |
|
3988
|
|
|
); |
|
3989
|
|
|
|
|
3990
|
|
|
} |
|
3991
|
|
|
|
|
3992
|
|
|
/** |
|
3993
|
|
|
* Return PHP binary path |
|
3994
|
|
|
* |
|
3995
|
|
|
* @return string |
|
3996
|
|
|
*/ |
|
3997
|
|
|
function getPHPBinary(): string |
|
3998
|
|
|
{ |
|
3999
|
|
|
// Get PHP binary path |
|
4000
|
|
|
$phpBinaryFinder = new PhpExecutableFinder(); |
|
4001
|
|
|
$phpBinaryPath = $phpBinaryFinder->find(); |
|
4002
|
|
|
return $phpBinaryPath === false ? 'false' : $phpBinaryPath; |
|
4003
|
|
|
} |
|
4004
|
|
|
|
|
4005
|
|
|
|
|
4006
|
|
|
|
|
4007
|
|
|
/** |
|
4008
|
|
|
* Delete unnecessary keys for personal items |
|
4009
|
|
|
* |
|
4010
|
|
|
* @param boolean $allUsers |
|
4011
|
|
|
* @param integer $user_id |
|
4012
|
|
|
* @return void |
|
4013
|
|
|
*/ |
|
4014
|
|
|
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0) |
|
4015
|
|
|
{ |
|
4016
|
|
|
if ($allUsers === true) { |
|
4017
|
|
|
// Load class DB |
|
4018
|
|
|
if (class_exists('DB') === false) { |
|
4019
|
|
|
loadClasses('DB'); |
|
4020
|
|
|
} |
|
4021
|
|
|
|
|
4022
|
|
|
$users = DB::query( |
|
4023
|
|
|
'SELECT id |
|
4024
|
|
|
FROM ' . prefixTable('users') . ' |
|
4025
|
|
|
WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.') |
|
4026
|
|
|
ORDER BY login ASC' |
|
4027
|
|
|
); |
|
4028
|
|
|
foreach ($users as $user) { |
|
4029
|
|
|
purgeUnnecessaryKeysForUser((int) $user['id']); |
|
4030
|
|
|
} |
|
4031
|
|
|
} else { |
|
4032
|
|
|
purgeUnnecessaryKeysForUser((int) $user_id); |
|
4033
|
|
|
} |
|
4034
|
|
|
} |
|
4035
|
|
|
|
|
4036
|
|
|
/** |
|
4037
|
|
|
* Delete unnecessary keys for personal items |
|
4038
|
|
|
* |
|
4039
|
|
|
* @param integer $user_id |
|
4040
|
|
|
* @return void |
|
4041
|
|
|
*/ |
|
4042
|
|
|
function purgeUnnecessaryKeysForUser(int $user_id=0) |
|
4043
|
|
|
{ |
|
4044
|
|
|
if ($user_id === 0) { |
|
4045
|
|
|
return; |
|
4046
|
|
|
} |
|
4047
|
|
|
|
|
4048
|
|
|
// Load class DB |
|
4049
|
|
|
loadClasses('DB'); |
|
4050
|
|
|
|
|
4051
|
|
|
$personalItems = DB::queryFirstColumn( |
|
4052
|
|
|
'SELECT id |
|
4053
|
|
|
FROM ' . prefixTable('items') . ' AS i |
|
4054
|
|
|
INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id |
|
4055
|
|
|
WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')', |
|
4056
|
|
|
$user_id |
|
4057
|
|
|
); |
|
4058
|
|
|
if (count($personalItems) > 0) { |
|
4059
|
|
|
// Item keys |
|
4060
|
|
|
DB::delete( |
|
4061
|
|
|
prefixTable('sharekeys_items'), |
|
4062
|
|
|
'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')', |
|
4063
|
|
|
$personalItems, |
|
4064
|
|
|
$user_id |
|
4065
|
|
|
); |
|
4066
|
|
|
// Files keys |
|
4067
|
|
|
DB::delete( |
|
4068
|
|
|
prefixTable('sharekeys_files'), |
|
4069
|
|
|
'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')', |
|
4070
|
|
|
$personalItems, |
|
4071
|
|
|
$user_id |
|
4072
|
|
|
); |
|
4073
|
|
|
// Fields keys |
|
4074
|
|
|
DB::delete( |
|
4075
|
|
|
prefixTable('sharekeys_fields'), |
|
4076
|
|
|
'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')', |
|
4077
|
|
|
$personalItems, |
|
4078
|
|
|
$user_id |
|
4079
|
|
|
); |
|
4080
|
|
|
// Logs keys |
|
4081
|
|
|
DB::delete( |
|
4082
|
|
|
prefixTable('sharekeys_logs'), |
|
4083
|
|
|
'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')', |
|
4084
|
|
|
$personalItems, |
|
4085
|
|
|
$user_id |
|
4086
|
|
|
); |
|
4087
|
|
|
} |
|
4088
|
|
|
} |
|
4089
|
|
|
|
|
4090
|
|
|
/** |
|
4091
|
|
|
* Generate recovery keys file |
|
4092
|
|
|
* |
|
4093
|
|
|
* @param integer $userId |
|
4094
|
|
|
* @param array $SETTINGS |
|
4095
|
|
|
* @return string |
|
4096
|
|
|
*/ |
|
4097
|
|
|
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string |
|
4098
|
|
|
{ |
|
4099
|
|
|
$session = SessionManager::getSession(); |
|
4100
|
|
|
// Check if user exists |
|
4101
|
|
|
$userInfo = DB::queryFirstRow( |
|
4102
|
|
|
'SELECT login |
|
4103
|
|
|
FROM ' . prefixTable('users') . ' |
|
4104
|
|
|
WHERE id = %i', |
|
4105
|
|
|
$userId |
|
4106
|
|
|
); |
|
4107
|
|
|
|
|
4108
|
|
|
if (DB::count() > 0) { |
|
4109
|
|
|
$now = (int) time(); |
|
4110
|
|
|
// Prepare file content |
|
4111
|
|
|
$export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n". |
|
4112
|
|
|
"Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n". |
|
4113
|
|
|
"RECOVERY KEYS - Not to be shared - To be store safely\n\n". |
|
4114
|
|
|
"Public Key:\n".$session->get('user-public_key')."\n\n". |
|
4115
|
|
|
"Private Key:\n".$session->get('user-private_key')."\n\n"; |
|
4116
|
|
|
|
|
4117
|
|
|
// Update user's keys_recovery_time |
|
4118
|
|
|
DB::update( |
|
4119
|
|
|
prefixTable('users'), |
|
4120
|
|
|
[ |
|
4121
|
|
|
'keys_recovery_time' => $now, |
|
4122
|
|
|
], |
|
4123
|
|
|
'id=%i', |
|
4124
|
|
|
$userId |
|
4125
|
|
|
); |
|
4126
|
|
|
$session->set('user-keys_recovery_time', $now); |
|
4127
|
|
|
|
|
4128
|
|
|
//Log into DB the user's disconnection |
|
4129
|
|
|
logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']); |
|
4130
|
|
|
|
|
4131
|
|
|
// Return data |
|
4132
|
|
|
return prepareExchangedData( |
|
4133
|
|
|
array( |
|
4134
|
|
|
'error' => false, |
|
4135
|
|
|
'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now), |
|
4136
|
|
|
'timestamp' => $now, |
|
4137
|
|
|
'content' => base64_encode($export_value), |
|
4138
|
|
|
'login' => $userInfo['login'], |
|
4139
|
|
|
), |
|
4140
|
|
|
'encode' |
|
4141
|
|
|
); |
|
4142
|
|
|
} |
|
4143
|
|
|
|
|
4144
|
|
|
return prepareExchangedData( |
|
4145
|
|
|
array( |
|
4146
|
|
|
'error' => true, |
|
4147
|
|
|
'datetime' => '', |
|
4148
|
|
|
), |
|
4149
|
|
|
'encode' |
|
4150
|
|
|
); |
|
4151
|
|
|
} |
|
4152
|
|
|
|
|
4153
|
|
|
/** |
|
4154
|
|
|
* Permits to load expected classes |
|
4155
|
|
|
* |
|
4156
|
|
|
* @param string $className |
|
4157
|
|
|
* @return void |
|
4158
|
|
|
*/ |
|
4159
|
|
|
function loadClasses(string $className = ''): void |
|
4160
|
|
|
{ |
|
4161
|
|
|
require_once __DIR__. '/../includes/config/include.php'; |
|
4162
|
|
|
require_once __DIR__. '/../includes/config/settings.php'; |
|
4163
|
|
|
require_once __DIR__.'/../vendor/autoload.php'; |
|
4164
|
|
|
|
|
4165
|
|
|
if (defined('DB_PASSWD_CLEAR') === false) { |
|
4166
|
|
|
define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD)); |
|
4167
|
|
|
} |
|
4168
|
|
|
|
|
4169
|
|
|
if (empty($className) === false) { |
|
4170
|
|
|
// Load class DB |
|
4171
|
|
|
if ((string) $className === 'DB') { |
|
4172
|
|
|
//Connect to DB |
|
4173
|
|
|
DB::$host = DB_HOST; |
|
4174
|
|
|
DB::$user = DB_USER; |
|
4175
|
|
|
DB::$password = DB_PASSWD_CLEAR; |
|
4176
|
|
|
DB::$dbName = DB_NAME; |
|
4177
|
|
|
DB::$port = DB_PORT; |
|
4178
|
|
|
DB::$encoding = DB_ENCODING; |
|
4179
|
|
|
DB::$ssl = DB_SSL; |
|
4180
|
|
|
DB::$connect_options = DB_CONNECT_OPTIONS; |
|
4181
|
|
|
} |
|
4182
|
|
|
} |
|
4183
|
|
|
} |
|
4184
|
|
|
|
|
4185
|
|
|
/** |
|
4186
|
|
|
* Returns the page the user is visiting. |
|
4187
|
|
|
* |
|
4188
|
|
|
* @return string The page name |
|
4189
|
|
|
*/ |
|
4190
|
|
|
function getCurrectPage($SETTINGS) |
|
4191
|
|
|
{ |
|
4192
|
|
|
|
|
4193
|
|
|
$request = SymfonyRequest::createFromGlobals(); |
|
4194
|
|
|
|
|
4195
|
|
|
// Parse the url |
|
4196
|
|
|
parse_str( |
|
4197
|
|
|
substr( |
|
4198
|
|
|
(string) $request->getRequestUri(), |
|
4199
|
|
|
strpos((string) $request->getRequestUri(), '?') + 1 |
|
4200
|
|
|
), |
|
4201
|
|
|
$result |
|
4202
|
|
|
); |
|
4203
|
|
|
|
|
4204
|
|
|
return $result['page']; |
|
4205
|
|
|
} |
|
4206
|
|
|
|
|
4207
|
|
|
/** |
|
4208
|
|
|
* Permits to return value if set |
|
4209
|
|
|
* |
|
4210
|
|
|
* @param string|int $value |
|
4211
|
|
|
* @param string|int|null $retFalse |
|
4212
|
|
|
* @param string|int $retTrue |
|
4213
|
|
|
* @return mixed |
|
4214
|
|
|
*/ |
|
4215
|
|
|
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed |
|
4216
|
|
|
{ |
|
4217
|
|
|
if (!empty($value)) { |
|
4218
|
|
|
return is_null($retTrue) ? $value : $retTrue; |
|
4219
|
|
|
} |
|
4220
|
|
|
return $retFalse; |
|
4221
|
|
|
} |
|
4222
|
|
|
|
|
4223
|
|
|
|
|
4224
|
|
|
/** |
|
4225
|
|
|
* SEnd email to user |
|
4226
|
|
|
* |
|
4227
|
|
|
* @param string $post_receipt |
|
4228
|
|
|
* @param string $post_body |
|
4229
|
|
|
* @param string $post_subject |
|
4230
|
|
|
* @param array $post_replace |
|
4231
|
|
|
* @param boolean $immediate_email |
|
4232
|
|
|
* @param string $encryptedUserPassword |
|
4233
|
|
|
* @return string |
|
4234
|
|
|
*/ |
|
4235
|
|
|
function sendMailToUser( |
|
4236
|
|
|
string $post_receipt, |
|
4237
|
|
|
string $post_body, |
|
4238
|
|
|
string $post_subject, |
|
4239
|
|
|
array $post_replace, |
|
4240
|
|
|
bool $immediate_email = false, |
|
4241
|
|
|
$encryptedUserPassword = '' |
|
4242
|
|
|
): ?string { |
|
4243
|
|
|
global $SETTINGS; |
|
4244
|
|
|
$emailSettings = new EmailSettings($SETTINGS); |
|
4245
|
|
|
$emailService = new EmailService(); |
|
4246
|
|
|
$antiXss = new AntiXSS(); |
|
4247
|
|
|
|
|
4248
|
|
|
// Sanitize inputs |
|
4249
|
|
|
$post_receipt = filter_var($post_receipt, FILTER_SANITIZE_EMAIL); |
|
4250
|
|
|
$post_subject = $antiXss->xss_clean($post_subject); |
|
4251
|
|
|
$post_body = $antiXss->xss_clean($post_body); |
|
4252
|
|
|
|
|
4253
|
|
|
if (count($post_replace) > 0) { |
|
4254
|
|
|
$post_body = str_replace( |
|
4255
|
|
|
array_keys($post_replace), |
|
4256
|
|
|
array_values($post_replace), |
|
4257
|
|
|
$post_body |
|
4258
|
|
|
); |
|
4259
|
|
|
} |
|
4260
|
|
|
|
|
4261
|
|
|
// Remove newlines to prevent header injection |
|
4262
|
|
|
$post_body = str_replace(array("\r", "\n"), '', $post_body); |
|
4263
|
|
|
|
|
4264
|
|
|
if ($immediate_email === true) { |
|
4265
|
|
|
// Send email |
|
4266
|
|
|
$ret = $emailService->sendMail( |
|
4267
|
|
|
$post_subject, |
|
4268
|
|
|
$post_body, |
|
4269
|
|
|
$post_receipt, |
|
4270
|
|
|
$emailSettings, |
|
4271
|
|
|
'', |
|
4272
|
|
|
false |
|
4273
|
|
|
); |
|
4274
|
|
|
|
|
4275
|
|
|
$ret = json_decode($ret, true); |
|
4276
|
|
|
|
|
4277
|
|
|
return prepareExchangedData( |
|
4278
|
|
|
array( |
|
4279
|
|
|
'error' => empty($ret['error']) === true ? false : true, |
|
4280
|
|
|
'message' => $ret['message'], |
|
4281
|
|
|
), |
|
4282
|
|
|
'encode' |
|
4283
|
|
|
); |
|
4284
|
|
|
} else { |
|
4285
|
|
|
// Send through task handler |
|
4286
|
|
|
prepareSendingEmail( |
|
4287
|
|
|
$post_subject, |
|
4288
|
|
|
$post_body, |
|
4289
|
|
|
$post_receipt, |
|
4290
|
|
|
"", |
|
4291
|
|
|
$encryptedUserPassword, |
|
4292
|
|
|
); |
|
4293
|
|
|
} |
|
4294
|
|
|
|
|
4295
|
|
|
return null; |
|
4296
|
|
|
} |
|
4297
|
|
|
|
|
4298
|
|
|
/** |
|
4299
|
|
|
* Converts a password strengh value to zxcvbn level |
|
4300
|
|
|
* |
|
4301
|
|
|
* @param integer $passwordStrength |
|
4302
|
|
|
* |
|
4303
|
|
|
* @return integer |
|
4304
|
|
|
*/ |
|
4305
|
|
|
function convertPasswordStrength($passwordStrength): int |
|
4306
|
|
|
{ |
|
4307
|
|
|
if ($passwordStrength === 0) { |
|
4308
|
|
|
return TP_PW_STRENGTH_1; |
|
4309
|
|
|
} else if ($passwordStrength === 1) { |
|
4310
|
|
|
return TP_PW_STRENGTH_2; |
|
4311
|
|
|
} else if ($passwordStrength === 2) { |
|
4312
|
|
|
return TP_PW_STRENGTH_3; |
|
4313
|
|
|
} else if ($passwordStrength === 3) { |
|
4314
|
|
|
return TP_PW_STRENGTH_4; |
|
4315
|
|
|
} else { |
|
4316
|
|
|
return TP_PW_STRENGTH_5; |
|
4317
|
|
|
} |
|
4318
|
|
|
} |
|
4319
|
|
|
|
|
4320
|
|
|
/** |
|
4321
|
|
|
* Check that a password is strong. The password needs to have at least : |
|
4322
|
|
|
* - length >= 10. |
|
4323
|
|
|
* - Uppercase and lowercase chars. |
|
4324
|
|
|
* - Number or special char. |
|
4325
|
|
|
* - Not contain username, name or mail part. |
|
4326
|
|
|
* - Different from previous password. |
|
4327
|
|
|
* |
|
4328
|
|
|
* @param string $password - Password to ckeck. |
|
4329
|
|
|
* @return bool - true if the password is strong, false otherwise. |
|
4330
|
|
|
*/ |
|
4331
|
|
|
function isPasswordStrong($password) { |
|
4332
|
|
|
$session = SessionManager::getSession(); |
|
4333
|
|
|
|
|
4334
|
|
|
// Password can't contain login, name or lastname |
|
4335
|
|
|
$forbiddenWords = [ |
|
4336
|
|
|
$session->get('user-login'), |
|
4337
|
|
|
$session->get('user-name'), |
|
4338
|
|
|
$session->get('user-lastname'), |
|
4339
|
|
|
]; |
|
4340
|
|
|
|
|
4341
|
|
|
// Cut out the email |
|
4342
|
|
|
if ($email = $session->get('user-email')) { |
|
4343
|
|
|
$emailParts = explode('@', $email); |
|
4344
|
|
|
|
|
4345
|
|
|
if (count($emailParts) === 2) { |
|
4346
|
|
|
// Mail username (removed @domain.tld) |
|
4347
|
|
|
$forbiddenWords[] = $emailParts[0]; |
|
4348
|
|
|
|
|
4349
|
|
|
// Organisation name (removed username@ and .tld) |
|
4350
|
|
|
$domain = explode('.', $emailParts[1]); |
|
4351
|
|
|
if (count($domain) > 1) |
|
4352
|
|
|
$forbiddenWords[] = $domain[0]; |
|
4353
|
|
|
} |
|
4354
|
|
|
} |
|
4355
|
|
|
|
|
4356
|
|
|
// Search forbidden words in password |
|
4357
|
|
|
foreach ($forbiddenWords as $word) { |
|
4358
|
|
|
if (empty($word)) |
|
4359
|
|
|
continue; |
|
4360
|
|
|
|
|
4361
|
|
|
// Stop if forbidden word found in password |
|
4362
|
|
|
if (stripos($password, $word) !== false) |
|
4363
|
|
|
return false; |
|
4364
|
|
|
} |
|
4365
|
|
|
|
|
4366
|
|
|
// Get password complexity |
|
4367
|
|
|
$length = strlen($password); |
|
4368
|
|
|
$hasUppercase = preg_match('/[A-Z]/', $password); |
|
4369
|
|
|
$hasLowercase = preg_match('/[a-z]/', $password); |
|
4370
|
|
|
$hasNumber = preg_match('/[0-9]/', $password); |
|
4371
|
|
|
$hasSpecialChar = preg_match('/[\W_]/', $password); |
|
4372
|
|
|
|
|
4373
|
|
|
// Get current user hash |
|
4374
|
|
|
$userHash = DB::queryFirstRow( |
|
4375
|
|
|
"SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;", |
|
4376
|
|
|
$session->get('user-id') |
|
4377
|
|
|
)['pw']; |
|
4378
|
|
|
|
|
4379
|
|
|
$passwordManager = new PasswordManager(); |
|
4380
|
|
|
|
|
4381
|
|
|
return $length >= 8 |
|
4382
|
|
|
&& $hasUppercase |
|
4383
|
|
|
&& $hasLowercase |
|
4384
|
|
|
&& ($hasNumber || $hasSpecialChar) |
|
4385
|
|
|
&& !$passwordManager->verifyPassword($userHash, $password); |
|
4386
|
|
|
} |
|
4387
|
|
|
|
|
4388
|
|
|
|
|
4389
|
|
|
/** |
|
4390
|
|
|
* Converts a value to a string, handling various types and cases. |
|
4391
|
|
|
* |
|
4392
|
|
|
* @param mixed $value La valeur à convertir |
|
4393
|
|
|
* @param string $default Valeur par défaut si la conversion n'est pas possible |
|
4394
|
|
|
* @return string |
|
4395
|
|
|
*/ |
|
4396
|
|
|
function safeString($value, string $default = ''): string |
|
4397
|
|
|
{ |
|
4398
|
|
|
// Simple cases |
|
4399
|
|
|
if (is_string($value)) { |
|
4400
|
|
|
return $value; |
|
4401
|
|
|
} |
|
4402
|
|
|
|
|
4403
|
|
|
if (is_scalar($value)) { |
|
4404
|
|
|
return (string) $value; |
|
4405
|
|
|
} |
|
4406
|
|
|
|
|
4407
|
|
|
// Special cases |
|
4408
|
|
|
if (is_null($value)) { |
|
4409
|
|
|
return $default; |
|
4410
|
|
|
} |
|
4411
|
|
|
|
|
4412
|
|
|
if (is_array($value)) { |
|
4413
|
|
|
return empty($value) ? $default : json_encode($value, JSON_UNESCAPED_UNICODE); |
|
4414
|
|
|
} |
|
4415
|
|
|
|
|
4416
|
|
|
if (is_object($value)) { |
|
4417
|
|
|
// Vérifie si l'objet implémente __toString() |
|
4418
|
|
|
if (method_exists($value, '__toString')) { |
|
4419
|
|
|
return (string) $value; |
|
4420
|
|
|
} |
|
4421
|
|
|
|
|
4422
|
|
|
// Alternative: serialize ou json selon le contexte |
|
4423
|
|
|
return get_class($value) . (method_exists($value, 'getId') ? '#' . $value->getId() : ''); |
|
4424
|
|
|
} |
|
4425
|
|
|
|
|
4426
|
|
|
if (is_resource($value)) { |
|
4427
|
|
|
return 'Resource#' . get_resource_id($value) . ' of type ' . get_resource_type($value); |
|
4428
|
|
|
} |
|
4429
|
|
|
|
|
4430
|
|
|
// Cas par défaut |
|
4431
|
|
|
return $default; |
|
4432
|
|
|
} |
|
4433
|
|
|
|
|
4434
|
|
|
/** |
|
4435
|
|
|
* Check if a user has access to a file |
|
4436
|
|
|
* |
|
4437
|
|
|
* @param integer $userId |
|
4438
|
|
|
* @param integer $fileId |
|
4439
|
|
|
* @return boolean |
|
4440
|
|
|
*/ |
|
4441
|
|
|
function userHasAccessToFile(int $userId, int $fileId): bool |
|
4442
|
|
|
{ |
|
4443
|
|
|
// Check if user is admin |
|
4444
|
|
|
// Refuse access if user does not exist and/or is admin |
|
4445
|
|
|
$user = DB::queryFirstRow( |
|
4446
|
|
|
'SELECT admin |
|
4447
|
|
|
FROM ' . prefixTable('users') . ' |
|
4448
|
|
|
WHERE id = %i', |
|
4449
|
|
|
$userId |
|
4450
|
|
|
); |
|
4451
|
|
|
if (DB::count() === 0 || (int) $user['admin'] === 1) { |
|
4452
|
|
|
return false; |
|
4453
|
|
|
} |
|
4454
|
|
|
|
|
4455
|
|
|
// Get file info |
|
4456
|
|
|
$file = DB::queryFirstRow( |
|
4457
|
|
|
'SELECT f.id_item, i.id_tree |
|
4458
|
|
|
FROM ' . prefixTable('files') . ' as f |
|
4459
|
|
|
INNER JOIN ' . prefixTable('items') . ' AS i ON i.id = f.id_item |
|
4460
|
|
|
WHERE f.id = %i', |
|
4461
|
|
|
$fileId |
|
4462
|
|
|
); |
|
4463
|
|
|
if (DB::count() === 0) { |
|
4464
|
|
|
return false; |
|
4465
|
|
|
} |
|
4466
|
|
|
|
|
4467
|
|
|
// Check if user has access to the item |
|
4468
|
|
|
include_once __DIR__. '/items.queries.php'; |
|
4469
|
|
|
$itemAccess = getCurrentAccessRights( |
|
4470
|
|
|
(int) filter_var($userId, FILTER_SANITIZE_NUMBER_INT), |
|
4471
|
|
|
(int) filter_var($file['id_item'], FILTER_SANITIZE_NUMBER_INT), |
|
4472
|
|
|
(int) filter_var($file['id_tree'], FILTER_SANITIZE_NUMBER_INT), |
|
4473
|
|
|
(string) filter_var('show', FILTER_SANITIZE_SPECIAL_CHARS), |
|
4474
|
|
|
); |
|
4475
|
|
|
|
|
4476
|
|
|
return $itemAccess['access'] === true; |
|
4477
|
|
|
} |
|
4478
|
|
|
|
|
4479
|
|
|
/** |
|
4480
|
|
|
* Check if a user has access to a backup file |
|
4481
|
|
|
* |
|
4482
|
|
|
* @param integer $userId |
|
4483
|
|
|
* @param string $file |
|
4484
|
|
|
* @param string $key |
|
4485
|
|
|
* @param string $keyTmp |
|
4486
|
|
|
* @return boolean |
|
4487
|
|
|
*/ |
|
4488
|
|
|
function userHasAccessToBackupFile(int $userId, string $file, string $key, string $keyTmp): bool |
|
4489
|
|
|
{ |
|
4490
|
|
|
$session = SessionManager::getSession(); |
|
4491
|
|
|
|
|
4492
|
|
|
// Ensure session keys are ok |
|
4493
|
|
|
if ($session->get('key') !== $key || $session->get('user-key_tmp') !== $keyTmp) { |
|
4494
|
|
|
return false; |
|
4495
|
|
|
} |
|
4496
|
|
|
|
|
4497
|
|
|
// Check if user is admin |
|
4498
|
|
|
// Refuse access if user does not exist and/or is not admin |
|
4499
|
|
|
$user = DB::queryFirstRow( |
|
4500
|
|
|
'SELECT admin |
|
4501
|
|
|
FROM ' . prefixTable('users') . ' |
|
4502
|
|
|
WHERE id = %i', |
|
4503
|
|
|
$userId |
|
4504
|
|
|
); |
|
4505
|
|
|
if (DB::count() === 0 || (int) $user['admin'] === 0) { |
|
4506
|
|
|
return false; |
|
4507
|
|
|
} |
|
4508
|
|
|
|
|
4509
|
|
|
// Ensure that user has performed the backup |
|
4510
|
|
|
DB::queryFirstRow( |
|
4511
|
|
|
'SELECT f.id |
|
4512
|
|
|
FROM ' . prefixTable('log_system') . ' as f |
|
4513
|
|
|
WHERE f.type = %s AND f.label = %s AND f.qui = %i AND f.field_1 = %s', |
|
4514
|
|
|
'admin_action', |
|
4515
|
|
|
'dataBase backup', |
|
4516
|
|
|
$userId, |
|
4517
|
|
|
$file |
|
4518
|
|
|
); |
|
4519
|
|
|
if (DB::count() === 0) { |
|
4520
|
|
|
return false; |
|
4521
|
|
|
} |
|
4522
|
|
|
|
|
4523
|
|
|
return true; |
|
4524
|
|
|
} |
|
4525
|
|
|
|
|
4526
|
|
|
/** |
|
4527
|
|
|
* Ensure that personal items have only keys for their owner |
|
4528
|
|
|
* |
|
4529
|
|
|
* @param integer $userId |
|
4530
|
|
|
* @param integer $itemId |
|
4531
|
|
|
* @return boolean |
|
4532
|
|
|
*/ |
|
4533
|
|
|
function EnsurePersonalItemHasOnlyKeysForOwner(int $userId, int $itemId): bool |
|
4534
|
|
|
{ |
|
4535
|
|
|
// Check if user is admin |
|
4536
|
|
|
// Refuse access if user does not exist and/or is admin |
|
4537
|
|
|
$user = DB::queryFirstRow( |
|
4538
|
|
|
'SELECT admin |
|
4539
|
|
|
FROM ' . prefixTable('users') . ' |
|
4540
|
|
|
WHERE id = %i', |
|
4541
|
|
|
$userId |
|
4542
|
|
|
); |
|
4543
|
|
|
if (DB::count() === 0 || (int) $user['admin'] === 1) { |
|
4544
|
|
|
return false; |
|
4545
|
|
|
} |
|
4546
|
|
|
|
|
4547
|
|
|
// Get item info |
|
4548
|
|
|
$item = DB::queryFirstRow( |
|
4549
|
|
|
'SELECT i.perso, i.id_tree |
|
4550
|
|
|
FROM ' . prefixTable('items') . ' as i |
|
4551
|
|
|
WHERE i.id = %i', |
|
4552
|
|
|
$itemId |
|
4553
|
|
|
); |
|
4554
|
|
|
if (DB::count() === 0 || (int) $item['perso'] === 0) { |
|
4555
|
|
|
return false; |
|
4556
|
|
|
} |
|
4557
|
|
|
|
|
4558
|
|
|
// Get item owner |
|
4559
|
|
|
$itemOwner = DB::queryFirstRow( |
|
4560
|
|
|
'SELECT li.id_user |
|
4561
|
|
|
FROM ' . prefixTable('log_items') . ' as li |
|
4562
|
|
|
WHERE li.id_item = %i AND li.action = %s', |
|
4563
|
|
|
$itemId, |
|
4564
|
|
|
'at_creation' |
|
4565
|
|
|
); |
|
4566
|
|
|
if (DB::count() === 0 || (int) $itemOwner['id_user'] !== $userId) { |
|
4567
|
|
|
return false; |
|
4568
|
|
|
} |
|
4569
|
|
|
|
|
4570
|
|
|
// Delete all keys for this item except for the owner and TeamPass system user |
|
4571
|
|
|
DB::delete( |
|
4572
|
|
|
prefixTable('sharekeys_items'), |
|
4573
|
|
|
'object_id = %i AND user_id != %i', |
|
4574
|
|
|
$itemId, |
|
4575
|
|
|
$userId |
|
4576
|
|
|
); |
|
4577
|
|
|
DB::delete( |
|
4578
|
|
|
prefixTable('sharekeys_files'), |
|
4579
|
|
|
'object_id IN (SELECT id FROM '.prefixTable('files').' WHERE id_item = %i) AND user_id != %i', |
|
4580
|
|
|
$itemId, |
|
4581
|
|
|
$userId |
|
4582
|
|
|
); |
|
4583
|
|
|
DB::delete( |
|
4584
|
|
|
prefixTable('sharekeys_fields'), |
|
4585
|
|
|
'object_id IN (SELECT id FROM '.prefixTable('fields').' WHERE id_item = %i) AND user_id != %i', |
|
4586
|
|
|
$itemId, |
|
4587
|
|
|
$userId |
|
4588
|
|
|
); |
|
4589
|
|
|
DB::delete( |
|
4590
|
|
|
prefixTable('sharekeys_logs'), |
|
4591
|
|
|
'object_id IN (SELECT id FROM '.prefixTable('log_items').' WHERE id_item = %i) AND user_id != %i', |
|
4592
|
|
|
$itemId, |
|
4593
|
|
|
$userId |
|
4594
|
|
|
); |
|
4595
|
|
|
|
|
4596
|
|
|
return true; |
|
4597
|
|
|
} |
|
4598
|
|
|
|
|
4599
|
|
|
/** |
|
4600
|
|
|
* Insert a new record in a table with an "is_current" flag. |
|
4601
|
|
|
* This function ensures that only one record per user has the "is_current" flag set to true. |
|
4602
|
|
|
* |
|
4603
|
|
|
* @param int $userId The ID of the user. |
|
4604
|
|
|
* @param string $privateKey The private key to be inserted. |
|
4605
|
|
|
* @return void |
|
4606
|
|
|
*/ |
|
4607
|
|
|
function insertPrivateKeyWithCurrentFlag(int $userId, string $privateKey) { |
|
4608
|
|
|
try { |
|
4609
|
|
|
DB::startTransaction(); |
|
4610
|
|
|
|
|
4611
|
|
|
// Disable is_current for existing records of the user |
|
4612
|
|
|
DB::update( |
|
4613
|
|
|
prefixTable('user_private_keys'), |
|
4614
|
|
|
array('is_current' => false), |
|
4615
|
|
|
"user_id = %i AND is_current = %i", |
|
4616
|
|
|
$userId, |
|
4617
|
|
|
true |
|
4618
|
|
|
); |
|
4619
|
|
|
|
|
4620
|
|
|
// Insert the new record |
|
4621
|
|
|
DB::insert( |
|
4622
|
|
|
prefixTable('user_private_keys'), |
|
4623
|
|
|
array( |
|
4624
|
|
|
'user_id' => $userId, |
|
4625
|
|
|
'private_key' => $privateKey, |
|
4626
|
|
|
'is_current' => true, |
|
4627
|
|
|
) |
|
4628
|
|
|
); |
|
4629
|
|
|
|
|
4630
|
|
|
DB::commit(); |
|
4631
|
|
|
|
|
4632
|
|
|
} catch (Exception $e) { |
|
4633
|
|
|
DB::rollback(); |
|
4634
|
|
|
throw $e; |
|
4635
|
|
|
} |
|
4636
|
|
|
} |