functions_openid_connect.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. <?php
  2. /* Copyright (C) 2022 Jeritiana Ravelojaona <jeritiana.rav@smartone.ai>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. /**
  18. * \file htdocs/core/login/functions_openid_connect.php
  19. * \ingroup core
  20. * \brief OpenID Connect: Authorization Code flow authentication
  21. *
  22. * See https://github.com/Dolibarr/dolibarr/issues/22740 for more information about setup openid_connect
  23. */
  24. include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
  25. /**
  26. * Check validity of user/password/entity
  27. * If test is ko, reason must be filled into $_SESSION["dol_loginmesg"]
  28. *
  29. * @param string $usertotest Login
  30. * @param string $passwordtotest Password
  31. * @param int $entitytotest Number of instance (always 1 if module multicompany not enabled)
  32. * @return string Login if OK, '' if KO
  33. */
  34. function check_user_password_openid_connect($usertotest, $passwordtotest, $entitytotest)
  35. {
  36. global $db, $conf, $langs;
  37. // Force master entity in transversal mode
  38. $entity = $entitytotest;
  39. if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE')) {
  40. $entity = 1;
  41. }
  42. $login = '';
  43. dol_syslog("functions_openid_connect::check_user_password_openid_connect usertotest=".$usertotest." passwordtotest=".preg_replace('/./', '*', $passwordtotest)." entitytotest=".$entitytotest);
  44. // Step 1 is done by user: request an authorization code
  45. if (GETPOSTISSET('username')) {
  46. // OIDC does not require credentials here: pass on to next auth handler
  47. $_SESSION["dol_loginmesg"] = "Not an OpenID Connect flow";
  48. dol_syslog("functions_openid_connect::check_user_password_openid_connect not an OIDC flow");
  49. } elseif (GETPOSTISSET('code')) {
  50. $auth_code = GETPOST('code', 'aZ09');
  51. dol_syslog("functions_openid_connect::check_user_password_openid_connect code=".$auth_code);
  52. // Step 2: turn the authorization code into an access token, using client_secret
  53. $auth_param = [
  54. 'grant_type' => 'authorization_code',
  55. 'client_id' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_ID,
  56. 'client_secret' => $conf->global->MAIN_AUTHENTICATION_OIDC_CLIENT_SECRET,
  57. 'code' => $auth_code,
  58. 'redirect_uri' => $conf->global->MAIN_AUTHENTICATION_OIDC_REDIRECT_URL
  59. ];
  60. $token_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_TOKEN_URL, 'POST', http_build_query($auth_param));
  61. $token_content = json_decode($token_response['content']);
  62. dol_syslog("functions_openid_connect::check_user_password_openid_connect /token=".print_r($token_response, true), LOG_DEBUG);
  63. if (property_exists($token_content, 'access_token')) {
  64. // Step 3: retrieve user info using token
  65. $userinfo_headers = array('Authorization: Bearer '.$token_content->access_token);
  66. $userinfo_response = getURLContent($conf->global->MAIN_AUTHENTICATION_OIDC_USERINFO_URL, 'GET', '', 1, $userinfo_headers);
  67. $userinfo_content = json_decode($userinfo_response['content']);
  68. dol_syslog("functions_openid_connect::check_user_password_openid_connect /userinfo=".print_r($userinfo_response, true), LOG_DEBUG);
  69. // Get the user attribute (claim) matching the Dolibarr login
  70. $login_claim = 'email'; // default
  71. if (getDolGlobalString('MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM')) {
  72. $login_claim = $conf->global->MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM;
  73. }
  74. if (property_exists($userinfo_content, $login_claim)) {
  75. // Success: retrieve claim to return to Dolibarr as login
  76. $sql = 'SELECT login, entity, datestartvalidity, dateendvalidity';
  77. $sql .= ' FROM '.MAIN_DB_PREFIX.'user';
  78. $sql .= " WHERE login = '".$db->escape($userinfo_content->$login_claim)."'";
  79. $sql .= ' AND entity IN (0,'.(array_key_exists('dol_entity', $_SESSION) ? ((int) $_SESSION["dol_entity"]) : 1).')';
  80. dol_syslog("functions_openid::check_user_password_openid", LOG_DEBUG);
  81. $resql = $db->query($sql);
  82. if ($resql) {
  83. $obj = $db->fetch_object($resql);
  84. if ($obj) {
  85. // Note: Test on date validity is done later natively with isNotIntoValidityDateRange() by core after calling checkLoginPassEntity() that call this method
  86. $login = $obj->login;
  87. }
  88. }
  89. } elseif ($userinfo_content->error) {
  90. // Got user info response but content is an error
  91. $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$userinfo_content->error_description.")";
  92. } elseif ($userinfo_response['http_code'] == 200) {
  93. // Claim does not exist
  94. $_SESSION["dol_loginmesg"] = "OpenID Connect claim not found: ".$login_claim;
  95. } elseif ($userinfo_response['curl_error_no']) {
  96. // User info request error
  97. $_SESSION["dol_loginmesg"] = "Network error: ".$userinfo_response['curl_error_msg']." (".$userinfo_response['curl_error_no'].")";
  98. } else {
  99. // Other user info request error
  100. $_SESSION["dol_loginmesg"] = "Userinfo request error (".$userinfo_response['http_code'].")";
  101. }
  102. } elseif ($token_content->error) {
  103. // Got token response but content is an error
  104. $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$token_content->error_description.")";
  105. } elseif ($token_response['curl_error_no']) {
  106. // Token request error
  107. $_SESSION["dol_loginmesg"] = "Network error: ".$token_response['curl_error_msg']." (".$token_response['curl_error_no'].")";
  108. } else {
  109. // Other token request error
  110. $_SESSION["dol_loginmesg"] = "Token request error (".$token_response['http_code'].")";
  111. }
  112. } else {
  113. // No code received
  114. $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (no code received)";
  115. }
  116. dol_syslog("functions_openid_connect::check_user_password_openid_connect END");
  117. return !empty($login) ? $login : false;
  118. }