From ad878cb12e57eec9daf29d273361d352e070fc4e Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Thu, 3 Nov 2022 18:43:26 +0300 Subject: [PATCH 01/10] Add OpenID Connect impl --- .../core/login/functions_openid_connect.php | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 htdocs/core/login/functions_openid_connect.php diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php new file mode 100644 index 00000000000..32f7f934117 --- /dev/null +++ b/htdocs/core/login/functions_openid_connect.php @@ -0,0 +1,120 @@ + + * + * 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 . + */ + +/** + * \file htdocs/core/login/functions_openid_connect.php + * \ingroup core + * \brief OpenID Connect: Authorization Code flow authentication + */ + +include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; + +/** + * Check validity of user/password/entity + * If test is ko, reason must be filled into $_SESSION["dol_loginmesg"] + * + * @param string $usertotest Login + * @param string $passwordtotest Password + * @param int $entitytotest Number of instance (always 1 if module multicompany not enabled) + * @return string Login if OK, '' if KO + */ +function check_user_password_openid_connect($usertotest, $passwordtotest, $entitytotest) +{ + global $conf; + + dol_syslog("functions_openid_connect::check_user_password_openid_connect"); + + $login = ''; + + // Step 1 is done by user: request an authorization code + + if (GETPOSTISSET('username')) { + + // OIDC does not require credentials here: pass on to next auth handler + $_SESSION["dol_loginmesg"] = "Not an OpenID Connect flow"; + dol_syslog("functions_openid_connect::check_user_password_openid_connect not an OIDC flow"); + + } elseif (GETPOSTISSET['code']) { + $auth_code = GETPOST('code', 'aZ09'); + dol_syslog("functions_openid_connect::check_user_password_openid_connect code=".$auth_code); + + // Step 2: turn the authorization code into an access token, using client_secret + $auth_param = [ + 'grant_type' => 'authorization_code', + 'client_id' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_ID, + 'client_secret' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_SECRET, + 'code' => $auth_code, + 'redirect_uri' => $conf->global->MAIN_AUTHENTICATION_OIDC_REDIRECT_URL + ]; + + $token_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_TOKEN_URL, 'POST', http_build_query($auth_param)); + $token_content = json_decode($token_response['content']); + dol_syslog("functions_openid_connect::check_user_password_openid_connect /token=".print_r($token_response, true), LOG_DEBUG); + + if ($token_content->access_token) { + + // Step 3: retrieve user info using token + $userinfo_headers = array('Authorization: Bearer '.$token_content->access_token); + $userinfo_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_USERINFO_URL, 'GET', '', 1, $userinfo_headers); + $userinfo_content = json_decode($userinfo_response['content']); + dol_syslog("functions_openid_connect::check_user_password_openid_connect /userinfo=".print_r($userinfo_response, true), LOG_DEBUG); + + // Get the user attribute (claim) matching the Dolibarr login + $login_claim = 'email'; // default + if (!empty($conf->global->MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM)) { + $login_claim = $conf->global->MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM; + } + + if (array_key_exists($login_claim, $userinfo_content)) { + // Success: retrieve claim to return to Dolibarr as login + $login = $userinfo_content->$login_claim; + $_SESSION["dol_loginmesg"] = ""; + } elseif ($userinfo_content->error) { + // Got user info response but content is an error + $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$userinfo_content->error_description.")"; + } elseif ($userinfo_response['http_code'] == 200) { + // Claim does not exist + $_SESSION["dol_loginmesg"] = "OpenID Connect claim not found: ".$login_claim; + } elseif ($userinfo_response['curl_error_no']) { + // User info request error + $_SESSION["dol_loginmesg"] = "Network error: ".$userinfo_response['curl_error_msg']." (".$userinfo_response['curl_error_no'].")"; + } else { + // Other user info request error + $_SESSION["dol_loginmesg"] = "Userinfo request error (".$userinfo_response['http_code'].")"; + } + + } elseif ($token_content->error) { + // Got token response but content is an error + $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$token_content->error_description.")"; + } elseif ($token_response['curl_error_no']) { + // Token request error + $_SESSION["dol_loginmesg"] = "Network error: ".$token_response['curl_error_msg']." (".$token_response['curl_error_no'].")"; + } else { + // Other token request error + $_SESSION["dol_loginmesg"] = "Token request error (".$token_response['http_code'].")"; + } + + } else { + // No code received + $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (no code received)"; + } + + dol_syslog("functions_openid_connect::check_user_password_openid_connect END"); + + return !empty($login) ? $login : false; +} + From 8ba94e7294f55625a728e156be500b24165f89c5 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Thu, 3 Nov 2022 15:45:53 +0000 Subject: [PATCH 02/10] Fixing style errors. --- htdocs/core/login/functions_openid_connect.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index 32f7f934117..5be96b213ea 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -43,11 +43,9 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit // Step 1 is done by user: request an authorization code if (GETPOSTISSET('username')) { - // OIDC does not require credentials here: pass on to next auth handler $_SESSION["dol_loginmesg"] = "Not an OpenID Connect flow"; dol_syslog("functions_openid_connect::check_user_password_openid_connect not an OIDC flow"); - } elseif (GETPOSTISSET['code']) { $auth_code = GETPOST('code', 'aZ09'); dol_syslog("functions_openid_connect::check_user_password_openid_connect code=".$auth_code); @@ -58,7 +56,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit 'client_id' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_ID, 'client_secret' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_SECRET, 'code' => $auth_code, - 'redirect_uri' => $conf->global->MAIN_AUTHENTICATION_OIDC_REDIRECT_URL + 'redirect_uri' => $conf->global->MAIN_AUTHENTICATION_OIDC_REDIRECT_URL ]; $token_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_TOKEN_URL, 'POST', http_build_query($auth_param)); @@ -66,7 +64,6 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit dol_syslog("functions_openid_connect::check_user_password_openid_connect /token=".print_r($token_response, true), LOG_DEBUG); if ($token_content->access_token) { - // Step 3: retrieve user info using token $userinfo_headers = array('Authorization: Bearer '.$token_content->access_token); $userinfo_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_USERINFO_URL, 'GET', '', 1, $userinfo_headers); @@ -96,7 +93,6 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit // Other user info request error $_SESSION["dol_loginmesg"] = "Userinfo request error (".$userinfo_response['http_code'].")"; } - } elseif ($token_content->error) { // Got token response but content is an error $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$token_content->error_description.")"; @@ -107,14 +103,12 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit // Other token request error $_SESSION["dol_loginmesg"] = "Token request error (".$token_response['http_code'].")"; } - - } else { + } else { // No code received $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (no code received)"; - } + } dol_syslog("functions_openid_connect::check_user_password_openid_connect END"); return !empty($login) ? $login : false; } - From cb9e2a1bdfba1bf7a74d2d181c7799e575d5730f Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Sat, 21 Jan 2023 14:05:56 +0300 Subject: [PATCH 03/10] Update htdocs/core/login/functions_openid_connect.php Co-authored-by: Florent Poinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- htdocs/core/login/functions_openid_connect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index 5be96b213ea..53741347e66 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -46,7 +46,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit // OIDC does not require credentials here: pass on to next auth handler $_SESSION["dol_loginmesg"] = "Not an OpenID Connect flow"; dol_syslog("functions_openid_connect::check_user_password_openid_connect not an OIDC flow"); - } elseif (GETPOSTISSET['code']) { + } elseif (GETPOSTISSET('code')) { $auth_code = GETPOST('code', 'aZ09'); dol_syslog("functions_openid_connect::check_user_password_openid_connect code=".$auth_code); From 1d962a40ecbecf9824670b1a11ace23f52db13a0 Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Sat, 21 Jan 2023 14:06:09 +0300 Subject: [PATCH 04/10] Update htdocs/core/login/functions_openid_connect.php Co-authored-by: Florent Poinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- htdocs/core/login/functions_openid_connect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index 53741347e66..93fea1bb566 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -56,7 +56,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit 'client_id' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_ID, 'client_secret' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_SECRET, 'code' => $auth_code, - 'redirect_uri' => $conf->global->MAIN_AUTHENTICATION_OIDC_REDIRECT_URL + 'redirect_uri' => $conf->global->MAIN_AUTHENTICATION_OIDC_REDIRECT_URL ]; $token_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_TOKEN_URL, 'POST', http_build_query($auth_param)); From b1ea162104c9fb7091a5be6fca9d2539b70e2689 Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Sat, 21 Jan 2023 14:06:23 +0300 Subject: [PATCH 05/10] Update htdocs/core/login/functions_openid_connect.php Co-authored-by: Florent Poinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- htdocs/core/login/functions_openid_connect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index 93fea1bb566..7bc459902b1 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -76,7 +76,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit $login_claim = $conf->global->MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM; } - if (array_key_exists($login_claim, $userinfo_content)) { + if (property_exists($userinfo_content, $login_claim)) { // Success: retrieve claim to return to Dolibarr as login $login = $userinfo_content->$login_claim; $_SESSION["dol_loginmesg"] = ""; From 37bd06d9bcc9b69f579658f92fb9c3f81f6ff4fa Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Sat, 21 Jan 2023 14:06:36 +0300 Subject: [PATCH 06/10] Update htdocs/core/login/functions_openid_connect.php Co-authored-by: Florent Poinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- htdocs/core/login/functions_openid_connect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index 7bc459902b1..db6281f14d1 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -63,7 +63,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit $token_content = json_decode($token_response['content']); dol_syslog("functions_openid_connect::check_user_password_openid_connect /token=".print_r($token_response, true), LOG_DEBUG); - if ($token_content->access_token) { + if (property_exists($token_content, 'access_token')) { // Step 3: retrieve user info using token $userinfo_headers = array('Authorization: Bearer '.$token_content->access_token); $userinfo_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_USERINFO_URL, 'GET', '', 1, $userinfo_headers); From 881c2f833fb41c2d04a9c77eab0535fb32681cba Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Sat, 21 Jan 2023 14:06:48 +0300 Subject: [PATCH 07/10] Update htdocs/core/login/functions_openid_connect.php Co-authored-by: Florent Poinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- htdocs/core/login/functions_openid_connect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index db6281f14d1..fbdca8a8706 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -34,7 +34,7 @@ include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; */ function check_user_password_openid_connect($usertotest, $passwordtotest, $entitytotest) { - global $conf; + global $db, $conf, $langs; dol_syslog("functions_openid_connect::check_user_password_openid_connect"); From dc12fdbef447dbb9ecca9fc6dc6c014fb704fa39 Mon Sep 17 00:00:00 2001 From: Jeritiana Ravelojaona Date: Sat, 21 Jan 2023 14:06:58 +0300 Subject: [PATCH 08/10] Update htdocs/core/login/functions_openid_connect.php Co-authored-by: Florent Poinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- .../core/login/functions_openid_connect.php | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index fbdca8a8706..ea72235ec29 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -78,8 +78,33 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit if (property_exists($userinfo_content, $login_claim)) { // Success: retrieve claim to return to Dolibarr as login - $login = $userinfo_content->$login_claim; - $_SESSION["dol_loginmesg"] = ""; + $sql = 'SELECT login, entity, datestartvalidity, dateendvalidity'; + $sql .= ' FROM '.MAIN_DB_PREFIX.'user'; + $sql .= " WHERE login = '".$userinfo_content->$login_claim."'"; + $sql .= ' AND entity IN (0,'.(array_key_exists('dol_entity', $_SESSION) ? ((int) $_SESSION["dol_entity"]) : 1).')'; + + dol_syslog("functions_openid::check_user_password_openid", LOG_DEBUG); + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $now = dol_now(); + if ($obj->datestartvalidity && $db->jdate($obj->datestartvalidity) > $now) { + // Load translation files required by the page + $langs->loadLangs(array('main', 'errors')); + $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLoginDateValidity"); + return '--bad-login-validity--'; + } + if ($obj->dateendvalidity && $db->jdate($obj->dateendvalidity) < dol_get_first_hour($now)) { + // Load translation files required by the page + $langs->loadLangs(array('main', 'errors')); + $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLoginDateValidity"); + return '--bad-login-validity--'; + } + + $login = $obj->login; + } + } } elseif ($userinfo_content->error) { // Got user info response but content is an error $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$userinfo_content->error_description.")"; From 8e1a753e3f6ab12f221e279edcf89768dad58e33 Mon Sep 17 00:00:00 2001 From: stickler-ci Date: Sat, 21 Jan 2023 11:07:27 +0000 Subject: [PATCH 09/10] Fixing style errors. --- htdocs/core/login/functions_openid_connect.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index ea72235ec29..dcff99adaad 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -82,7 +82,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit $sql .= ' FROM '.MAIN_DB_PREFIX.'user'; $sql .= " WHERE login = '".$userinfo_content->$login_claim."'"; $sql .= ' AND entity IN (0,'.(array_key_exists('dol_entity', $_SESSION) ? ((int) $_SESSION["dol_entity"]) : 1).')'; - + dol_syslog("functions_openid::check_user_password_openid", LOG_DEBUG); $resql = $db->query($sql); if ($resql) { @@ -101,7 +101,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLoginDateValidity"); return '--bad-login-validity--'; } - + $login = $obj->login; } } From 291a7b1d15b4a05c20504dc146c3980281c85fc6 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 13 Apr 2023 22:59:05 +0200 Subject: [PATCH 10/10] Update functions_openid_connect.php --- htdocs/core/login/functions_openid_connect.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/htdocs/core/login/functions_openid_connect.php b/htdocs/core/login/functions_openid_connect.php index dcff99adaad..f9a4483205e 100644 --- a/htdocs/core/login/functions_openid_connect.php +++ b/htdocs/core/login/functions_openid_connect.php @@ -36,10 +36,16 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit { global $db, $conf, $langs; - dol_syslog("functions_openid_connect::check_user_password_openid_connect"); + // Force master entity in transversal mode + $entity = $entitytotest; + if (isModEnabled('multicompany') && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) { + $entity = 1; + } $login = ''; + dol_syslog("functions_openid_connect::check_user_password_openid_connect usertotest=".$usertotest." passwordtotest=".preg_replace('/./', '*', $passwordtotest)." entitytotest=".$entitytotest); + // Step 1 is done by user: request an authorization code if (GETPOSTISSET('username')) { @@ -80,7 +86,7 @@ function check_user_password_openid_connect($usertotest, $passwordtotest, $entit // Success: retrieve claim to return to Dolibarr as login $sql = 'SELECT login, entity, datestartvalidity, dateendvalidity'; $sql .= ' FROM '.MAIN_DB_PREFIX.'user'; - $sql .= " WHERE login = '".$userinfo_content->$login_claim."'"; + $sql .= " WHERE login = '".$db->escape($userinfo_content->$login_claim)."'"; $sql .= ' AND entity IN (0,'.(array_key_exists('dol_entity', $_SESSION) ? ((int) $_SESSION["dol_entity"]) : 1).')'; dol_syslog("functions_openid::check_user_password_openid", LOG_DEBUG);