* Copyright (C) 2008-2017 Regis Houssin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * or see http://www.gnu.org/ */ /** * \file htdocs/core/lib/security.lib.php * \ingroup core * \brief Set of function used for dolibarr security (common function included into filefunc.inc.php) * Warning, this file must not depends on other library files, except function.lib.php * because it is used at low code level. */ /** * Encode a string with base 64 algorithm + specific change * Code of this function is useless and we should use base64_encode only instead * * @param string $chain string to encode * @return string encoded string */ function dol_encode($chain) { $strlength=dol_strlen($chain); for ($i=0; $i < $strlength; $i++) { $output_tab[$i] = chr(ord(substr($chain,$i,1))+17); } $string_coded = base64_encode(implode("",$output_tab)); return $string_coded; } /** * Decode a base 64 encoded + specific string. * This function is called by filefunc.inc.php at each page call. * Code of this function is useless and we should use base64_decode only instead * * @param string $chain string to decode * @return string decoded string */ function dol_decode($chain) { $chain = base64_decode($chain); $strlength=dol_strlen($chain); for($i=0; $i < $strlength;$i++) { $output_tab[$i] = chr(ord(substr($chain,$i,1))-17); } $string_decoded = implode("",$output_tab); return $string_decoded; } /** * Returns a hash of a string. * If constant MAIN_SECURITY_HASH_ALGO is defined, we use this function as hashing function. * If constant MAIN_SECURITY_SALT is defined, we use it as a salt. * * @param string $chain String to hash * @param int $type Type of hash (0:auto, 1:sha1, 2:sha1+md5, 3:md5, 4:md5 for OpenLdap). Use 3 here, if hash is not needed for security purpose, for security need, prefer 0. * @return string Hash of string */ function dol_hash($chain,$type=0) { global $conf; // Salt value if (! empty($conf->global->MAIN_SECURITY_SALT)) $chain=$conf->global->MAIN_SECURITY_SALT.$chain; if ($type == 1) return sha1($chain); else if ($type == 2) return sha1(md5($chain)); else if ($type == 3) return md5($chain); else if ($type == 4) return '{md5}'.base64_encode(mhash(MHASH_MD5,$chain)); // For OpenLdap with md5 else if (! empty($conf->global->MAIN_SECURITY_HASH_ALGO) && $conf->global->MAIN_SECURITY_HASH_ALGO == 'sha1') return sha1($chain); else if (! empty($conf->global->MAIN_SECURITY_HASH_ALGO) && $conf->global->MAIN_SECURITY_HASH_ALGO == 'sha1md5') return sha1(md5($chain)); // No particular enconding defined, use default return md5($chain); } /** * Check permissions of a user to show a page and an object. Check read permission. * If GETPOST('action','aZ09') defined, we also check write and delete permission. * * @param User $user User to check * @param string $features Features to check (it must be module name. Examples: 'societe', 'contact', 'produit&service', 'produit|service', ...) * @param int $objectid Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional). * @param string $tableandshare 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity for multicompany modume. Param not used if objectid is null (optional). * @param string $feature2 Feature to check, second level of permission (optional). Can be or check with 'level1|level2'. * @param string $dbt_keyfield Field name for socid foreign key if not fk_soc. Not used if objectid is null (optional) * @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional) * @param Canvas $objcanvas Object canvas * @return int Always 1, die process if not allowed * @see dol_check_secure_access_document */ function restrictedArea($user, $features, $objectid=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $objcanvas=null) { global $db, $conf; //dol_syslog("functions.lib:restrictedArea $feature, $objectid, $dbtablename,$feature2,$dbt_socfield,$dbt_select"); //print "user_id=".$user->id.", features=".$features.", feature2=".$feature2.", objectid=".$objectid; //print ", dbtablename=".$dbtablename.", dbt_socfield=".$dbt_keyfield.", dbt_select=".$dbt_select; //print ", perm: ".$features."->".$feature2."=".($user->rights->$features->$feature2->lire)."
"; // If we use canvas, we try to use function that overlod restrictarea if provided with canvas if (is_object($objcanvas)) { if (method_exists($objcanvas->control,'restrictedArea')) return $objcanvas->control->restrictedArea($user,$features,$objectid,$dbtablename,$feature2,$dbt_keyfield,$dbt_select); } if ($dbt_select != 'rowid' && $dbt_select != 'id') $objectid = "'".$objectid."'"; // Features/modules to check $featuresarray = array($features); if (preg_match('/&/', $features)) $featuresarray = explode("&", $features); else if (preg_match('/\|/', $features)) $featuresarray = explode("|", $features); // More subfeatures to check if (! empty($feature2)) $feature2 = explode("|", $feature2); // More parameters $params = explode('&', $tableandshare); $dbtablename=(! empty($params[0]) ? $params[0] : ''); $sharedelement=(! empty($params[1]) ? $params[1] : $dbtablename); $listofmodules=explode(',',$conf->global->MAIN_MODULES_FOR_EXTERNAL); // Check read permission from module $readok=1; $nbko=0; foreach ($featuresarray as $feature) // first we check nb of test ko { $featureforlistofmodule=$feature; if ($featureforlistofmodule == 'produit') $featureforlistofmodule='product'; if (! empty($user->societe_id) && ! empty($conf->global->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 { $readok=0; $nbko++; continue; } if ($feature == 'societe') { if (! $user->rights->societe->lire && ! $user->rights->fournisseur->lire) { $readok=0; $nbko++; } } else if ($feature == 'contact') { if (! $user->rights->societe->contact->lire) { $readok=0; $nbko++; } } else if ($feature == 'produit|service') { if (! $user->rights->produit->lire && ! $user->rights->service->lire) { $readok=0; $nbko++; } } else if ($feature == 'prelevement') { if (! $user->rights->prelevement->bons->lire) { $readok=0; $nbko++; } } else if ($feature == 'cheque') { if (! $user->rights->banque->cheque) { $readok=0; $nbko++; } } else if ($feature == 'projet') { if (! $user->rights->projet->lire && ! $user->rights->projet->all->lire) { $readok=0; $nbko++; } } else if (! empty($feature2)) // This should be used for future changes { $tmpreadok=1; foreach($feature2 as $subfeature) { if (! empty($subfeature) && empty($user->rights->$feature->$subfeature->lire) && empty($user->rights->$feature->$subfeature->read)) { $tmpreadok=0; } else if (empty($subfeature) && empty($user->rights->$feature->lire) && empty($user->rights->$feature->read)) { $tmpreadok=0; } else { $tmpreadok=1; break; } // Break is to bypass second test if the first is ok } if (! $tmpreadok) // We found a test on feature that is ko { $readok=0; // All tests are ko (we manage here the and, the or will be managed later using $nbko). $nbko++; } } else if (! empty($feature) && ($feature!='user' && $feature!='usergroup')) // This is for old permissions { if (empty($user->rights->$feature->lire) && empty($user->rights->$feature->read) && empty($user->rights->$feature->run)) { $readok=0; $nbko++; } } } // If a or and at least one ok if (preg_match('/\|/', $features) && $nbko < count($featuresarray)) $readok=1; if (! $readok) accessforbidden(); //print "Read access is ok"; // Check write permission from module $createok=1; $nbko=0; if (GETPOST('action','aZ09') == 'create') { foreach ($featuresarray as $feature) { if ($feature == 'contact') { if (! $user->rights->societe->contact->creer) { $createok=0; $nbko++; } } else if ($feature == 'produit|service') { if (! $user->rights->produit->creer && ! $user->rights->service->creer) { $createok=0; $nbko++; } } else if ($feature == 'prelevement') { if (! $user->rights->prelevement->bons->creer) { $createok=0; $nbko++; } } else if ($feature == 'commande_fournisseur') { if (! $user->rights->fournisseur->commande->creer) { $createok=0; $nbko++; } } else if ($feature == 'banque') { if (! $user->rights->banque->modifier) { $createok=0; $nbko++; } } else if ($feature == 'cheque') { if (! $user->rights->banque->cheque) { $createok=0; $nbko++; } } else if (! empty($feature2)) // This should be used { foreach($feature2 as $subfeature) { if (empty($user->rights->$feature->$subfeature->creer) && empty($user->rights->$feature->$subfeature->write) && empty($user->rights->$feature->$subfeature->create)) { $createok=0; $nbko++; } else { $createok=1; break; } // Break to bypass second test if the first is ok } } else if (! empty($feature)) // This is for old permissions ('creer' or 'write') { //print '
feature='.$feature.' creer='.$user->rights->$feature->creer.' write='.$user->rights->$feature->write; if (empty($user->rights->$feature->creer) && empty($user->rights->$feature->write)) { $createok=0; $nbko++; } } } // If a or and at least one ok if (preg_match('/\|/', $features) && $nbko < count($featuresarray)) $createok=1; if (! $createok) accessforbidden(); //print "Write access is ok"; } // Check create user permission $createuserok=1; if (GETPOST('action','aZ09') == 'confirm_create_user' && GETPOST("confirm") == 'yes') { if (! $user->rights->user->user->creer) $createuserok=0; if (! $createuserok) accessforbidden(); //print "Create user access is ok"; } // Check delete permission from module $deleteok=1; $nbko=0; if ((GETPOST('action','aZ09') == 'confirm_delete' && GETPOST("confirm") == 'yes') || GETPOST('action','aZ09') == 'delete') { foreach ($featuresarray as $feature) { if ($feature == 'contact') { if (! $user->rights->societe->contact->supprimer) $deleteok=0; } else if ($feature == 'produit|service') { if (! $user->rights->produit->supprimer && ! $user->rights->service->supprimer) $deleteok=0; } else if ($feature == 'commande_fournisseur') { if (! $user->rights->fournisseur->commande->supprimer) $deleteok=0; } else if ($feature == 'banque') { if (! $user->rights->banque->modifier) $deleteok=0; } else if ($feature == 'cheque') { if (! $user->rights->banque->cheque) $deleteok=0; } else if ($feature == 'ecm') { if (! $user->rights->ecm->upload) $deleteok=0; } else if ($feature == 'ftp') { if (! $user->rights->ftp->write) $deleteok=0; }else if ($feature == 'salaries') { if (! $user->rights->salaries->delete) $deleteok=0; } else if (! empty($feature2)) // This should be used for future changes { foreach($feature2 as $subfeature) { if (empty($user->rights->$feature->$subfeature->supprimer) && empty($user->rights->$feature->$subfeature->delete)) $deleteok=0; else { $deleteok=1; break; } // For bypass the second test if the first is ok } } else if (! empty($feature)) // This is for old permissions { //print '
feature='.$feature.' creer='.$user->rights->$feature->supprimer.' write='.$user->rights->$feature->delete; if (empty($user->rights->$feature->supprimer) && empty($user->rights->$feature->delete) && empty($user->rights->$feature->run)) $deleteok=0; } } // If a or and at least one ok if (preg_match('/\|/', $features) && $nbko < count($featuresarray)) $deleteok=1; if (! $deleteok) accessforbidden(); //print "Delete access is ok"; } // If we have a particular object to check permissions on, we check this object // is linked to a company allowed to $user. if (! empty($objectid) && $objectid > 0) { $ok = checkUserAccessToObject($user, $featuresarray, $objectid, $tableandshare, $feature2, $dbt_keyfield, $dbt_select); return $ok ? 1 : accessforbidden(); } return 1; } /** * Check access by user to object. * This function is also called by restrictedArea * * @param User $user User to check * @param array $featuresarray Features/modules to check. Example: ('user','service','member','project','task',...) * @param int $objectid Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional). * @param string $tableandshare 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity for multicompany modume. Param not used if objectid is null (optional). * @param string $feature2 Feature to check, second level of permission (optional). Can be or check with 'level1|level2'. * @param string $dbt_keyfield Field name for socid foreign key if not fk_soc. Not used if objectid is null (optional) * @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional) * @return bool True if user has access, False otherwise * @see restrictedArea */ function checkUserAccessToObject($user, $featuresarray, $objectid=0, $tableandshare='', $feature2='', $dbt_keyfield='', $dbt_select='rowid') { global $db, $conf; // More parameters $params = explode('&', $tableandshare); $dbtablename=(! empty($params[0]) ? $params[0] : ''); $sharedelement=(! empty($params[1]) ? $params[1] : $dbtablename); foreach ($featuresarray as $feature) { $sql=''; // For backward compatibility if ($feature == 'member') $feature='adherent'; if ($feature == 'project') $feature='projet'; if ($feature == 'task') $feature='projet_task'; $check = array('adherent','banque','user','usergroup','produit','service','produit|service','categorie'); // Test on entity only (Objects with no link to company) $checksoc = array('societe'); // Test for societe object $checkother = array('contact','agenda'); // Test on entity and link to third party. Allowed if link is empty (Ex: contacts...). $checkproject = array('projet','project'); // Test for project object $checktask = array('projet_task'); $nocheck = array('barcode','stock','fournisseur'); // No test $checkdefault = 'all other not already defined'; // Test on entity and link to third party. Not allowed if link is empty (Ex: invoice, orders...). // If dbtablename not defined, we use same name for table than module name if (empty($dbtablename)) { $dbtablename = $feature; $sharedelement = (! empty($params[1]) ? $params[1] : $dbtablename); // We change dbtablename, so we set sharedelement too. } // Check permission for object with entity if (in_array($feature,$check)) { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; if (($feature == 'user' || $feature == 'usergroup') && ! empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && ! $user->entity) { $sql.= " AND dbt.entity IS NOT NULL"; } else { $sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } } else if (in_array($feature,$checksoc)) // We check feature = checksoc { // If external user: Check permission for external users if ($user->socid > 0) { if ($user->socid <> $objectid) return false; } // If internal user: Check permission for internal users that are restricted on their objects else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir)) { $sql = "SELECT sc.fk_soc"; $sql.= " FROM (".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= ", ".MAIN_DB_PREFIX."societe as s)"; $sql.= " WHERE sc.fk_soc = ".$objectid; $sql.= " AND sc.fk_user = ".$user->id; $sql.= " AND sc.fk_soc = s.rowid"; $sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")"; } // If multicompany and internal users with all permissions, check user is in correct entity else if (! empty($conf->multicompany->enabled)) { $sql = "SELECT s.rowid"; $sql.= " FROM ".MAIN_DB_PREFIX."societe as s"; $sql.= " WHERE s.rowid = ".$objectid; $sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")"; } } else if (in_array($feature,$checkother)) // Test on entity and link to societe. Allowed if link is empty (Ex: contacts...). { // If external user: Check permission for external users if ($user->societe_id > 0) { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $sql.= " AND dbt.fk_soc = ".$user->societe_id; } // If internal user: Check permission for internal users that are restricted on their objects else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir)) { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON dbt.fk_soc = sc.fk_soc AND sc.fk_user = '".$user->id."'"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $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 $sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } // If multicompany and internal users with all permissions, check user is in correct entity else if (! empty($conf->multicompany->enabled)) { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } } else if (in_array($feature,$checkproject)) { if (! empty($conf->projet->enabled) && empty($user->rights->projet->all->lire)) { include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; $projectstatic=new Project($db); $tmps=$projectstatic->getProjectsAuthorizedForUser($user,0,1,0); $tmparray=explode(',',$tmps); if (! in_array($objectid,$tmparray)) return false; } else { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } } else if (in_array($feature,$checktask)) { if (! empty($conf->projet->enabled) && empty($user->rights->projet->all->lire)) { $task = new Task($db); $task->fetch($objectid); include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; $projectstatic=new Project($db); $tmps=$projectstatic->getProjectsAuthorizedForUser($user,0,1,0); $tmparray=explode(',',$tmps); if (! in_array($task->fk_project,$tmparray)) return false; } else { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } } else if (! in_array($feature,$nocheck)) // By default we check with link to third party { // If external user: Check permission for external users if ($user->societe_id > 0) { if (empty($dbt_keyfield)) dol_print_error('','Param dbt_keyfield is required but not defined'); $sql = "SELECT dbt.".$dbt_keyfield; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.rowid = ".$objectid; $sql.= " AND dbt.".$dbt_keyfield." = ".$user->societe_id; } // If internal user: Check permission for internal users that are restricted on their objects else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir)) { if (empty($dbt_keyfield)) dol_print_error('','Param dbt_keyfield is required but not defined'); $sql = "SELECT sc.fk_soc"; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= ", ".MAIN_DB_PREFIX."societe as s"; $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $sql.= " AND sc.fk_soc = dbt.".$dbt_keyfield; $sql.= " AND dbt.".$dbt_keyfield." = s.rowid"; $sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")"; $sql.= " AND sc.fk_user = ".$user->id; } // If multicompany and internal users with all permissions, check user is in correct entity else if (! empty($conf->multicompany->enabled)) { $sql = "SELECT dbt.".$dbt_select; $sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql.= " WHERE dbt.".$dbt_select." = ".$objectid; $sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } } //print "sql=".$sql."
"; if ($sql) { $resql=$db->query($sql); if ($resql) { if ($db->num_rows($resql) == 0) return false; } else { return false; } } } return true; } /** * Show a message to say access is forbidden and stop program * Calling this function terminate execution of PHP. * * @param string $message Force error message * @param int $printheader Show header before * @param int $printfooter Show footer after * @param int $showonlymessage Show only message parameter. Otherwise add more information. * @return void */ function accessforbidden($message='',$printheader=1,$printfooter=1,$showonlymessage=0) { global $conf, $db, $user, $langs; if (! is_object($langs)) { include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php'; $langs=new Translate('',$conf); } $langs->load("errors"); if ($printheader) { if (function_exists("llxHeader")) llxHeader(''); else if (function_exists("llxHeaderVierge")) llxHeaderVierge(''); } print '
'; if (! $message) print $langs->trans("ErrorForbidden"); else print $message; print '
'; print '
'; if (empty($showonlymessage)) { if ($user->login) { print $langs->trans("CurrentLogin").': '.$user->login.'
'; print $langs->trans("ErrorForbidden2",$langs->trans("Home"),$langs->trans("Users")); } else { print $langs->trans("ErrorForbidden3"); } } if ($printfooter && function_exists("llxFooter")) llxFooter(); exit(0); }