dolibarr 24.0.0-beta
functions_openid_connect.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2022 Jeritiana Ravelojaona <jeritiana.rav@smartone.ai>
3 * Copyright (C) 2023-2024 Solution Libre SAS <contact@solution-libre.fr>
4 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2024 Maximilien Rozniecki <mrozniecki@easya.solutions>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
29include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
30include_once DOL_DOCUMENT_ROOT.'/core/lib/openid_connect.lib.php';
31
41function check_user_password_openid_connect($usertotest, $passwordtotest, $entitytotest)
42{
43 global $db;
44
45 if (getDolGlobalInt('MAIN_AUTHENTICATION_OIDC_ON', 0) <= 0) {
46 $_SESSION["dol_loginmesg"] = "OpenID Connect is disabled";
47 dol_syslog("functions_openid_connect::check_user_password_openid_connect Module disabled");
48 return false;
49 }
50
51 // Force master entity in transversal mode
52 $entity = $entitytotest;
53 if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE')) {
54 $entity = 1;
55 }
56
57 dol_syslog("functions_openid_connect::check_user_password_openid_connect usertotest=".$usertotest." passwordtotest=".preg_replace('/./', '*', $passwordtotest)." entitytotest=".$entitytotest);
58
59 // Step 1 is done by user: request an authorization code
60
61 if (GETPOSTISSET('username')) {
62 // OIDC does not require credentials here: pass on to next auth handler
63 $_SESSION["dol_loginmesg"] = "Not an OpenID Connect flow";
64 dol_syslog("functions_openid_connect::check_user_password_openid_connect::not an OIDC flow");
65 return false;
66 } elseif (empty($_SESSION['oidc_state'])) {
67 // No state in session (set by callback.php)
68 $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (no state received)";
69 dol_syslog("functions_openid_connect::check_user_password_openid_connect::no state received", LOG_ERR);
70 return false;
71 } elseif (empty($_SESSION['oidc_code'])) {
72 // No code in session (set by callback.php)
73 $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (no code received)";
74 dol_syslog("functions_openid_connect::check_user_password_openid_connect::no code received", LOG_ERR);
75 return false;
76 }
77
78 // Read OIDC params from session and clear immediately (single-use)
79 $auth_code = $_SESSION['oidc_code'];
80 $state = $_SESSION['oidc_state'];
81 unset($_SESSION['oidc_code'], $_SESSION['oidc_state']);
82 dol_syslog('functions_openid_connect::check_user_password_openid_connect state='.$state);
83
84 if (!openid_connect_verify_state($state)) {
85 // State signature invalid - not generated by this Dolibarr instance
86 $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (state does not match)";
87 dol_syslog("functions_openid_connect::check_user_password_openid_connect::state does not match", LOG_ERR);
88 return false;
89 }
90
91 // Step 2: turn the authorization code into an access token, using client_secret
92 $auth_param = [
93 'grant_type' => 'authorization_code',
94 'client_id' => getDolGlobalString('MAIN_AUTHENTICATION_OIDC_CLIENT_ID'),
95 'client_secret' => getDolGlobalString('MAIN_AUTHENTICATION_OIDC_CLIENT_SECRET'),
96 'code' => $auth_code,
97 'redirect_uri' => openid_connect_get_redirect_url()
98 ];
99
100 $token_response = getURLContent(getDolGlobalString('MAIN_AUTHENTICATION_OIDC_TOKEN_URL'), 'POST', http_build_query($auth_param), 1, array(), array('https'), 2);
101 $token_content = json_decode($token_response['content']);
102 dol_syslog("functions_openid_connect::check_user_password_openid_connect /token=".print_r($token_response, true), LOG_DEBUG);
103
104 if ($token_response['curl_error_no']) {
105 // Token request error
106 $_SESSION["dol_loginmesg"] = "Network error: ".$token_response['curl_error_msg']." (".$token_response['curl_error_no'].")";
107 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$_SESSION["dol_loginmesg"], LOG_ERR);
108 return false;
109 } elseif ($token_response['http_code'] >= 400 && $token_response['http_code'] < 500) {
110 // HTTP Error
111 $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$token_response['content'].")";
112 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$token_response['content'], LOG_ERR);
113 return false;
114 } elseif (property_exists($token_content, 'error') && $token_content->error) {
115 // Got token response but content is an error
116 $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$token_content->error_description.")";
117 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$token_content->error_description, LOG_ERR);
118 return false;
119 } elseif (!property_exists($token_content, 'access_token')) {
120 // Other token request error
121 $_SESSION["dol_loginmesg"] = "Token request error (".$token_response['http_code'].")";
122 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$_SESSION["dol_loginmesg"], LOG_ERR);
123 return false;
124 }
125
126 // Step 3: retrieve user info (login, email, ...) from OIDC server using token
127 $userinfo_headers = array('Authorization: Bearer '.$token_content->access_token);
128 $userinfo_response = getURLContent(getDolGlobalString('MAIN_AUTHENTICATION_OIDC_USERINFO_URL'), 'GET', '', 1, $userinfo_headers, array('https'), 2);
129 $userinfo_content = json_decode($userinfo_response['content']);
130
131 dol_syslog("functions_openid_connect::check_user_password_openid_connect /userinfo=".print_r($userinfo_response, true), LOG_DEBUG);
132
133 // Get the user attribute (claim) matching the Dolibarr login
134 $login_claim = 'email'; // default
135 if (getDolGlobalString('MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM')) {
136 $login_claim = getDolGlobalString('MAIN_AUTHENTICATION_OIDC_LOGIN_CLAIM');
137 }
138
139 if ($userinfo_response['curl_error_no']) {
140 // User info request error
141 $_SESSION["dol_loginmesg"] = "Network error: ".$userinfo_response['curl_error_msg']." (".$userinfo_response['curl_error_no'].")";
142 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$_SESSION["dol_loginmesg"], LOG_ERR);
143 return false;
144 } elseif ($userinfo_response['http_code'] >= 400 && $userinfo_response['http_code'] < 500) {
145 // HTTP Error
146 $_SESSION["dol_loginmesg"] = "OpenID Connect user info error: " . $userinfo_response['content'];
147 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$userinfo_response['content'], LOG_ERR);
148 return false;
149 } elseif (property_exists($userinfo_content, 'error') && $userinfo_content->error) {
150 // Got user info response but content is an error
151 $_SESSION["dol_loginmesg"] = "Error in OAuth 2.0 flow (".$userinfo_content->error_description.")";
152 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$userinfo_content->error_description, LOG_ERR);
153 return false;
154 } elseif (!property_exists($userinfo_content, $login_claim)) {
155 // Other user info request error
156 $_SESSION["dol_loginmesg"] = "Userinfo request error (".$userinfo_response['http_code'].")";
157 dol_syslog("functions_openid_connect::check_user_password_openid_connect::".$_SESSION["dol_loginmesg"], LOG_ERR);
158 return false;
159 }
160
161 // Success: retrieve claim to return to Dolibarr as login
162 $sql = 'SELECT login, entity, datestartvalidity, dateendvalidity';
163 $sql .= ' FROM '.MAIN_DB_PREFIX.'user';
164 if ($login_claim === 'email') {
165 // If login claim is email, check both login and email fields
166 $sql .= " WHERE (login = '".$db->escape((string) $userinfo_content->$login_claim)."' OR email = '".$db->escape((string) $userinfo_content->$login_claim)."')";
167 } else {
168 $sql .= " WHERE login = '".$db->escape((string) $userinfo_content->$login_claim)."'";
169 }
170 $sql .= ' AND entity IN (0,'.(array_key_exists('dol_entity', $_SESSION) ? ((int) $_SESSION["dol_entity"]) : 1).')';
171
172 dol_syslog("functions_openid::check_user_password_openid", LOG_DEBUG);
173
174 $resql = $db->query($sql);
175 if (!$resql) {
176 dol_syslog("functions_openid_connect::check_user_password_openid_connect::Error with sql query (".$db->error().")");
177 return false;
178 }
179 $numres = $db->num_rows($resql);
180 if ($numres > 1) {
181 dol_syslog("functions_openid_connect::check_user_password_openid_connect::Error more than 1 result from the query");
182 return false;
183 }
184 $obj = $db->fetch_object($resql);
185 if (!$obj) {
186 // User not found in Dolibarr - check if auto-creation is enabled
187 global $dolibarr_main_authentication_autocreateuser;
188 if (empty($dolibarr_main_authentication_autocreateuser)) {
189 $_SESSION["dol_loginmesg"] = "User not found in Dolibarr and auto-creation is disabled";
190 dol_syslog("functions_openid_connect::check_user_password_openid_connect::User not found, auto-creation disabled");
191 return false;
192 }
193
194 dol_syslog("functions_openid_connect::check_user_password_openid_connect::User not found, auto-creating from OIDC claims");
195
196 $claim_value = (string) $userinfo_content->$login_claim;
197 $result_create = openid_connect_create_user($db, $userinfo_content, $claim_value, $entity);
198 if (is_numeric($result_create) && $result_create < 0) {
199 $_SESSION["dol_loginmesg"] = "Error creating user from OIDC";
200 dol_syslog("functions_openid_connect::check_user_password_openid_connect::Error creating user, result=".$result_create, LOG_ERR);
201 return false;
202 }
203
204 // $result_create is the sanitized login string
205 $newlogin = $result_create;
206 dol_syslog("functions_openid_connect::check_user_password_openid_connect::User auto-created login=".$newlogin);
207
208 $_SESSION['OPENID_CONNECT'] = true;
209 return $newlogin;
210 }
211
212 $_SESSION['OPENID_CONNECT'] = true;
213
214 // Note: Test on date validity is done later natively with isNotIntoValidityDateRange() by core after calling checkLoginPassEntity() that call this method
215 dol_syslog("functions_openid_connect::check_user_password_openid_connect END");
216 return $obj->login;
217}
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
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.
check_user_password_openid_connect($usertotest, $passwordtotest, $entitytotest)
Check validity of user/password/entity If test is ko, reason must be filled into $_SESSION["dol_login...
getURLContent($url, $postorget='GET', $param='', $followlocation=1, $addheaders=array(), $allowedschemes=array('http', 'https'), $localurl=0, $ssl_verifypeer=-1, $timeoutconnect=0, $timeoutresponse=0, $otherCurlOptions=array(), $morelogsuffix='')
Function to get a content from an URL (use proxy if proxy defined).
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
openid_connect_verify_state($state)
Verify an OIDC state token.
openid_connect_create_user($db, $userinfo, $login, $entity)
Create a Dolibarr user from OIDC userinfo claims.
openid_connect_get_redirect_url()
Return the OIDC callback redirect URL.