diff --git a/SECURITY.md b/SECURITY.md index 4e7e5fa933a..cadd4a23791 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -67,7 +67,7 @@ Scope is the web application (back office) and the APIs. * Remote code execution (RCE) * Local files access and manipulation (LFI, RFI, XXE, SSRF, XSPA) * Code injections (HTML, JS, SQL, PHP, ...) -* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose or into module "Web site" when permission to edit website content is allowed). +* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except into module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too). * Cross-Site Requests Forgery (CSRF) with real security impact (when using GET URLs, CSRF are qualified only for creating, updating or deleting data from pages restricted to admin users) * Open redirect * Broken authentication & session management diff --git a/htdocs/adherents/partnership.php b/htdocs/adherents/partnership.php index cf0e11d70e0..160a037c187 100644 --- a/htdocs/adherents/partnership.php +++ b/htdocs/adherents/partnership.php @@ -22,27 +22,6 @@ * \brief Page to create/edit/view partnership */ -//if (! defined('NOREQUIREDB')) define('NOREQUIREDB', '1'); // Do not create database handler $db -//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER', '1'); // Do not load object $user -//if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1'); // Do not load object $mysoc -//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN', '1'); // Do not load object $langs -//if (! defined('NOSCANGETFORINJECTION')) define('NOSCANGETFORINJECTION', '1'); // Do not check injection attack on GET parameters -//if (! defined('NOSCANPOSTFORINJECTION')) define('NOSCANPOSTFORINJECTION', '1'); // Do not check injection attack on POST parameters -//if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK', '1'); // Do not check CSRF attack (test on referer + on token if option MAIN_SECURITY_CSRF_WITH_TOKEN is on). -//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); // Do not roll the Anti CSRF token (used if MAIN_SECURITY_CSRF_WITH_TOKEN is on) -//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK', '1'); // Do not check style html tag into posted data -//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1'); // If there is no need to load and show top and left menu -//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1'); // If we don't need to load the html.form.class.php -//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1'); // Do not load ajax.lib.php library -//if (! defined("NOLOGIN")) define("NOLOGIN", '1'); // If this page is public (can be called outside logged session). This include the NOIPCHECK too. -//if (! defined('NOIPCHECK')) define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip -//if (! defined("MAIN_LANG_DEFAULT")) define('MAIN_LANG_DEFAULT', 'auto'); // Force lang to a particular value -//if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule'); // Force authentication handler -//if (! defined("NOREDIRECTBYMAINTOLOGIN")) define('NOREDIRECTBYMAINTOLOGIN', 1); // The main.inc.php does not make a redirect if not logged, instead show simple error message -//if (! defined("FORCECSP")) define('FORCECSP', 'none'); // Disable all Content Security Policies -//if (! defined('CSRFCHECK_WITH_TOKEN')) define('CSRFCHECK_WITH_TOKEN', '1'); // Force use of CSRF protection with tokens even for GET -//if (! defined('NOBROWSERNOTIF')) define('NOBROWSERNOTIF', '1'); // Disable browser notification - // Load Dolibarr environment require '../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; diff --git a/htdocs/admin/agenda.php b/htdocs/admin/agenda.php index 730983ad090..a730f199213 100644 --- a/htdocs/admin/agenda.php +++ b/htdocs/admin/agenda.php @@ -192,6 +192,7 @@ if (!empty($triggers)) { } if ($search_event === '' || preg_match('/'.preg_quote($search_event, '/').'/i', $trigger['code'])) { + print ''; print ''; print ''.$trigger['code'].''; print ''.$trigger['label'].''; diff --git a/htdocs/admin/const.php b/htdocs/admin/const.php index a632d5f1a41..1426c323c01 100644 --- a/htdocs/admin/const.php +++ b/htdocs/admin/const.php @@ -37,8 +37,6 @@ if (!$user->admin) { $rowid = GETPOST('rowid', 'int'); $entity = GETPOST('entity', 'int'); $action = GETPOST('action', 'aZ09'); -$update = GETPOST('update', 'alpha'); -$delete = GETPOST('delete', 'none'); // Do not use alpha here $debug = GETPOST('debug', 'int'); $consts = GETPOST('const', 'array'); $constname = GETPOST('constname', 'alphanohtml'); diff --git a/htdocs/admin/defaultvalues.php b/htdocs/admin/defaultvalues.php index 4d88675ffd4..58a72250d64 100644 --- a/htdocs/admin/defaultvalues.php +++ b/htdocs/admin/defaultvalues.php @@ -64,7 +64,7 @@ if (!$sortorder) { $defaulturl = GETPOST('defaulturl', 'alphanohtml'); $defaultkey = GETPOST('defaultkey', 'alphanohtml'); -$defaultvalue = GETPOST('defaultvalue', 'none'); +$defaultvalue = GETPOST('defaultvalue', 'restricthtml'); $defaulturl = preg_replace('/^\//', '', $defaulturl); diff --git a/htdocs/admin/oauth.php b/htdocs/admin/oauth.php index 9bf5be2c294..a0d82d1d6bd 100644 --- a/htdocs/admin/oauth.php +++ b/htdocs/admin/oauth.php @@ -27,6 +27,7 @@ require '../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; +// $supportedoauth2array is defined into oauth.lib.php // Define $urlwithroot $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); @@ -96,10 +97,12 @@ print ''; $i = 0; -// $list is defined into oauth.lib.php +// $list is defined into oauth.lib.php to the list of supporter OAuth providers. foreach ($list as $key) { $supported = 0; - if (in_array($key[0], array_keys($supportedoauth2array))) { + $keyforsupportedoauth2array = $key[0]; + + if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) { $supported = 1; } if (!$supported) { @@ -110,20 +113,23 @@ foreach ($list as $key) { print ''; // Api Name - $label = $langs->trans($key[0]); - print ''; + $label = $langs->trans($keyforsupportedoauth2array); print ''; + print ''; print ''; if ($supported) { - $redirect_uri = $urlwithroot.'/core/modules/oauth/'.$supportedoauth2array[$key[0]].'_oauthcallback.php'; + $redirect_uri = $urlwithroot.'/core/modules/oauth/'.$supportedoauth2array[$keyforsupportedoauth2array]['callbackfile'].'_oauthcallback.php'; print ''; print ''; - print ''; } else { print ''; diff --git a/htdocs/admin/oauthlogintokens.php b/htdocs/admin/oauthlogintokens.php index 8697b400a2b..73a9139f856 100644 --- a/htdocs/admin/oauthlogintokens.php +++ b/htdocs/admin/oauthlogintokens.php @@ -25,17 +25,13 @@ require '../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // This define $list +require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // This define $list and $supportedoauth2array require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; use OAuth\Common\Storage\DoliStorage; // Load translation files required by the page $langs->loadLangs(array('admin', 'printing', 'oauth')); -if (!$user->admin) { - accessforbidden(); -} - $action = GETPOST('action', 'aZ09'); $mode = GETPOST('mode', 'alpha'); $value = GETPOST('value', 'alpha'); @@ -50,6 +46,10 @@ if (!$mode) { $mode = 'setup'; } +if (!$user->admin) { + accessforbidden(); +} + /* * Action @@ -122,7 +122,7 @@ $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domai $form = new Form($db); -llxHeader('', $langs->trans("PrintingSetup")); +llxHeader('', $langs->trans("TokenManager")); $linkback = ''.$langs->trans("BackToModuleList").''; print load_fiche_titre($langs->trans('ConfigOAuth'), $linkback, 'title_setup'); @@ -140,7 +140,9 @@ if ($mode == 'setup' && $user->admin) { foreach ($list as $key) { $supported = 0; - if (in_array($key[0], array_keys($supportedoauth2array))) { + $keyforsupportedoauth2array = $key[0]; + + if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) { $supported = 1; } if (!$supported) { @@ -148,34 +150,44 @@ if ($mode == 'setup' && $user->admin) { } - $OAUTH_SERVICENAME = 'Unknown'; - if ($key[0] == 'OAUTH_GITHUB_NAME') { - $OAUTH_SERVICENAME = 'GitHub'; + $OAUTH_SERVICENAME = empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name']; + + // Define $shortscope, $urltorenew, $urltodelete, $urltocheckperms + // TODO Use array $supportedoauth2array + if ($keyforsupportedoauth2array == 'OAUTH_GITHUB_NAME') { // List of keys that will be converted into scopes (from constants 'SCOPE_state_in_uppercase' in file of service). // We pass this param list in to 'state' because we need it before and after the redirect. $shortscope = 'user,public_repo'; $urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = 'https://github.com/settings/applications/'; - } elseif ($key[0] == 'OAUTH_GOOGLE_NAME') { - $OAUTH_SERVICENAME = 'Google'; + } elseif ($keyforsupportedoauth2array == 'OAUTH_GOOGLE_NAME') { // List of keys that will be converted into scopes (from constants 'SCOPE_state_in_uppercase' in file of service). - // We pass this param list in to 'state' because we need it before and after the redirect. - $shortscope = 'userinfo_email,userinfo_profile,cloud_print'; - if (!empty($conf->global->OAUTH_GSUITE)) { + // List of scopes for Google are here: https://developers.google.com/identity/protocols/oauth2/scopes + // We pass this key list into the param 'state' because we need it before and after the redirect. + $shortscope = 'userinfo_email,userinfo_profile'; + $shortscope .= ',openid,email,profile'; // For openid connect + if (!empty($conf->printing->enabled)) { + $shortscope .= ',cloud_print'; + } + if (!empty($conf->global->OAUTH_GOOGLE_GSUITE)) { $shortscope .= ',admin_directory_user'; } - //$scope.=',gmail_full'; - $urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); + if (!empty($conf->global->OAUTH_GOOGLE_GMAIL)) { + $shortscope.=',gmail_full'; + } + + $oauthstateanticsrf = bin2hex(random_bytes(128/8)); + $_SESSION['oauthstateanticsrf'] = $shortscope.'-'.$oauthstateanticsrf; + + $urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'-'.$oauthstateanticsrf.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = 'https://security.google.com/settings/security/permissions'; - } elseif ($key[0] == 'OAUTH_STRIPE_TEST_NAME') { - $OAUTH_SERVICENAME = 'StripeTest'; + } elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_TEST_NAME') { $urltorenew = $urlwithroot.'/core/modules/oauth/stripetest_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = ''; $urltocheckperms = ''; - } elseif ($key[0] == 'OAUTH_STRIPE_LIVE_NAME') { - $OAUTH_SERVICENAME = 'StripeLive'; + } elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_LIVE_NAME') { $urltorenew = $urlwithroot.'/core/modules/oauth/stripelive_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = ''; $urltocheckperms = ''; @@ -230,11 +242,14 @@ if ($mode == 'setup' && $user->admin) { print ''; print ''; - print '
'; + print '
'; print '
'.$label.''; - if (!empty($key[3])) { - print $langs->trans($key[3]); + print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"'); + print $label; + print ''; + if (!empty($supportedoauth2array[$keyforsupportedoauth2array]['urlforapp'])) { + print $langs->trans($supportedoauth2array[$keyforsupportedoauth2array]['urlforapp']); } print '
'.$langs->trans("UseTheFollowingUrlAsRedirectURI").''; + print ''; print '
'."\n"; print ''; - print ''; + print ''; print ''; print ''; print "\n"; @@ -244,7 +259,7 @@ if ($mode == 'setup' && $user->admin) { //var_dump($key); print $langs->trans("OAuthIDSecret").''; print ''; print ''; @@ -259,7 +274,7 @@ if ($mode == 'setup' && $user->admin) { if (is_object($tokenobj)) { print $langs->trans("HasAccessToken"); } else { - print $langs->trans("NoAccessToken"); + print ''.$langs->trans("NoAccessToken").''; } print ''; print '
'.$langs->trans($key[0]).''; + print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"'); + print $langs->trans($keyforsupportedoauth2array); + print '
'; - print $langs->trans("SeePreviousTab"); + print ''.$langs->trans("SeePreviousTab").''; print ''; print ''; @@ -346,7 +361,7 @@ if ($mode == 'setup' && $user->admin) { if ($mode == 'test' && $user->admin) { print $langs->trans('PrintTestDesc'.$driver)."

\n"; - print '
'; + print '
'; print ''; if (!empty($driver)) { require_once DOL_DOCUMENT_ROOT.'/core/modules/printing/'.$driver.'.modules.php'; diff --git a/htdocs/comm/mailing/card.php b/htdocs/comm/mailing/card.php index 7f2410da136..79faf4b2cd2 100644 --- a/htdocs/comm/mailing/card.php +++ b/htdocs/comm/mailing/card.php @@ -493,9 +493,9 @@ if (empty($reshook)) { if ($action == 'add') { $mesgs = array(); - $object->email_from = (string) GETPOST("from", "none"); // Must allow 'name ' - $object->email_replyto = (string) GETPOST("replyto", "none"); // Must allow 'name ' - $object->email_errorsto = (string) GETPOST("errorsto", "none"); // Must allow 'name ' + $object->email_from = (string) GETPOST("from", 'alphawithlgt'); // Must allow 'name ' + $object->email_replyto = (string) GETPOST("replyto", 'alphawithlgt'); // Must allow 'name ' + $object->email_errorsto = (string) GETPOST("errorsto", 'alphawithlgt'); // Must allow 'name ' $object->title = (string) GETPOST("title"); $object->sujet = (string) GETPOST("sujet"); $object->body = (string) GETPOST("bodyemail", 'restricthtml'); @@ -531,11 +531,11 @@ if (empty($reshook)) { if ($action == 'settitle') { $object->title = trim(GETPOST('title', 'alpha')); } elseif ($action == 'setemail_from') { - $object->email_from = trim(GETPOST('email_from', 'none')); // Must allow 'name ' + $object->email_from = trim(GETPOST('email_from', 'alphawithlgt')); // Must allow 'name ' } elseif ($action == 'setemail_replyto') { - $object->email_replyto = trim(GETPOST('email_replyto', 'none')); // Must allow 'name ' + $object->email_replyto = trim(GETPOST('email_replyto', 'alphawithlgt')); // Must allow 'name ' } elseif ($action == 'setemail_errorsto') { - $object->email_errorsto = trim(GETPOST('email_errorsto', 'none')); // Must allow 'name ' + $object->email_errorsto = trim(GETPOST('email_errorsto', 'alphawithlgt')); // Must allow 'name ' } elseif ($action == 'settitle' && empty($object->title)) { $mesg = $langs->trans("ErrorFieldRequired", $langs->transnoentities("MailTitle")); } elseif ($action == 'setfrom' && empty($object->email_from)) { diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index 74d08ddef2a..63fee0085bb 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -527,9 +527,9 @@ class Facture extends CommonInvoice // Fields coming from GUI (priority on template). TODO Value of template should be used as default value on GUI so we can use here always value from GUI $this->fk_project = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project; - $this->note_public = GETPOST('note_public', 'none') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public; - $this->note_private = GETPOST('note_private', 'none') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private; - $this->model_pdf = GETPOST('model', 'alpha') ? GETPOST('model', 'alpha') : $_facrec->model_pdf; + $this->note_public = GETPOSTISSET('note_public') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public; + $this->note_private = GETPOSTISSET('note_private') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private; + $this->model_pdf = GETPOSTISSET('model') ? GETPOST('model', 'alpha') : $_facrec->model_pdf; $this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id; $this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id; $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account; diff --git a/htdocs/compta/tva/card.php b/htdocs/compta/tva/card.php index 530d18d17f9..317838009c7 100644 --- a/htdocs/compta/tva/card.php +++ b/htdocs/compta/tva/card.php @@ -196,7 +196,8 @@ if ($action == 'add' && !$cancel) { } $object->amount = $amount; $object->label = GETPOST("label", 'alpha'); - $object->note = GETPOST("note", 'none'); + $object->note = GETPOST("note", 'restricthtml'); + $object->note_private = GETPOST("note", 'restricthtml'); if (empty($object->datep)) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("DatePayment")), null, 'errors'); @@ -236,7 +237,7 @@ if ($action == 'add' && !$cancel) { $paiement->amounts = array($object->id=>$amount); // Tableau de montant $paiement->paiementtype = GETPOST("type_payment", 'alphanohtml'); $paiement->num_payment = GETPOST("num_payment", 'alphanohtml'); - $paiement->note = GETPOST("note", 'none'); + $paiement->note = GETPOST("note", 'restricthtml'); if (!$error) { $paymentid = $paiement->create($user, (int) GETPOST('closepaidtva')); diff --git a/htdocs/core/class/CSMSFile.class.php b/htdocs/core/class/CSMSFile.class.php index 8d5bcf7dc50..3f082ba6d53 100644 --- a/htdocs/core/class/CSMSFile.class.php +++ b/htdocs/core/class/CSMSFile.class.php @@ -29,8 +29,9 @@ /** * Class to send SMS - * Usage: $smsfile = new CSMSFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to); - * $smsfile->sendfile(); + * Usage: $smsfile = new CSMSFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to); + * $smsfile->socid=...; $smsfile->contact_id=...; $smsfile->member_id=...; $smsfile->fk_project=...; + * $smsfile->sendfile(); */ class CSMSFile { @@ -48,7 +49,8 @@ class CSMSFile public $nostop; public $socid; - public $contactid; + public $contact_id; + public $member_id; public $fk_project; @@ -135,6 +137,7 @@ class CSMSFile $sms->socid = $this->socid; $sms->contact_id = $this->contact_id; + $sms->member_id = $this->member_id; $sms->project = $this->fk_project; $res = $sms->SmsSend(); @@ -167,6 +170,7 @@ class CSMSFile $sms->socid = $this->socid; $sms->contact_id = $this->contact_id; + $sms->member_id = $this->member_id; $sms->fk_project = $this->fk_project; $res = $sms->SmsSend(); diff --git a/htdocs/core/class/html.formadmin.class.php b/htdocs/core/class/html.formadmin.class.php index 3240d64c893..99ce3bb0201 100644 --- a/htdocs/core/class/html.formadmin.class.php +++ b/htdocs/core/class/html.formadmin.class.php @@ -73,15 +73,19 @@ class FormAdmin $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 12, 0, $mainlangonly); - // If the language to select is not inside the list of available language and empty value is not available, we must find - // an alternative as the language code to pre-select (to avoid to have first element in list pre-selected). - if ($selected && !array_key_exists($selected, $langs_available) && empty($showempty)) { - $tmparray = explode('_', $selected); - if (!empty($tmparray[1])) { - $selected = getLanguageCodeFromCountryCode($tmparray[1]); - } - if (empty($selected)) { - $selected = $langs->defaultlang; + // If empty value is not allowed and the language to select is not inside the list of available language and we must find + // an alternative of the language code to pre-select (to avoid to have first element in list pre-selected). + if ($selected && empty($showempty)) { + if (!is_array($selected) && !array_key_exists($selected, $langs_available)) { + $tmparray = explode('_', $selected); + if (!empty($tmparray[1])) { + $selected = getLanguageCodeFromCountryCode($tmparray[1]); + } + if (empty($selected)) { + $selected = $langs->defaultlang; + } + } else { + // If the preselected value is an array, we do not try to find alternative to preselect } } diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php index e380dea8403..fafd1c015e8 100644 --- a/htdocs/core/customreports.php +++ b/htdocs/core/customreports.php @@ -56,7 +56,7 @@ if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) { } $search_yaxis = GETPOST('search_yaxis', 'array'); - $search_graph = GETPOST('search_graph', 'none'); + $search_graph = GETPOST('search_graph', 'restricthtml'); // Load variable for pagination $limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit; diff --git a/htdocs/core/db/DoliDB.class.php b/htdocs/core/db/DoliDB.class.php index fdbb755637a..4476228d509 100644 --- a/htdocs/core/db/DoliDB.class.php +++ b/htdocs/core/db/DoliDB.class.php @@ -70,6 +70,18 @@ abstract class DoliDB implements Database /** @var string */ public $error; + + + /** + * Return the DB prefix + * + * @return string The DB prefix + */ + public function prefix() + { + return (empty($this->prefix_db) ? MAIN_DB_PREFIX : $this->prefix_db); + } + /** * Format a SQL IF * diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 28a20ad6b09..ce8e02e9cee 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -641,7 +641,7 @@ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null } // Check rule - if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' + if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma' if (!is_array($out) || empty($out)) { $out = array(); } else { @@ -1897,7 +1897,7 @@ function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldi if ($object->element == 'product') { $width = 80; - $cssclass = 'photoref'; + $cssclass = 'photowithmargin photoref'; $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]); $maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5); if ($conf->browser->layout == 'phone') { @@ -3658,7 +3658,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $ 'paiment', 'paragraph', 'play', 'pdf', 'phone', 'phoning', 'phoning_mobile', 'phoning_fax', 'playdisabled', 'previous', 'poll', 'pos', 'printer', 'product', 'propal', 'puce', 'stock', 'resize', 'service', 'stats', 'trip', 'security', 'setup', 'share-alt', 'sign-out', 'split', 'stripe', 'stripe-s', 'switch_off', 'switch_on', 'switch_on_red', 'tools', 'unlink', 'uparrow', 'user', 'vcard', 'wrench', - 'github', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp', + 'github', 'google', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp', 'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies', 'generic', 'home', 'hrm', 'members', 'products', 'invoicing', 'partnership', 'payment', 'payment_vat', 'pencil-ruler', 'preview', 'project', 'projectpub', 'projecttask', 'question', 'refresh', 'region', @@ -3678,7 +3678,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $ if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) { $fa = 'far'; } - if (in_array($pictowithouttext, array('black-tie', 'github', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) { + if (in_array($pictowithouttext, array('black-tie', 'github', 'google', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) { $fa = 'fab'; } diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index be7e1ffa80b..50ae7c33561 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -214,11 +214,14 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = } } - // Common check (local and external) - if (in_array($iptocheck, array('100.100.100.200'))) { - $info['http_code'] = 400; - $info['content'] = 'Error bad hostname IP (Used by Alibaba metadata). Must be an external URL.'; - break; + // Common check on ip (local and external) + $arrayofmetadataserver = array('100.100.100.200' => 'Alibaba', '192.0.0.192'=> 'Oracle', '192.80.8.124'=>'Packet'); + foreach ($arrayofmetadataserver as $ipofmetadataserver => $nameofmetadataserver) { + if ($iptocheck == $ipofmetadataserver) { + $info['http_code'] = 400; + $info['content'] = 'Error bad hostname IP (Used by '.$nameofmetadataserver.' metadata server). This IP is forbidden.'; + break 2; // exit the foreach and the do... + } } // Set CURLOPT_CONNECT_TO so curl will not try another resolution that may give a different result. Possible only on PHP v7+ diff --git a/htdocs/core/lib/oauth.lib.php b/htdocs/core/lib/oauth.lib.php index 48356868143..ab1b5a217b8 100644 --- a/htdocs/core/lib/oauth.lib.php +++ b/htdocs/core/lib/oauth.lib.php @@ -25,13 +25,13 @@ // Supported OAUTH (a provider is supported when a file xxx_oauthcallback.php is available into htdocs/core/modules/oauth) $supportedoauth2array = array( - 'OAUTH_GOOGLE_NAME'=>'google', + 'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google'), ); -if ($conf->global->MAIN_FEATURES_LEVEL >= 2) { - $supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = 'stripetest'; - $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = 'stripelive'; +if (!empty($conf->stripe->enabled)) { + $supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest'); + $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive'); } -$supportedoauth2array['OAUTH_GITHUB_NAME'] = 'github'; +$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub'); diff --git a/htdocs/core/modules/modPrinting.class.php b/htdocs/core/modules/modPrinting.class.php index 4c104b30ecf..c9e99b3d933 100644 --- a/htdocs/core/modules/modPrinting.class.php +++ b/htdocs/core/modules/modPrinting.class.php @@ -34,7 +34,6 @@ include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php'; */ class modPrinting extends DolibarrModules { - /** * Constructor * diff --git a/htdocs/core/modules/oauth/google_oauthcallback.php b/htdocs/core/modules/oauth/google_oauthcallback.php index 4f9fded878f..9fbc38ef156 100644 --- a/htdocs/core/modules/oauth/google_oauthcallback.php +++ b/htdocs/core/modules/oauth/google_oauthcallback.php @@ -16,6 +16,9 @@ * along with this program. If not, see . */ +// This page should make the process to login and get token as described here: +// https://developers.google.com/identity/protocols/oauth2/openid-connect#server-flow + /** * \file htdocs/core/modules/oauth/google_oauthcallback.php * \ingroup oauth @@ -70,9 +73,13 @@ $credentials = new Credentials( $currentUri->getAbsoluteUri() ); +$state = GETPOST('state'); + $requestedpermissionsarray = array(); -if (GETPOST('state')) { - $requestedpermissionsarray = explode(',', GETPOST('state')); // Example: 'userinfo_email,userinfo_profile,cloud_print'. 'state' parameter is standard to store a hash value and can be used to retrieve some parameters back +if ($state) { + // 'state' parameter is standard to store a hash value and can be used to retrieve some parameters back + $statewithscopeonly = preg_replace('/\-.*$/', '', $state); + $requestedpermissionsarray = explode(',', $statewithscopeonly); // Example: 'userinfo_email,userinfo_profile,openid,email,profile,cloud_print'. } if ($action != 'delete' && empty($requestedpermissionsarray)) { print 'Error, parameter state is not defined'; @@ -80,6 +87,8 @@ if ($action != 'delete' && empty($requestedpermissionsarray)) { } //var_dump($requestedpermissionsarray);exit; + + // Instantiate the Api service using the credentials, http client and storage mechanism for the token // $requestedpermissionsarray contains list of scopes. // Conversion into URL is done by Reflection on constant with name SCOPE_scope_in_uppercase @@ -89,7 +98,6 @@ $apiService = $serviceFactory->createService('Google', $credentials, $storage, $ // also note that a refresh token is sent only after a prompt $apiService->setAccessType('offline'); -$apiService->setApprouvalPrompt('force'); $langs->load("oauth"); @@ -108,48 +116,86 @@ if ($action == 'delete') { exit(); } -if (!empty($_GET['code'])) { // We are coming from oauth provider page +if (GETPOST('code')) { // We are coming from oauth provider page. dol_syslog("We are coming from the oauth provider page"); - //llxHeader('',$langs->trans("OAuthSetup")); - //$linkback=''.$langs->trans("BackToModuleList").''; - //print load_fiche_titre($langs->trans("OAuthSetup"),$linkback,'title_setup'); + // We must validate that the $state is the same than the one into $_SESSION['oauthstateanticsrf'], return error if not. + if (isset($_SESSION['oauthstateanticsrf']) && $state != $_SESSION['oauthstateanticsrf']) { + print 'Value for state = '.dol_escape_htmltag($state).' differs from value in $_SESSION["oauthstateanticsrf"]. Code is refused.'; + unset($_SESSION['oauthstateanticsrf']); + } else { + // This was a callback request from service, get the token + try { + //var_dump($_GET['code']); + //var_dump($state); + //var_dump($apiService); // OAuth\OAuth2\Service\Google - //print dol_get_fiche_head(); - // retrieve the CSRF state parameter - $state = isset($_GET['state']) ? $_GET['state'] : null; - //print '
'; + // This request the token + // Result is stored into object managed by class DoliStorage into includes/OAuth/Common/Storage/DoliStorage.php, so into table llx_oauth_token + $token = $apiService->requestAccessToken(GETPOST('code'), $state); - // This was a callback request from service, get the token - try { - //var_dump($_GET['code']); - //var_dump($state); - //var_dump($apiService); // OAuth\OAuth2\Service\Google + // Note: The extraparams has the 'id_token' than contains a lot of information about the user. + $extraparams = $token->getExtraParams(); + $jwt = explode('.', $extraparams['id_token']); - $token = $apiService->requestAccessToken($_GET['code'], $state); + // Extract the middle part, base64 decode, then json_decode it + if (!empty($jwt[1])) { + $userinfo = json_decode(base64_decode($jwt[1]), true); - setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token + // TODO + // We should make the 5 steps of validation of id_token + // Verify that the ID token is properly signed by the issuer. Google-issued tokens are signed using one of the certificates found at the URI specified in the jwks_uri metadata value of the Discovery document. + // Verify that the value of the iss claim in the ID token is equal to https://accounts.google.com or accounts.google.com. + // Verify that the value of the aud claim in the ID token is equal to your app's client ID. + // Verify that the expiry time (exp claim) of the ID token has not passed. + // If you specified a hd parameter value in the request, verify that the ID token has a hd claim that matches an accepted G Suite hosted domain. - $backtourl = $_SESSION["backtourlsavedbeforeoauthjump"]; - unset($_SESSION["backtourlsavedbeforeoauthjump"]); + /* + $useremailuniq = $userinfo['sub']; + $useremail = $userinfo['email']; + $useremailverified = $userinfo['email_verified']; + $username = $userinfo['name']; + $userfamilyname = $userinfo['family_name']; + $usergivenname = $userinfo['given_name']; + $hd = $userinfo['hd']; + */ + } - header('Location: '.$backtourl); - exit(); - } catch (Exception $e) { - print $e->getMessage(); + setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); + + $backtourl = $_SESSION["backtourlsavedbeforeoauthjump"]; + unset($_SESSION["backtourlsavedbeforeoauthjump"]); + + header('Location: '.$backtourl); + exit(); + } catch (Exception $e) { + print $e->getMessage(); + } } -} else // If entry on page with no parameter, we arrive here -{ +} else { + // If we enter this page without 'code' parameter, we arrive here. this is the case when we want to get the redirect + // to the OAuth provider login page $_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl; + if (!preg_match('/^forlogin/', $state)) { + $apiService->setApprouvalPrompt('force'); + } + // This may create record into oauth_state before the header redirect. // Creation of record with state in this tables depend on the Provider used (see its constructor). - if (GETPOST('state')) { - $url = $apiService->getAuthorizationUri(array('state'=>GETPOST('state'))); + if ($state) { + $url = $apiService->getAuthorizationUri(array('state' => $state)); } else { $url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated } + // Add more param + $url .= '&nonce='.bin2hex(random_bytes(64/8)); + // TODO Add param hd and/or login_hint + if (!preg_match('/^forlogin/', $state)) { + //$url .= 'hd=xxx'; + } + // we go on oauth provider authorization page header('Location: '.$url); exit(); @@ -160,6 +206,6 @@ if (!empty($_GET['code'])) { // We are coming from oauth provider page * View */ -// No view at all, just actions +// No view at all, just actions, so we never reach this line. $db->close(); diff --git a/htdocs/core/tpl/admin_extrafields_add.tpl.php b/htdocs/core/tpl/admin_extrafields_add.tpl.php index d9c0f650395..9b46fba6c67 100644 --- a/htdocs/core/tpl/admin_extrafields_add.tpl.php +++ b/htdocs/core/tpl/admin_extrafields_add.tpl.php @@ -185,7 +185,7 @@ $listofexamplesforlink = 'Societe:societe/class/societe.class.php
Contact:con - + diff --git a/htdocs/core/tpl/login.tpl.php b/htdocs/core/tpl/login.tpl.php index 338d2bd1a19..d7e9bda13dd 100644 --- a/htdocs/core/tpl/login.tpl.php +++ b/htdocs/core/tpl/login.tpl.php @@ -33,6 +33,8 @@ if (empty($conf) || !is_object($conf)) { require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; + + header('Cache-Control: Public, must-revalidate'); header("Content-type: text/html; charset=".$conf->file->character_set_client); @@ -316,6 +318,32 @@ if (isset($conf->file->main_authentication) && preg_match('/openid/', $conf->fil echo ''; } +if (isset($conf->file->main_authentication) && preg_match('/google/', $conf->file->main_authentication)) { + $langs->load("users"); + + global $dolibarr_main_url_root; + + // Define $urlwithroot + $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); + $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file + //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current + + echo '
'; + echo '
'; + + //$shortscope = 'userinfo_email,userinfo_profile'; + $shortscope = 'openid,email,profile'; // For openid connect + + $oauthstateanticsrf = bin2hex(random_bytes(128/8)); + $_SESSION['oauthstateanticsrf'] = $shortscope.'-'.$oauthstateanticsrf; + $urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state=forlogin-'.$shortscope.'-'.$oauthstateanticsrf; + + $url = $urltorenew; + + print img_picto('', 'google', 'class="pictofixedwidth"').''.$langs->trans("LoginWith", "Google").''; + + echo '
'; +} ?> diff --git a/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php b/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php index d441d364f4b..2ff5d3f9c43 100644 --- a/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php +++ b/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php @@ -66,7 +66,7 @@ class InterfaceActionsAuto extends DolibarrTriggers * $object->elementtype (->element of object to link action to) * $object->module (if defined, elementtype in llx_actioncomm will be elementtype@module) * - * @param string $action Event action code ('CONTRACT_MODIFY', 'RECRUITMENTCANDIDATURE_MODIFIY', ...) + * @param string $action Event action code ('CONTRACT_MODIFY', 'RECRUITMENTCANDIDATURE_MODIFIY', or example by external module: 'SENTBYSMS'...) * @param Object $object Object * @param User $user Object user * @param Translate $langs Object langs @@ -88,6 +88,7 @@ class InterfaceActionsAuto extends DolibarrTriggers //var_dump($action.' - '.$conf->global->$key);exit; // Do not log events not enabled for this action + // GUI allow to set this option only if entry exists into table llx_c_action_trigger if (empty($conf->global->$key)) { return 0; } @@ -887,8 +888,9 @@ class InterfaceActionsAuto extends DolibarrTriggers } else { // TODO Merge all previous cases into this generic one // $action = BILL_DELETE, TICKET_CREATE, TICKET_MODIFY, TICKET_DELETE, CONTACT_SENTBYMAIL, RECRUITMENTCANDIDATURE_MODIFY, ... + // Can also be a value defined by an external module like SENTBYSMS, COMPANY_SENTBYSMS, MEMBER_SENTBYSMS, ... // Note: We are here only if $conf->global->MAIN_AGENDA_ACTIONAUTO_action is on (tested at begining of this function). - // Note that these key can be set in agenda setup, only if defined into c_action_trigger + // Note that these key can be set in agenda setup, only if defined into llx_c_action_trigger // Load translation files required by the page if (empty($object->actionmsg2)) { $langs->loadLangs(array("agenda", "other")); diff --git a/htdocs/eventorganization/conferenceorboothattendee_note.php b/htdocs/eventorganization/conferenceorboothattendee_note.php index 19d413daaa0..b8fb87d39fa 100644 --- a/htdocs/eventorganization/conferenceorboothattendee_note.php +++ b/htdocs/eventorganization/conferenceorboothattendee_note.php @@ -39,7 +39,6 @@ //if (! defined("MAIN_LANG_DEFAULT")) define('MAIN_LANG_DEFAULT', 'auto'); // Force lang to a particular value //if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule'); // Force authentication handler //if (! defined("NOREDIRECTBYMAINTOLOGIN")) define('NOREDIRECTBYMAINTOLOGIN', 1); // The main.inc.php does not make a redirect if not logged, instead show simple error message -//if (! defined("FORCECSP")) define('FORCECSP', 'none'); // Disable all Content Security Policies //if (! defined('CSRFCHECK_WITH_TOKEN')) define('CSRFCHECK_WITH_TOKEN', '1'); // Force use of CSRF protection with tokens even for GET //if (! defined('NOBROWSERNOTIF')) define('NOBROWSERNOTIF', '1'); // Disable browser notification diff --git a/htdocs/externalsite/admin/index.php b/htdocs/externalsite/admin/index.php index ba23b88e68e..0b55a297be9 100644 --- a/htdocs/externalsite/admin/index.php +++ b/htdocs/externalsite/admin/index.php @@ -57,7 +57,7 @@ if ($action == 'update') { $label = GETPOST('EXTERNALSITE_LABEL', 'alphanohtml'); // exturl can be an url or a HTML string - $exturl = GETPOST('EXTERNALSITE_URL', 'none'); + $exturl = GETPOST('EXTERNALSITE_URL', 'restricthtml'); $exturl = dol_string_onlythesehtmltags($exturl, 1, 1, 0, 1); $exturl = dol_string_onlythesehtmlattributes($exturl); @@ -110,7 +110,7 @@ print ''; print '"; print '
textwithpicto($langs->trans("ComputedFormula"), $langs->trans("ComputedFormulaDesc")).$form->textwithpicto($langs->trans("Computedpersistent"), $langs->trans("ComputedpersistentDesc"), 1, 'warning'); ?>
trans("DefaultValue").' ('.$langs->trans("Database").')'; ?>
'.$langs->trans("ExternalSiteURL")."