dolibarr 25.0.0-alpha
security.lib.php
Go to the documentation of this file.
1<?php
2
3/* Copyright (C) 2008-2021 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2008-2021 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2020 Ferran Marcet <fmarcet@2byte.es>
6 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
7 * Copyright (C) 2025-2026 Frédéric France <frederic.france@free.fr>
8 * Copyright (C) 2026 William Mead <william@m34d.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 * or see https://www.gnu.org/
23 */
24
33include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
34
35
45function dol_encode($chain, $key = '1')
46{
47 if (is_numeric($key) && $key == '1') { // rule 1 is offset of 17 for char
48 $output_tab = array();
49 $strlength = strlen($chain);
50 for ($i = 0; $i < $strlength; $i++) {
51 $output_tab[$i] = chr(ord(substr($chain, $i, 1)) + 17);
52 }
53 $chain = implode("", $output_tab);
54 } elseif ($key) {
55 $result = '';
56 $strlength = strlen($chain);
57 for ($i = 0; $i < $strlength; $i++) {
58 $keychar = substr($key, ($i % strlen($key)) - 1, 1);
59 $result .= chr(ord(substr($chain, $i, 1)) + (ord($keychar) - 65));
60 }
61 $chain = $result;
62 }
63
64 return base64_encode($chain);
65}
66
77function dol_decode($chain, $key = '1')
78{
79 $chain = base64_decode($chain);
80
81 if (is_numeric($key) && $key == '1') { // rule 1 is offset of 17 for char
82 $output_tab = array();
83 $strlength = strlen($chain);
84 for ($i = 0; $i < $strlength; $i++) {
85 $output_tab[$i] = chr(ord(substr($chain, $i, 1)) - 17);
86 }
87
88 $chain = implode("", $output_tab);
89 } elseif ($key) {
90 $result = '';
91 $strlength = strlen($chain);
92 for ($i = 0; $i < $strlength; $i++) {
93 $keychar = substr($key, ($i % strlen($key)) - 1, 1);
94 $result .= chr(ord(substr($chain, $i, 1)) - (ord($keychar) - 65));
95 }
96 $chain = $result;
97 }
98
99 return $chain;
100}
101
108function dolGetRandomBytes($length)
109{
110 if (function_exists('random_bytes')) { // Available with PHP 7+ only.
111 return bin2hex(random_bytes((int) floor($length / 2))); // the bin2hex will double the number of bytes so we take length / 2
112 }
113
114 return bin2hex(openssl_random_pseudo_bytes((int) floor($length / 2))); // the bin2hex will double the number of bytes so we take length / 2. May be very slow on Windows.
115}
116
125function dolGetLdapPasswordHash($password, $type = 'md5')
126{
127 if (empty($type)) {
128 $type = 'md5';
129 }
130
131 $salt = substr(sha1((string) time()), 0, 8);
132
133 if ($type === 'md5') {
134 return '{MD5}' . base64_encode(hash("md5", $password, true)); //For OpenLdap with md5 (based on an unencrypted password in base)
135 } elseif ($type === 'md5frommd5') {
136 return '{MD5}' . base64_encode(hex2bin($password)); // Create OpenLDAP MD5 password from Dolibarr MD5 password
137 } elseif ($type === 'smd5') {
138 return "{SMD5}" . base64_encode(hash("md5", $password . $salt, true) . $salt);
139 } elseif ($type === 'sha') {
140 return '{SHA}' . base64_encode(hash("sha1", $password, true));
141 } elseif ($type === 'ssha') {
142 return "{SSHA}" . base64_encode(hash("sha1", $password . $salt, true) . $salt);
143 } elseif ($type === 'sha256') {
144 return "{SHA256}" . base64_encode(hash("sha256", $password, true));
145 } elseif ($type === 'ssha256') {
146 return "{SSHA256}" . base64_encode(hash("sha256", $password . $salt, true) . $salt);
147 } elseif ($type === 'sha384') {
148 return "{SHA384}" . base64_encode(hash("sha384", $password, true));
149 } elseif ($type === 'ssha384') {
150 return "{SSHA384}" . base64_encode(hash("sha384", $password . $salt, true) . $salt);
151 } elseif ($type === 'sha512') {
152 return "{SHA512}" . base64_encode(hash("sha512", $password, true));
153 } elseif ($type === 'ssha512') {
154 return "{SSHA512}" . base64_encode(hash("sha512", $password . $salt, true) . $salt);
155 } elseif ($type === 'crypt') {
156 return '{CRYPT}' . crypt($password, $salt);
157 } elseif ($type === 'clear') {
158 return '{CLEAR}' . $password; // Just for test, plain text password is not secured !
159 }
160 return "";
161}
162
183function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $feature2 = '', $dbt_keyfield = 'fk_soc', $dbt_select = 'rowid', $isdraft = 0, $nodie = 0, $mode = '')
184{
185 global $hookmanager;
186
187 // Define $objectid
188 if (is_object($object)) {
189 $objectid = $object->id;
190 } else {
191 $objectid = $object; // $objectid can be X or 'X,Y,Z'
192 }
193 if ($objectid == "-1") {
194 $objectid = 0;
195 }
196 if ($objectid) {
197 $objectid = preg_replace('/[^0-9\.\,]/', '', (string) $objectid); // For the case value is coming from a non sanitized user input
198 }
199
200 //dol_syslog("functions.lib:restrictedArea $feature, $object, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft");
201 /*print "user_id=".$user->id.", features=".$features.", feature2=".$feature2.", object=".$object;
202 print ", dbtablename=".$tableandshare.", dbt_socfield=".$dbt_keyfield.", dbt_select=".$dbt_select;
203 print ", perm: user->hasRight(".$features.($feature2 ? ",".$feature2 : "").", lire) = ".($feature2 ? $user->hasRight($features, $feature2, 'lire') : $user->hasRight($features, 'lire'))."<br>";
204 */
205
206 $parentfortableentity = '';
207
208 $originalfeatures = $features;
209
210 // Fix syntax of $features param to support non standard module names.
211 if ($features == 'agenda') {
212 $tableandshare = 'actioncomm&societe';
213 $feature2 = 'myactions|allactions';
214 $dbt_select = 'id';
215 } elseif ($features == 'bank') {
216 $features = 'banque';
217 } elseif ($features == 'remisecheque') {
218 $features = 'banque';
219 } elseif ($features == 'facturerec') {
220 $features = 'facture';
221 } elseif ($features == 'supplier_invoicerec') {
222 $features = 'fournisseur';
223 $feature2 = 'facture';
224 } elseif ($features == 'company') {
225 $features = 'societe';
226 } elseif ($features == 'mo') {
227 $features = 'mrp';
228 } elseif ($features == 'member') {
229 $features = 'adherent';
230 } elseif ($features == 'subscription') {
231 $features = 'adherent';
232 $feature2 = 'cotisation';
233 $tableandshare = 'subscription&adherent';
234 $parentfortableentity = 'fk_adherent@adherent'; // A subscription has no entity, the entity is the one of its member
235 } elseif ($features == 'website' && is_object($object) && $object->element == 'websitepage') {
236 $parentfortableentity = 'fk_website@website';
237 } elseif ($features == 'project') {
238 $features = 'projet';
239 } elseif ($features == 'project_task') {
240 $features = 'projet';
241 $objectid = (int) $object->fk_project;
242 $object = $objectid;
243 } elseif (is_object($object) && ($features == 'conferenceorbooth@eventorganization' || ($features == 'eventorganization' && $object->element == 'conferenceorbooth'))) {
244 // The module of an event organization declares no permission of its own, on purpose.
245 // Permission are done on project table.
246 if (!empty($user->socid) || empty($object->fk_project)) {
247 if ($nodie) {
248 return 0;
249 } else {
251 }
252 }
253 $features = 'projet';
254 $tableandshare = 'projet&project';
255 $objectid = (int) $object->fk_project;
256 $object = $objectid;
257 } elseif ($features == 'product') {
258 $features = 'produit';
259 } elseif ($features == 'productbatch') {
260 $features = 'produit';
261 } elseif ($features == 'tax') {
262 $feature2 = 'charges';
263 } elseif ($features == 'workstation') {
264 $feature2 = 'workstation';
265 } elseif ($features == 'hrm' && is_object($object) && in_array($object->element, array('job', 'position', 'skill'))) {
266 $feature2 = 'all'; // These 3 objects have no permission of their own, they share the level "all"
267 } elseif ($features == 'recruitment' && is_object($object) && in_array($object->element, array('recruitmentjobposition', 'recruitmentcandidature'))) {
268 // The recruitment module declares no permission at its first level, all its objects share the
269 // second level "recruitmentjobposition". When the caller provides the module name only
270 // (like document.php with its modulepart), we complete the missing parameters from the object.
271 if (empty($feature2)) {
272 $feature2 = 'recruitmentjobposition';
273 }
274 if (empty($tableandshare)) {
275 $tableandshare = $object->table_element;
276 }
277 } elseif ($features == 'stocktransfer' && is_object($object) && $object->element == 'stocktransfer') {
278 $feature2 = 'stocktransfer'; // This module declares no permission at its first level, only this one
279 } elseif (in_array($features, array('fournisseur', 'commande_fournisseur', 'facture_fournisseur', 'order_supplier', 'invoice_supplier'))) { // When vendor invoice and purchase order are into module 'fournisseur'
280 $features = 'fournisseur';
281 if (is_object($object) && $object->element == 'invoice_supplier') {
282 $feature2 = 'facture';
283 } elseif (is_object($object) && $object->element == 'order_supplier') {
284 $feature2 = 'commande';
285 }
286 } elseif ($features == 'payment_sc') {
287 $tableandshare = 'paiementcharge';
288 $parentfortableentity = 'fk_charge@chargesociales';
289 } elseif ($features == 'payment_vat') {
290 $tableandshare = 'payment_vat';
291 $parentfortableentity = 'fk_tva@tva';
292 }
293
294 // if commonObjectLine : Using many2one related commonObject
295 // @see commonObjectLine::parentElement
296 if (in_array($features, ['commandedet', 'propaldet', 'facturedet', 'supplier_proposaldet', 'evaluationdet', 'skilldet', 'deliverydet', 'contratdet'])) {
297 $features = substr($features, 0, -3); // @phan-suppress-current-line DolibarrForbiddenFunctionPlugin
298 } elseif (in_array($features, ['stocktransferline', 'inventoryline', 'bomline', 'expensereport_det', 'facture_fourn_det'])) {
299 $features = substr($features, 0, -4); // @phan-suppress-current-line DolibarrForbiddenFunctionPlugin
300 } elseif ($features == 'commandefournisseurdispatch') {
301 $features = 'commandefournisseur';
302 } elseif ($features == 'invoice_supplier_det_rec') {
303 $features = 'invoice_supplier_rec';
304 }
305 if ($features == 'evaluation') {
306 $features = 'hrm';
307 $feature2 = 'evaluation';
308 }
309
310 // When the object is a task (element='project_task') and $feature2 is empty,
311 // $checkUserAccessToObject() falls into the $checkproject path and uses the task ID
312 // as project ID, which always fails. Setting $feature2='project_task' triggers the
313 // normalization at line 974 that redirects to the $checktask path, which correctly
314 // resolves $task->fk_project before calling getProjectsAuthorizedForUser().
315 if (is_object($object) && in_array($object->element, array('project_task', 'task'))
316 && (empty($features) || in_array($features, array('projet', 'project')))
317 && empty($feature2)) {
318 $features = 'projet';
319 $feature2 = 'project_task';
320 if (empty($tableandshare)) {
321 $tableandshare = 'projet_task';
322 }
323 }
324
325 // If the $features parameter is empty, there is no permission we can check, so the access must
326 // be refused. Without this test, all the checks of permission below would be silently skipped and
327 // the access would be granted to any user without any test (see also selectobject.php that forces
328 // its features parameter to 'unknownobject' instead of '' for the same reason).
329 if (empty($features) || trim((string) $features) === '') {
330 dol_syslog('restrictedArea() called with an empty features parameter, we refuse the access', LOG_WARNING);
331 if ($nodie) {
332 return 0;
333 } else {
334 accessforbidden('Bad value for parameter features');
335 }
336 }
337
338 // print $features.' - '.$tableandshare.' - '.$feature2.' - '.$dbt_select."\n";
339
340 // Get more permissions checks from hooks
341 $parameters = array(
342 'features' => $features,
343 'feature2' => $feature2,
344 'originalfeatures' => $originalfeatures,
345 'tableandshare' => $tableandshare,
346 'object' => $object,
347 'objectid' => $objectid,
348 'dbt_keyfield' => $dbt_keyfield,
349 'dbt_select' => $dbt_select,
350 'idtype' => $dbt_select,
351 'isdraft' => $isdraft,
352 'nodie' => $nodie,
353 'mode' => $mode
354 );
355 if (!empty($hookmanager)) {
356 $reshook = $hookmanager->executeHooks('restrictedArea', $parameters);
357
358 if (isset($hookmanager->resArray['result'])) {
359 if ($hookmanager->resArray['result'] == 0) {
360 if ($nodie) {
361 return 0;
362 } else {
363 accessforbidden(); // Module returns 0, so access forbidden
364 }
365 }
366 }
367 if ($reshook > 0) { // No other test done.
368 return 1;
369 }
370 }
371
372 // Features/modules to check (to support the & and | operator)
373 $featuresarray = array($features);
374 if (preg_match('/&/', $features)) {
375 $featuresarray = explode("&", $features);
376 } elseif (preg_match('/\|/', $features)) {
377 $featuresarray = explode("|", $features);
378 }
379
380 // More subfeatures to check
381 if (!empty($feature2)) {
382 $feature2 = explode("|", $feature2);
383 }
384
385 $listofmodules = explode(',', getDolGlobalString('MAIN_MODULES_FOR_EXTERNAL'));
386
387 //var_dump($featuresarray, $object->id);
388
389 // Check read permission from module
390 $readok = 1;
391 $nbko = 0;
392 foreach ($featuresarray as $feature) { // first we check nb of test ko
393 $featureforlistofmodule = $feature;
394 if ($featureforlistofmodule == 'produit') {
395 $featureforlistofmodule = 'product';
396 }
397 if ($featureforlistofmodule == 'supplier_proposal') {
398 $featureforlistofmodule = 'supplierproposal';
399 }
400 if (!empty($user->socid) && getDolGlobalString('MAIN_MODULES_FOR_EXTERNAL') && !in_array($featureforlistofmodule, $listofmodules)) { // If limits on modules for external users, module must be into list of modules for external users
401 $readok = 0;
402 $nbko++;
403 continue;
404 }
405
406 if (in_array($feature, array('societe', 'company')) && (empty($feature2) || !in_array('contact', $feature2))) {
407 if (!$user->hasRight('societe', 'lire') && !$user->hasRight('fournisseur', 'lire')) {
408 $readok = 0;
409 $nbko++;
410 }
411 } elseif ((in_array($feature, array('societe', 'company')) && (!empty($feature2) && in_array('contact', $feature2))) || $feature == 'contact') {
412 if (!$user->hasRight('societe', 'contact', 'lire')) {
413 $readok = 0;
414 $nbko++;
415 }
416 } elseif ($feature == 'produit|service') {
417 if (!$user->hasRight('produit', 'lire') && !$user->hasRight('service', 'lire')) {
418 $readok = 0;
419 $nbko++;
420 }
421 } elseif ($feature == 'produit') {
422 // $object is only a real Product/Service when the check is scoped to one specific
423 // record (e.g. a product card); a generic area check (e.g. the product/service
424 // dashboard) never sets it, so it keeps its default int value and has no ->type to
425 // tell products and services apart - fall back to requiring either right.
426 if (!is_object($object)) {
427 if (!$user->hasRight('produit', 'lire') && !$user->hasRight('service', 'lire')) {
428 $readok = 0;
429 $nbko++;
430 }
431 } elseif ($object->type == 0 && !$user->hasRight('produit', 'lire')) {
432 $readok = 0;
433 $nbko++;
434 } elseif ($object->type == 1 && !$user->hasRight('service', 'lire')) {
435 $readok = 0;
436 $nbko++;
437 }
438 } elseif ($feature == 'prelevement') {
439 if (!$user->hasRight('prelevement', 'bons', 'lire')) {
440 $readok = 0;
441 $nbko++;
442 }
443 } elseif ($feature == 'cheque') {
444 if (!$user->hasRight('banque', 'cheque')) {
445 $readok = 0;
446 $nbko++;
447 }
448 } elseif ($feature == 'projet') {
449 if (!$user->hasRight('projet', 'lire') && !$user->hasRight('projet', 'all', 'lire')) {
450 $readok = 0;
451 $nbko++;
452 }
453 } elseif ($feature == 'payment') {
454 if (!$user->hasRight('facture', 'lire')) {
455 $readok = 0;
456 $nbko++;
457 }
458 } elseif ($feature == 'payment_supplier') {
459 if (!$user->hasRight('fournisseur', 'facture', 'lire')) {
460 $readok = 0;
461 $nbko++;
462 }
463 } elseif ($feature == 'payment_sc') {
464 if (!$user->hasRight('tax', 'charges', 'lire')) {
465 $readok = 0;
466 $nbko++;
467 }
468 } elseif ($feature == 'payment_vat') {
469 if (!$user->hasRight('tax', 'charges', 'lire')) {
470 $readok = 0;
471 $nbko++;
472 }
473 } elseif ($feature == 'webhook') {
474 if (empty($user->admin)) {
475 $readok = 0;
476 $nbko++;
477 }
478 } elseif (!empty($feature2)) { // This is for permissions on 2 levels (module->object->read)
479 $tmpreadok = 1;
480 foreach ($feature2 as $subfeature) {
481 if ($subfeature == 'user' && $user->id == $objectid) {
482 continue; // A user can always read its own card
483 }
484 if ($subfeature == 'fiscalyear' && $user->hasRight('accounting', 'fiscalyear', 'write')) {
485 // only one right for fiscalyear
486 $tmpreadok = 1;
487 continue;
488 }
489 if (!empty($subfeature) && !$user->hasRight($feature, $subfeature, 'lire') && !$user->hasRight($feature, $subfeature, 'read')) {
490 $tmpreadok = 0;
491 } elseif (empty($subfeature) && !$user->hasRight($feature, 'lire') && !$user->hasRight($feature, 'read')) {
492 $tmpreadok = 0;
493 } else {
494 $tmpreadok = 1;
495 break;
496 } // Break is to bypass second test if the first is ok
497 }
498 if (!$tmpreadok) { // We found a test on feature that is ko
499 $readok = 0; // All tests are ko (we manage here the and, the or will be managed later using $nbko).
500 $nbko++;
501 }
502 } elseif (!empty($feature) && ($feature != 'user' && $feature != 'usergroup')) { // This is permissions on 1 level (module->read)
503 if (!$user->hasRight($feature, 'lire')
504 && !$user->hasRight($feature, 'read')
505 && !$user->hasRight($feature, 'run')) {
506 $readok = 0;
507 $nbko++;
508 }
509 }
510 }
511
512 // If a or and at least one ok
513 if (preg_match('/\|/', $features) && $nbko < count($featuresarray)) {
514 $readok = 1;
515 }
516
517 if (!$readok) {
518 if ($nodie) {
519 return 0;
520 } else {
522 }
523 }
524 //print "Read access is ok";
525
526 // Check write permission from module (we need to know write permission to create but also to delete drafts record or to upload files)
527 $createok = 1;
528 $nbko = 0;
529 if ($mode == 'read') {
530 $wemustcheckpermissionforcreate = 0;
531 $wemustcheckpermissionfordeletedraft = 0;
532 $wemustcheckpermissionfordelete = 0;
533 } elseif ($mode == 'write') {
534 $wemustcheckpermissionforcreate = 1;
535 $wemustcheckpermissionfordeletedraft = 1;
536 $wemustcheckpermissionfordelete = 0;
537 } elseif ($mode == 'delete') {
538 $wemustcheckpermissionforcreate = 0;
539 $wemustcheckpermissionfordeletedraft = 0;
540 $wemustcheckpermissionfordelete = 1;
541 } else {
542 // This is possible in a GUI context only
543 $wemustcheckpermissionforcreate = (GETPOST('sendit', 'alpha') || GETPOST('linkit', 'alpha') || in_array(GETPOST('action', 'aZ09'), array('create', 'update', 'set', 'upload', 'add_element_resource', 'confirm_deletebank', 'confirm_delete_linked_resource')) || GETPOST('roworder', 'alpha', 2));
544 $wemustcheckpermissionfordeletedraft = ((GETPOST("action", "aZ09") == 'confirm_delete' && GETPOST("confirm", "aZ09") == 'yes') || GETPOST("action", "aZ09") == 'delete');
545 $wemustcheckpermissionfordelete = ((GETPOST("action", "aZ09") == 'confirm_delete' && GETPOST("confirm", "aZ09") == 'yes') || GETPOST("action", "aZ09") == 'delete');
546 }
547
548 //var_dump($wemustcheckpermissionforcreate, $wemustcheckpermissionfordeletedraft, $wemustcheckpermissionfordelete);
549
550 if ($wemustcheckpermissionforcreate || $wemustcheckpermissionfordeletedraft) {
551 foreach ($featuresarray as $feature) {
552 if ($feature == 'contact') {
553 if (!$user->hasRight('societe', 'contact', 'creer')) {
554 $createok = 0;
555 $nbko++;
556 }
557 } elseif ($feature == 'produit|service') {
558 if (!$user->hasRight('produit', 'creer') && !$user->hasRight('service', 'creer')) {
559 $createok = 0;
560 $nbko++;
561 }
562 } elseif ($feature == 'prelevement') {
563 if (!$user->hasRight('prelevement', 'bons', 'creer')) {
564 $createok = 0;
565 $nbko++;
566 }
567 } elseif ($feature == 'commande_fournisseur') {
568 if (!$user->hasRight('fournisseur', 'commande', 'creer') || !$user->hasRight('supplier_order', 'creer')) {
569 $createok = 0;
570 $nbko++;
571 }
572 } elseif ($feature == 'banque') {
573 if (!$user->hasRight('banque', 'modifier')) {
574 $createok = 0;
575 $nbko++;
576 }
577 } elseif ($feature == 'cheque') {
578 if (!$user->hasRight('banque', 'cheque')) {
579 $createok = 0;
580 $nbko++;
581 }
582 } elseif ($feature == 'import') {
583 if (!$user->hasRight('import', 'run')) {
584 $createok = 0;
585 $nbko++;
586 }
587 } elseif ($feature == 'ecm') {
588 if (!$user->hasRight('ecm', 'upload')) {
589 $createok = 0;
590 $nbko++;
591 }
592 } elseif ($feature == 'modulebuilder') {
593 if (!$user->hasRight('modulebuilder', 'run')) {
594 $createok = 0;
595 $nbko++;
596 }
597 } elseif ($feature == 'payment') {
598 if (!$user->hasRight('facture', 'paiement')) {
599 $createok = 0;
600 $nbko++;
601 }
602 } elseif ($feature == 'payment_supplier') { // Permission to write on a payment of an invoice is permission to edit an invoice.
603 if (!$user->hasRight('fournisseur', 'facture', 'creer')) {
604 $createok = 0;
605 $nbko++;
606 }
607 } elseif ($feature == 'webhook') {
608 if (empty($user->admin)) {
609 $createok = 0;
610 $nbko++;
611 }
612 } elseif (!empty($feature2)) { // This is for permissions on 2 levels (module->object->write)
613 foreach ($feature2 as $subfeature) {
614 if ($subfeature == 'user' && $user->id == $objectid && $user->hasRight('user', 'self', 'creer')) {
615 continue; // User can edit its own card
616 }
617 if ($subfeature == 'user' && $user->id == $objectid && $user->hasRight('user', 'self', 'password')) {
618 continue; // User can edit its own password
619 }
620 if ($subfeature == 'user' && $user->id != $objectid && $user->hasRight('user', 'user', 'password')) {
621 continue; // User can edit another user's password
622 }
623
624 if (!$user->hasRight($feature, $subfeature, 'creer')
625 && !$user->hasRight($feature, $subfeature, 'write')
626 && !$user->hasRight($feature, $subfeature, 'create')) {
627 $createok = 0;
628 $nbko++;
629 } else {
630 $createok = 1;
631 // Break to bypass second test if the first is ok
632 break;
633 }
634 }
635 } elseif (!empty($feature)) { // This is for permissions on 1 levels (module->write)
636 //print '<br>feature='.$feature.' creer='.$user->rights->$feature->creer.' write='.$user->rights->$feature->write; exit;
637 if (!$user->hasRight($feature, 'creer')
638 && !$user->hasRight($feature, 'write')
639 && !$user->hasRight($feature, 'create')) {
640 $createok = 0;
641 $nbko++;
642 }
643 }
644 }
645
646 // If a or and at least one ok
647 if (preg_match('/\|/', $features) && $nbko < count($featuresarray)) {
648 $createok = 1;
649 }
650
651 if ($wemustcheckpermissionforcreate && !$createok) {
652 if ($nodie) {
653 return 0;
654 } else {
656 }
657 }
658 //print "Write access is ok";
659 }
660
661 // Check create user permission (special case)
662 $createuserok = 1;
663 if (GETPOST('action', 'aZ09') == 'confirm_create_user' && GETPOST("confirm", 'aZ09') == 'yes') {
664 if (!$user->hasRight('user', 'user', 'creer')) {
665 $createuserok = 0;
666 }
667
668 if (!$createuserok) {
669 if ($nodie) {
670 return 0;
671 } else {
673 }
674 }
675 //print "Create user access is ok";
676 }
677
678 // Check delete permission from module
679 $deleteok = 1;
680 $nbko = 0;
681 if ($wemustcheckpermissionfordelete) {
682 foreach ($featuresarray as $feature) {
683 if ($feature == 'bookmark') {
684 if (!$user->hasRight('bookmark', 'supprimer')) {
685 if ($user->id != $object->fk_user || !$user->hasRight('bookmark', 'creer')) {
686 $deleteok = 0;
687 }
688 }
689 } elseif ($feature == 'contact') {
690 if (!$user->hasRight('societe', 'contact', 'supprimer')) {
691 $deleteok = 0;
692 }
693 } elseif ($feature == 'produit|service') {
694 if (!$user->hasRight('produit', 'supprimer') && !$user->hasRight('service', 'supprimer')) {
695 $deleteok = 0;
696 }
697 } elseif ($feature == 'commande_fournisseur') {
698 if (!$user->hasRight('fournisseur', 'commande', 'supprimer')) {
699 $deleteok = 0;
700 }
701 } elseif ($feature == 'payment_supplier') { // Permission to delete a payment of an invoice is permission to edit an invoice.
702 if (!$user->hasRight('fournisseur', 'facture', 'creer')) {
703 $deleteok = 0;
704 }
705 } elseif ($feature == 'payment') {
706 if (!$user->hasRight('facture', 'paiement')) {
707 $deleteok = 0;
708 }
709 } elseif ($feature == 'payment_sc') {
710 if (!$user->hasRight('tax', 'charges', 'creer')) {
711 $deleteok = 0;
712 }
713 } elseif ($feature == 'banque') {
714 if (!$user->hasRight('banque', 'modifier')) {
715 $deleteok = 0;
716 }
717 } elseif ($feature == 'cheque') {
718 if (!$user->hasRight('banque', 'cheque')) {
719 $deleteok = 0;
720 }
721 } elseif ($feature == 'ecm') {
722 if (!$user->hasRight('ecm', 'upload')) {
723 $deleteok = 0;
724 }
725 } elseif ($feature == 'ftp') {
726 if (!$user->hasRight('ftp', 'write')) {
727 $deleteok = 0;
728 }
729 } elseif ($feature == 'salaries') {
730 if (!$user->hasRight('salaries', 'delete')) {
731 $deleteok = 0;
732 }
733 } elseif ($feature == 'adherent') {
734 if (!$user->hasRight('adherent', 'supprimer')) {
735 $deleteok = 0;
736 }
737 } elseif ($feature == 'paymentbybanktransfer') {
738 if (!$user->hasRight('paymentbybanktransfer', 'create')) { // There is no delete permission
739 $deleteok = 0;
740 }
741 } elseif ($feature == 'prelevement') {
742 if (!$user->hasRight('prelevement', 'bons', 'creer')) { // There is no delete permission
743 $deleteok = 0;
744 }
745 } elseif (!empty($feature2)) { // This is for permissions on 2 levels
746 foreach ($feature2 as $subfeature) {
747 if (!$user->hasRight($feature, $subfeature, 'supprimer') && !$user->hasRight($feature, $subfeature, 'delete')) {
748 $deleteok = 0;
749 } else {
750 $deleteok = 1;
751 break;
752 } // For bypass the second test if the first is ok
753 }
754 } elseif (!empty($feature)) { // This is used for permissions on 1 level
755 //print '<br>feature='.$feature.' creer='.$user->rights->$feature->supprimer.' write='.$user->rights->$feature->delete;
756 if (!$user->hasRight($feature, 'supprimer')
757 && !$user->hasRight($feature, 'delete')
758 && !$user->hasRight($feature, 'run')) {
759 $deleteok = 0;
760 }
761 }
762 }
763
764 // If a or and at least one ok
765 if (preg_match('/\|/', $features) && $nbko < count($featuresarray)) {
766 $deleteok = 1;
767 }
768
769 if (!$deleteok && !($isdraft && $createok)) {
770 if ($nodie) {
771 return 0;
772 } else {
774 }
775 }
776 //print "Delete access is ok";
777 }
778
779 // If we have a particular object to check permissions on, we check if $user has permission
780 // for this given object (link to company, is contact for project, ...)
781 if (!empty($objectid) && $objectid > 0) {
782 $ok = checkUserAccessToObject($user, $featuresarray, $object, $tableandshare, $feature2, $dbt_keyfield, $dbt_select, $parentfortableentity);
783 $params = array('objectid' => $objectid, 'features' => implode(',', $featuresarray), 'features2' => $feature2);
784 //print 'checkUserAccessToObject ok='.$ok;
785 if ($nodie) {
786 return $ok ? 1 : 0;
787 } else {
788 if ($ok) {
789 return 1;
790 } else {
791 accessforbidden('', 1, 1, 0, $params);
792 }
793 }
794 }
795
796 return 1;
797}
798
814function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tableandshare = '', $feature2 = '', $dbt_keyfield = '', $dbt_select = 'rowid', $parenttableforentity = '')
815{
816 global $db, $conf;
817
818 if (is_object($object)) {
819 $objectid = $object->id;
820 } else {
821 $objectid = $object; // $objectid can be X or 'X,Y,Z'
822 }
823 $objectid = preg_replace('/[^0-9\.\,]/', '', (string) $objectid); // For the case value is coming from a non sanitized user input
824
825 //dol_syslog("functions.lib:restrictedArea $feature, $object, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft");
826 //print "user_id=".$user->id.", features=".join(',', $featuresarray).", object=".$object;
827 //print ", tableandshare=".$tableandshare.", dbt_socfield=".$dbt_keyfield.", dbt_select=".$dbt_select."<br>";
828
829 // More parameters
830 $params = explode('&', $tableandshare);
831 $dbtablename = (!empty($params[0]) ? $params[0] : '');
832 $sharedelement = (!empty($params[1]) ? $params[1] : $dbtablename);
833
834 foreach ($featuresarray as $feature) {
835 $sql = '';
836
837 //var_dump($feature);exit;
838
839 // Normalize table and feature name for compatibility
840 if ($feature == 'bom') {
841 $feature = 'bom_bom';
842 }
843 if ($feature == 'societe' && !empty($feature2) && is_array($feature2) && in_array('contact', $feature2)) {
844 $feature = 'contact';
845 $feature2 = '';
846 }
847 if ($feature == 'member') {
848 $feature = 'adherent';
849 }
850 if ($feature == 'category') {
851 $feature = 'categorie';
852 }
853 if ($feature == 'bank') {
854 $feature = 'banque';
855 }
856 if ($feature == 'contract') {
857 $dbtablename = 'contrat';
858 }
859 if ($feature == 'mrp') {
860 $dbtablename = 'mrp_mo';
861 }
862 if ($feature == 'order_supplier' || ($feature == 'fournisseur' && is_object($object) && $object->element == 'order_supplier')) {
863 $dbtablename = 'commande_fournisseur';
864 }
865 if ($feature == 'invoice_supplier' || ($feature == 'fournisseur' && is_object($object) && $object->element == 'invoice_supplier')) {
866 $dbtablename = 'facture_fourn';
867 }
868 if ($feature == 'produit') {
869 $dbtablename = 'product';
870 }
871 if ($feature == 'ficheinter') {
872 $dbtablename = 'fichinter';
873 }
874 if ($feature == 'banque') {
875 // The module name (and permission name) is 'banque', but the table of the bank account object is 'bank_account'
876 $dbtablename = 'bank_account';
877 }
878 if ($feature == 'project') {
879 $feature = 'projet';
880 }
881 if ($feature == 'projet' && !empty($feature2) && is_array($feature2) && !empty(array_intersect(array('project_task', 'projet_task'), $feature2))) {
882 $feature = 'project_task';
883 }
884 if ($feature == 'stock') {
885 $dbtablename = 'entrepot';
886 }
887 if ($feature == 'task' || $feature == 'projet_task') {
888 $feature = 'project_task';
889 $dbtablename = 'projet_task';
890 }
891 if ($feature == 'eventorganization') {
892 $feature = 'agenda';
893 $dbtablename = 'actioncomm';
894 }
895 if ($feature == 'payment_sc' && empty($parenttableforentity)) {
896 // If we check perm on payment page but $parenttableforentity not defined, we force value on parent table
897 $parenttableforentity = '';
898 $dbtablename = "chargesociales";
899 $feature = "chargesociales";
900 $objectid = (string) $object->fk_charge;
901 }
902 if ($feature == 'workstation') {
903 $dbtablename = 'workstation_workstation';
904 }
905
906 $checkonentityready = 0;
907
908 // Array to define rules of checks to do
909 // Test on entity only (Objects with no link to company)
910 $check = array('adherent', 'banque', 'bom', 'don', 'mrp', 'user', 'usergroup', 'payment', 'payment_supplier', 'payment_sc', 'product', 'produit', 'service', 'produit|service', 'categorie', 'resource', 'expensereport', 'holiday', 'salaries', 'website', 'recruitment', 'chargesociales', 'knowledgemanagement', 'stock', 'stockmovement', 'workstation');
911 // Test for object Societe
912 $checksoc = array('societe');
913 // Test on entity + link to third party on field $dbt_keyfield. Allowed if link is empty (Ex: contacts...).
914 $checkparentsoc = array('agenda', 'contact', 'contrat', 'ticket');
915 // Test for project object
916 $checkproject = array('projet', 'project');
917 // Test for task object
918 $checktask = array('projet_task', 'project_task');
919 // Check permission among the hierarchy of user
920 $checkhierarchy = array('expensereport', 'holiday', 'hrm');
921 // Check permission among the fk_user (must be myself or null)
922 $checkuser = array('bookmark');
923 // No test
924 $nocheck = array('barcode', 'webhook');
925
926 //$checkdefault = 'all other not already defined'; // Test on entity + link to third party on field $dbt_keyfield. Not allowed if link is empty (Ex: invoice, orders...).
927
928 // If dbtablename not defined, we use same name for table than module name
929 if (empty($dbtablename)) {
930 $dbtablename = $feature;
931 $sharedelement = (!empty($params[1]) ? $params[1] : $dbtablename); // We change dbtablename, so we set sharedelement too.
932 }
933
934 // The default rule reads the columns entity and $dbt_keyfield of the table, but some tables own neither of
935 // them. The sql was then built on columns that do not exist, so it always failed and the access was refused
936 // to the users that this rule applies to.
937 // The rule is selected on the table and not on the element of the object, because $object is an id and not
938 // an object for most of the callers, the card of an asset and the card of a workstation included.
939 if (!empty($objectid) && in_array($dbtablename, array('asset', 'paiement', 'paiementfourn', 'workstation_workstation', 'hrm_job', 'hrm_job_user', 'hrm_skill'))) {
940 // None of these objects is linked to a third party, so an external user can own none of them. The
941 // default rule refused him through a link that does not exist, we must refuse him explicitly instead,
942 // otherwise the rules below, which do not look at the third party of the user at all, would grant it.
943 if (!empty($user->socid)) {
944 return false;
945 }
946 if (in_array($dbtablename, array('hrm_job', 'hrm_job_user', 'hrm_skill'))) {
947 // These 3 tables have no entity column either, so no rule that reads the table can be run on them.
948 // The permission is still checked by restrictedArea(), and the $checkhierarchy rule below still runs.
949 // Note that these 3 objects are therefore not partitioned between entities at all, in the database
950 // itself: their cards already answer to a user of another entity, and their lists already show the
951 // records of all of them. This rule does not widen that, it aligns with it.
952 $nocheck[] = $feature;
953 } else {
954 $check[] = $feature; // Test on the entity only, there is no third party to restrict on
955 }
956 }
957
958 // $objectid was already sanitized at begin of this method (can be an int or a list of int separated by comma).
959 // To avoid an access forbidden with a numeric ref
960 if ($dbt_select != 'rowid' && $dbt_select != 'id') {
961 $objectid = "'".$objectid."'";
962 }
963
964 //var_dump($feature, $dbtablename, $parenttableforentity);
965
966 // Check permission for objectid on entity only
967 if (in_array($feature, $check) && !empty($objectid)) { // For $objectid = 0, no check
968 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
969 $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($dbtablename)." as dbt";
970 if (($feature == 'user' || $feature == 'usergroup') && isModEnabled('multicompany')) { // Special for multicompany
971 if (getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE')) {
972 if ($conf->entity == 1 && $user->admin && !$user->entity) {
973 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
974 $sql .= " AND dbt.entity IS NOT NULL";
975 } else {
976 $sql .= ",".MAIN_DB_PREFIX."usergroup_user as ug";
977 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
978 $sql .= " AND ((ug.fk_user = dbt.rowid";
979 $sql .= " AND ug.entity IN (".getEntity('usergroup')."))";
980 $sql .= " OR dbt.entity = 0)"; // Show always superadmin
981 }
982 } else {
983 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
984 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
985 }
986 } else {
987 $reg = array();
988 if ($parenttableforentity && preg_match('/(.*)@(.*)/', $parenttableforentity, $reg)) {
989 $sql .= ", ".MAIN_DB_PREFIX.$db->sanitize($reg[2])." as dbtp";
990 $sql .= " WHERE dbt.".$db->sanitize($reg[1])." = dbtp.rowid AND dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
991 $sql .= " AND dbtp.entity IN (".getEntity($sharedelement, 1).")";
992 } else {
993 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
994 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
995 }
996 }
997 $checkonentityready = 1;
998 //var_dump($checkonentityready, $sql);
999 }
1000
1001 if (in_array($feature, $checksoc) && !empty($objectid)) { // We check feature = checksoc. For $objectid = 0, no check
1002 // If external user: Check permission for external users
1003 if ($user->socid > 0) {
1004 if ((string) $user->socid != $objectid) {
1005 return false;
1006 }
1007 } elseif (isModEnabled('societe') && !$user->hasRight('societe', 'lire') && !$user->hasRight('societe', 'client', 'voir')) {
1008 dol_syslog("security.lib.php::checkUserAccessToObject Deny access due: (isModEnabled('societe') && !user->hasRight('societe', 'lire') && !user->hasRight('societe', 'client', 'voir'))", LOG_DEBUG);
1009 return false;
1010 } elseif (isModEnabled("societe") && ($user->hasRight('societe', 'lire') && !$user->hasRight('societe', 'client', 'voir'))) {
1011 // If internal user: Check permission for internal users that are restricted on their objects
1012 $sql = "SELECT COUNT(sc.fk_soc) as nb";
1013 $sql .= " FROM (".MAIN_DB_PREFIX."societe_commerciaux as sc";
1014 $sql .= ", ".MAIN_DB_PREFIX."societe as s)";
1015 $sql .= " WHERE sc.fk_soc IN (".$db->sanitize($objectid, 1).")";
1016 $sql .= " AND (sc.fk_user = ".((int) $user->id);
1017 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
1018 $userschilds = $user->getAllChildIds();
1019 if (!empty($userschilds)) {
1020 $sql .= " OR sc.fk_user IN (".$db->sanitize(implode(',', $userschilds)).")";
1021 }
1022 }
1023 $sql .= ")";
1024 $sql .= " AND sc.fk_soc = s.rowid";
1025 $sql .= " AND s.entity IN (".getEntity($sharedelement, 1).")";
1026 } elseif (isModEnabled('multicompany')) {
1027 // If multicompany and internal users with all permissions, check user is in correct entity
1028 $sql = "SELECT COUNT(s.rowid) as nb";
1029 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
1030 $sql .= " WHERE s.rowid IN (".$db->sanitize($objectid, 1).")";
1031 $sql .= " AND s.entity IN (".getEntity($sharedelement, 1).")";
1032 }
1033
1034 $checkonentityready = 1;
1035 }
1036
1037 if (in_array($feature, $checkparentsoc) && !empty($objectid)) { // Test on entity + link to thirdparty. Allowed if link is empty (Ex: contacts...).
1038 if ($user->socid > 0) {
1039 // If external user: Check permission for external users (limtited to their company, even object with company link that is null must remain not visible)
1040 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1041 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1042 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")"; // Link to third party
1043 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1044 $sql .= " AND dbt.fk_soc = ".((int) $user->socid); // Third party must be user company
1045 } elseif (isModEnabled("societe") && ($user->hasRight('societe', 'lire') && !$user->hasRight('societe', 'client', 'voir'))) {
1046 // If internal user: Check permission for internal users that are restricted on their objects
1047 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1048 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1049 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON dbt.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
1050 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1051 $sql .= " AND (dbt.fk_soc IS NULL OR sc.fk_soc IS NOT NULL)"; // Contact not linked to a company or to a company of user
1052 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1053 } elseif (isModEnabled('multicompany')) {
1054 // If multicompany and internal users with all permissions, check user is in correct entity
1055 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1056 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1057 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1058 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1059 }
1060
1061 $checkonentityready = 1;
1062 }
1063
1064 if (in_array($feature, $checkproject) && !empty($objectid)) {
1065 if (isModEnabled('project') && !$user->hasRight('projet', 'all', 'lire')) {
1066 $projectid = $objectid; // Note that if $objectid is a string list of id; the test later will return false
1067
1068 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
1069 $projectstatic = new Project($db);
1070 $tmps = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, 0);
1071
1072 $tmparray = explode(',', $tmps);
1073 if (!in_array($projectid, $tmparray)) {
1074 return false;
1075 }
1076 } else {
1077 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1078 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1079 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1080 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1081 }
1082 $checkonentityready = 1;
1083 }
1084
1085 if (in_array($feature, $checktask) && !empty($objectid)) {
1086 if (isModEnabled('project') && !$user->hasRight('projet', 'all', 'lire')) {
1087 if (preg_match('/,/', $objectid)) { // if this is a list of id
1088 return false;
1089 }
1090 $task = new Task($db);
1091 $task->fetch((int) $objectid);
1092 $projectid = $task->fk_project;
1093
1094 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
1095 $projectstatic = new Project($db);
1096 $tmps = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, 0);
1097
1098 $tmparray = explode(',', $tmps);
1099 if (!in_array($projectid, $tmparray)) {
1100 return false;
1101 }
1102 } else {
1103 $sharedelement = 'project'; // for multicompany compatibility
1104 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1105 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1106 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1107 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1108 }
1109
1110 $checkonentityready = 1;
1111 }
1112 //var_dump($checkonentityready, $sql);
1113
1114 if (!$checkonentityready && !in_array($feature, $nocheck) && !empty($objectid)) { // By default (case of $checkdefault), we check on object entity + link to third party on field $dbt_keyfield
1115 // If external user: Check permission for external users
1116 if ($user->socid > 0) {
1117 if (empty($dbt_keyfield)) {
1118 dol_print_error(null, 'Param dbt_keyfield is required but not defined');
1119 }
1120 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_keyfield).") as nb";
1121 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1122 $sql .= " WHERE dbt.rowid IN (".$db->sanitize($objectid, 1).")";
1123 $sql .= " AND dbt.".$db->sanitize($dbt_keyfield)." = ".((int) $user->socid);
1124 } elseif (isModEnabled("societe") && !$user->hasRight('societe', 'client', 'voir')) {
1125 // If internal user without permission to see all thirdparties: Check permission for internal users that are restricted on their objects
1126 if (empty($dbt_keyfield)) {
1127 dol_print_error(null, 'Param dbt_keyfield is required but not defined');
1128 }
1129 if ($feature != 'ticket') {
1130 $sql = "SELECT COUNT(sc.fk_soc) as nb";
1131 $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($dbtablename)." as dbt";
1132 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
1133 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1134 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1135 $sql .= " AND sc.fk_soc = dbt.".$db->sanitize($dbt_keyfield);
1136 $sql .= " AND (sc.fk_user = ".((int) $user->id);
1137 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
1138 $userschilds = $user->getAllChildIds();
1139 if (!empty($userschilds)) {
1140 $sql .= " OR sc.fk_user IN (".$db->sanitize(implode(',', $userschilds)).")";
1141 }
1142 }
1143 $sql .= ')';
1144 } else {
1145 // On ticket, the thirdparty is not mandatory, so we need a special test to accept record with no thirdparties.
1146 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1147 $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
1148 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = dbt.".$db->sanitize($dbt_keyfield)." AND sc.fk_user = ".((int) $user->id);
1149 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1150 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1151 $sql .= " AND (sc.fk_user = ".((int) $user->id)." OR dbt.".$dbt_keyfield." IS NULL OR dbt.".$dbt_keyfield." = 0)";
1152 }
1153 } elseif (isModEnabled('multicompany') && (!empty($object->ismultientitymanaged) || !isset($object->ismultientitymanaged))) {
1154 // If multicompany, and user is an internal user with all permissions, check that object is in correct entity
1155 $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb";
1156 $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($dbtablename)." as dbt";
1157 $sql .= " WHERE dbt.".$db->sanitize($dbt_select)." IN (".$db->sanitize($objectid, 1).")";
1158 $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
1159 }
1160 }
1161
1162 // For events, check on users assigned to event
1163 if ($feature === 'agenda' && !empty($objectid)) {
1164 // Also check owner or attendee for users without allactions->read
1165 if (!$user->hasRight('agenda', 'allactions', 'read')) {
1166 if (preg_match('/,/', $objectid)) { // if this is a list of id
1167 return false;
1168 }
1169
1170 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1171 $action = new ActionComm($db);
1172 $action->fetch((int) $objectid);
1173 if ($action->authorid != $user->id && $action->userownerid != $user->id && !(array_key_exists($user->id, $action->userassigned))) {
1174 return false;
1175 }
1176 }
1177 }
1178
1179 // For some object, we also have to check it is in the user hierarchy
1180 // Param $object must be the full object and not a simple id to have this test possible.
1181 if (in_array($feature, $checkhierarchy) && is_object($object) && !empty($objectid)) {
1182 $childids = $user->getAllChildIds(1);
1183 $useridtocheck = 0;
1184 if ($feature == 'holiday') {
1185 $useridtocheck = $object->fk_user;
1186 if (!$user->hasRight('holiday', 'readall') && !in_array($useridtocheck, $childids) && !in_array($object->fk_validator, $childids)) {
1187 return false;
1188 }
1189 }
1190 if ($feature == 'expensereport') {
1191 $useridtocheck = $object->fk_user_author;
1192 if (!$user->hasRight('expensereport', 'readall')) {
1193 if (!in_array($useridtocheck, $childids)) {
1194 return false;
1195 }
1196 }
1197 }
1198 if ($feature == 'hrm' && in_array('evaluation', $feature2)) {
1199 $useridtocheck = $object->fk_user;
1200
1201 if ($user->hasRight('hrm', 'evaluation', 'readall')) {
1202 // the user can view evaluations for anyone
1203 return true;
1204 }
1205 if (!$user->hasRight('hrm', 'evaluation', 'read')) {
1206 // the user can't view any evaluations
1207 return false;
1208 }
1209 // the user can only see their own evaluations or their subordinates'
1210 return in_array($useridtocheck, $childids);
1211 }
1212 }
1213
1214 // For some object, we also have to check it is public or owned by user
1215 // Param $object must be the full object and not a simple id to have this test possible.
1216 if (in_array($feature, $checkuser) && is_object($object) && !empty($objectid)) {
1217 $useridtocheck = $object->fk_user;
1218 if (!empty($useridtocheck) && $useridtocheck > 0 && $useridtocheck != $user->id && empty($user->admin)) {
1219 return false;
1220 }
1221 }
1222
1223 if ($sql) {
1224 $resql = $db->query($sql);
1225 if ($resql) {
1226 $obj = $db->fetch_object($resql);
1227 if (!$obj || $obj->nb < count(explode(',', $objectid))) { // error if we found 0 or less record than the nb of ids provided
1228 return false;
1229 }
1230 } else {
1231 dol_syslog("Bad forged sql in security.lib.php::checkUserAccessToObject", LOG_WARNING);
1232 return false;
1233 }
1234 }
1235 }
1236
1237 dol_syslog("security.lib.php::checkUserAccessToObject::return True", LOG_DEBUG);
1238 return true;
1239}
1240
1254function getObjectIdsRefusedToUser(User $user, $object, array $ids)
1255{
1256 $feature = '';
1257 switch ($object->element) {
1258 case 'societe':
1259 case 'contact':
1260 case 'contrat':
1261 case 'ticket':
1262 case 'facture':
1263 case 'commande':
1264 case 'propal':
1265 case 'supplier_proposal':
1266 case 'fichinter':
1267 case 'shipping':
1268 case 'reception':
1269 case 'order_supplier':
1270 case 'invoice_supplier':
1271 $feature = $object->element;
1272 break;
1273 case 'action':
1274 $feature = 'agenda';
1275 break;
1276 case 'project':
1277 $feature = 'projet';
1278 break;
1279 case 'project_task':
1280 include_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
1281 $feature = 'project_task';
1282 break;
1283 }
1284 if (empty($feature)) {
1285 return [];
1286 }
1287
1288 $refusedids = [];
1289 foreach ($ids as $id) {
1290 $id = (int) $id;
1291 if ($id <= 0) {
1292 continue;
1293 }
1294 if (!checkUserAccessToObject($user, [$feature], $id, $object->table_element.'&'.$object->element, '', 'fk_soc', 'rowid')) {
1295 $refusedids[] = $id;
1296 }
1297 }
1298
1299 return $refusedids;
1300}
1301
1302
1314function httponly_accessforbidden($message = '1', $http_response_code = 403, $stringalreadysanitized = 0)
1315{
1316 top_httphead();
1317 http_response_code($http_response_code);
1318
1319 if ($stringalreadysanitized) {
1320 print $message;
1321 } else {
1322 print htmlentities($message);
1323 }
1324
1325 exit(1);
1326}
1327
1341function accessforbidden($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
1342{
1343 global $conf, $db, $user, $langs, $hookmanager;
1344 global $action, $object;
1345
1346 if (!is_object($langs)) {
1347 include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
1348 $langs = new Translate('', $conf);
1349 $langs->setDefaultLang();
1350 }
1351
1352 $langs->loadLangs(array("main", "errors"));
1353
1354 if ($printheader && !defined('NOHEADERNOFOOTER')) {
1355 if (function_exists("llxHeader")) {
1356 llxHeader('');
1357 } elseif (function_exists("llxHeaderVierge")) {
1358 llxHeaderVierge('');
1359 }
1360 print '<div style="padding: 20px">';
1361 }
1362 print '<div class="error">';
1363 if (empty($message)) {
1364 print $langs->trans("ErrorForbidden");
1365 } else {
1366 print $langs->trans($message);
1367 }
1368 print '</div>';
1369 print '<br>';
1370 if (empty($showonlymessage)) {
1371 if (empty($hookmanager)) {
1372 include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
1373 $hookmanager = new HookManager($db);
1374 // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
1375 $hookmanager->initHooks(array('main'));
1376 }
1377
1378 $parameters = array('message' => $message, 'params' => $params);
1379 $reshook = $hookmanager->executeHooks('getAccessForbiddenMessage', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1380 print $hookmanager->resPrint;
1381 if (empty($reshook)) {
1382 $langs->loadLangs(array("errors"));
1383 if ($user->login) {
1384 print $langs->trans("CurrentLogin").': <span class="error">'.$user->login.'</span><br>';
1385 print $langs->trans("ErrorForbidden2", $langs->transnoentitiesnoconv("Home"), $langs->transnoentitiesnoconv("Users"));
1386 print $langs->trans("ErrorForbidden4");
1387 } else {
1388 print $langs->trans("ErrorForbidden3");
1389 }
1390 }
1391 }
1392 if ($printfooter && !defined('NOHEADERNOFOOTER') && function_exists("llxFooter")) {
1393 print '</div>';
1394 llxFooter();
1395 }
1396
1397 // End PHP
1398 exit(0);
1399}
1400
1401
1409{
1410 $max = getDolGlobalString('MAIN_UPLOAD_DOC'); // In Kb
1411
1412 $maxphp = @ini_get('upload_max_filesize'); // In unknown
1413 if (preg_match('/k$/i', $maxphp)) {
1414 $maxphp = preg_replace('/k$/i', '', $maxphp);
1415 $maxphp = (int) ((float) $maxphp * 1);
1416 }
1417 if (preg_match('/m$/i', $maxphp)) {
1418 $maxphp = preg_replace('/m$/i', '', $maxphp);
1419 $maxphp = (int) ((float) $maxphp * 1024);
1420 }
1421 if (preg_match('/g$/i', $maxphp)) {
1422 $maxphp = preg_replace('/g$/i', '', $maxphp);
1423 $maxphp = (int) ((float) $maxphp * 1024 * 1024);
1424 }
1425 if (preg_match('/t$/i', $maxphp)) {
1426 $maxphp = preg_replace('/t$/i', '', $maxphp);
1427 $maxphp = (int) ((float) $maxphp * 1024 * 1024 * 1024);
1428 }
1429 $maxphp2 = @ini_get('post_max_size'); // In unknown
1430 if (preg_match('/k$/i', $maxphp2)) {
1431 $maxphp2 = preg_replace('/k$/i', '', $maxphp2);
1432 $maxphp2 = (int) ((float) $maxphp2) * 1;
1433 }
1434 if (preg_match('/m$/i', $maxphp2)) {
1435 $maxphp2 = preg_replace('/m$/i', '', $maxphp2);
1436 $maxphp2 = (int) ((float) $maxphp2 * 1024);
1437 }
1438 if (preg_match('/g$/i', $maxphp2)) {
1439 $maxphp2 = preg_replace('/g$/i', '', $maxphp2);
1440 $maxphp2 = (int) ((float) $maxphp2 * 1024 * 1024);
1441 }
1442 if (preg_match('/t$/i', $maxphp2)) {
1443 $maxphp2 = preg_replace('/t$/i', '', $maxphp2);
1444 $maxphp2 = (int) ((float) $maxphp2 * 1024 * 1024 * 1024);
1445 }
1446 // Now $max and $maxphp and $maxphp2 are in Kb
1447 $maxmin = $max;
1448 $maxphptoshow = $maxphptoshowparam = '';
1449 if ($maxphp > 0) {
1450 $maxmin = min($maxmin, $maxphp);
1451 $maxphptoshow = $maxphp;
1452 $maxphptoshowparam = 'upload_max_filesize';
1453 }
1454 if ($maxphp2 > 0) {
1455 $maxmin = min($maxmin, $maxphp2);
1456 if ($maxphp2 < $maxphp) {
1457 $maxphptoshow = $maxphp2;
1458 $maxphptoshowparam = 'post_max_size';
1459 }
1460 }
1461 //var_dump($maxphp.'-'.$maxphp2);
1462 //var_dump($maxmin);
1463
1464 return array('max' => $max, 'maxmin' => $maxmin, 'maxphptoshow' => $maxphptoshow, 'maxphptoshowparam' => $maxphptoshowparam);
1465}
1466
1475function checkIPInCidr($ip, $cidr)
1476{
1477 list($network, $prefix) = explode('/', $cidr, 2);
1478
1479 // Convert IPs to binary format
1480 $ip_bin = @inet_pton($ip);
1481 $net_bin = @inet_pton($network);
1482 if ($ip_bin === false || $net_bin === false) {
1483 return -1;
1484 }
1485
1486 // Require same address IPvX family
1487 if (strlen($ip_bin) !== strlen($net_bin)) { // @phan-suppress-current-line DolibarrForbiddenFunctionPlugin
1488 return -1;
1489 }
1490
1491 // Comparison boundaries
1492 $total_bits = strlen($ip_bin) * 8;
1493 $prefix = max(0, min((int) $prefix, $total_bits));
1494 $full_bytes = intdiv($prefix, 8);
1495 $rem_bits = $prefix % 8;
1496
1497 // Compare full bytes and partial bytes
1498 if ($full_bytes > 0) {
1499 if (substr($ip_bin, 0, $full_bytes) !== substr($net_bin, 0, $full_bytes)) { // @phan-suppress-current-line DolibarrForbiddenFunctionPlugin
1500 return 0;
1501 }
1502 }
1503 if ($rem_bits > 0) {
1504 $mask = (0xFF << (8 - $rem_bits)) & 0xFF;
1505 $ip_byte = ord($ip_bin[$full_bytes]);
1506 $net_byte = ord($net_bin[$full_bytes]);
1507 if (($ip_byte & $mask) !== ($net_byte & $mask)) {
1508 return 0;
1509 }
1510 }
1511 return 1;
1512}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
if(!defined( 'NOTOKENRENEWAL')) if(!defined('NOREQUIREMENU')) if(!defined( 'NOREQUIREHTML')) if(!defined('NOREQUIREAJAX')) if(!defined( 'NOLOGIN')) if(!defined('NOCSRFCHECK')) if(!defined( 'NOIPCHECK')) llxHeaderVierge($title, $head="", $disablejs=0, $disablehead=0, $arrayofjs=[], $arrayofcss=[], $ws='')
Header function.
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to manage agenda events (actions)
Class to manage hooks.
Class to manage projects.
Class to manage tasks.
Class to manage translations.
Class to manage Dolibarr users.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
dolGetRandomBytes($length)
Return a string of random bytes (hexa string) with length = $length for cryptographic purposes.
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.
dol_encode($chain, $key='1')
Encode a string with base 64 algorithm + specific delta change.
checkUserAccessToObject($user, array $featuresarray, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='', $dbt_select='rowid', $parenttableforentity='')
Check that access by a given user to an object is ok.
checkIPInCidr($ip, $cidr)
Check if IP address is in CIDR range.
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $nodie=0, $mode='')
Check permissions of a user to show a page and an object.
getMaxFileSizeArray()
Return the max allowed for file upload.
dol_decode($chain, $key='1')
Decode a base 64 encoded + specific delta change.
dolGetLdapPasswordHash($password, $type='md5')
Returns a specific ldap hash of a password.
getObjectIdsRefusedToUser(User $user, $object, array $ids)
Return, among a list of ids of objects of the same type, the ids of the objects the user is not allow...
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.