dolibarr 21.0.0-beta
openid.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2013 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
4 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
30{
34 public $openid_url_identity;
38 public $URLs = array();
42 public $error = array();
46 public $fields = array(
47 'required' => array(),
48 'optional' => array(),
49 );
50
54 public function __construct()
55 {
56 if (!function_exists('curl_exec')) {
57 die('Error: Class SimpleOpenID requires curl extension to work');
58 }
59 }
60
61 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
68 public function SetOpenIDServer($a)
69 {
70 // phpcs:enable
71 $this->URLs['openid_server'] = $a;
72 }
73
74 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
81 public function SetTrustRoot($a)
82 {
83 // phpcs:enable
84 $this->URLs['trust_root'] = $a;
85 }
86
87 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
94 public function SetCancelURL($a)
95 {
96 // phpcs:enable
97 $this->URLs['cancel'] = $a;
98 }
99
100 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
107 public function SetApprovedURL($a)
108 {
109 // phpcs:enable
110 $this->URLs['approved'] = $a;
111 }
112
113 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
120 public function SetRequiredFields($a)
121 {
122 // phpcs:enable
123 if (is_array($a)) {
124 $this->fields['required'] = $a;
125 } else {
126 $this->fields['required'][] = $a;
127 }
128 }
129
130 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
137 public function SetOptionalFields($a)
138 {
139 // phpcs:enable
140 if (is_array($a)) {
141 $this->fields['optional'] = $a;
142 } else {
143 $this->fields['optional'][] = $a;
144 }
145 }
146
147 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
154 public function SetIdentity($a)
155 {
156 // phpcs:enable
157 // Set Identity URL
158 if ((stripos($a, 'http://') === false)
159 && (stripos($a, 'https://') === false)) {
160 $a = 'http://'.$a;
161 }
162 /*
163 $u = parse_url(trim($a));
164 if (!isset($u['path'])){
165 $u['path'] = '/';
166 }else if(substr($u['path'],-1,1) == '/'){
167 $u['path'] = substr($u['path'], 0, strlen($u['path'])-1);
168 }
169 if (isset($u['query'])){ // If there is a query string, then use identity as is
170 $identity = $a;
171 }else{
172 $identity = $u['scheme'] . '://' . $u['host'] . $u['path'];
173 }
174 */
175 $this->openid_url_identity = $a;
176 }
177
178 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
184 public function GetIdentity()
185 {
186 // phpcs:enable
187 // Get Identity
188 return $this->openid_url_identity;
189 }
190
191 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
197 public function GetError()
198 {
199 // phpcs:enable
200 $e = $this->error;
201 return array('code' => $e[0], 'description' => $e[1]);
202 }
203
204 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
212 public function ErrorStore($code, $desc = null)
213 {
214 // phpcs:enable
215 $errs = array();
216 $errs['OPENID_NOSERVERSFOUND'] = 'Cannot find OpenID Server TAG on Identity page.';
217 if ($desc == null) {
218 $desc = $errs[$code];
219 }
220 $this->error = array($code, $desc);
221 }
222
223 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
229 public function IsError()
230 {
231 // phpcs:enable
232 if (count($this->error) > 0) {
233 return true;
234 } else {
235 return false;
236 }
237 }
238
245 public function splitResponse($response)
246 {
247 $r = array();
248 $response = explode("\n", $response);
249 foreach ($response as $line) {
250 $line = trim($line);
251 if ($line != "") {
252 list($key, $value) = explode(":", $line, 2);
253 $r[trim($key)] = trim($value);
254 }
255 }
256 return $r;
257 }
258
259 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
266 public function OpenID_Standarize($openid_identity = null)
267 {
268 // phpcs:enable
269 if ($openid_identity === null) {
270 $openid_identity = $this->openid_url_identity;
271 }
272
273 $u = parse_url(strtolower(trim($openid_identity)));
274
275 if (!isset($u['path']) || ($u['path'] == '/')) {
276 $u['path'] = '';
277 }
278 if (substr($u['path'], -1, 1) == '/') {
279 $u['path'] = substr($u['path'], 0, strlen($u['path']) - 1);
280 }
281 if (isset($u['query'])) { // If there is a query string, then use identity as is
282 return $u['host'].$u['path'].'?'.$u['query'];
283 } else {
284 return $u['host'].$u['path'];
285 }
286 }
287
294 public function array2url($arr)
295 {
296 // converts associated array to URL Query String
297 if (!is_array($arr)) {
298 return false;
299 }
300 $query = '';
301 foreach ($arr as $key => $value) {
302 $query .= $key."=".$value."&";
303 }
304 return $query;
305 }
306
307 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
316 public function FSOCK_Request($url, $method = "GET", $params = "")
317 {
318 // phpcs:enable
319 $fp = fsockopen("ssl://www.myopenid.com", 443, $errno, $errstr, 3); // Connection timeout is 3 seconds
320 if (!$fp) {
321 $this->ErrorStore('OPENID_SOCKETERROR', $errstr);
322 return false;
323 } else {
324 $request = $method." /server HTTP/1.0\r\n";
325 $request .= "User-Agent: Dolibarr\r\n";
326 $request .= "Connection: close\r\n\r\n";
327 fwrite($fp, $request);
328 stream_set_timeout($fp, 4); // Connection response timeout is 4 seconds
329 $res = fread($fp, 2000);
330 $info = stream_get_meta_data($fp);
331 fclose($fp);
332
333 if ($info['timed_out']) {
334 $this->ErrorStore('OPENID_SOCKETTIMEOUT');
335 } else {
336 return $res;
337 }
338 }
339 }
340
341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
348 public function HTML2OpenIDServer($content)
349 {
350 // phpcs:enable
351 $get = array();
352
353 $matches1 = array();
354 $matches2 = array();
355
356 // Get details of their OpenID server and (optional) delegate
357 preg_match_all('/<link[^>]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
358 preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches2);
359 $servers = array_merge($matches1[1], $matches2[1]);
360
361 preg_match_all('/<link[^>]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
362
363 preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches2);
364
365 $delegates = array_merge($matches1[1], $matches2[1]);
366
367 $ret = array($servers, $delegates);
368 return $ret;
369 }
370
371
372 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
379 public function GetOpenIDServer($url = '')
380 {
381 // phpcs:enable
382 global $conf;
383
384 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
385 if (empty($url)) {
386 $url = getDolGlobalString('MAIN_AUTHENTICATION_OPENID_URL');
387 }
388
389 $response = getURLContent($url, 'GET', '', 1, array(), array('http', 'https'));
390
391 list($servers, $delegates) = $this->HTML2OpenIDServer($response);
392 if (count($servers) == 0) {
393 $this->ErrorStore('OPENID_NOSERVERSFOUND');
394 return false;
395 }
396 if (isset($delegates[0])
397 && ($delegates[0] != "")) {
398 $this->SetIdentity($delegates[0]);
399 }
400 $this->SetOpenIDServer($servers[0]);
401 return $servers[0];
402 }
403
404 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
410 public function GetRedirectURL()
411 {
412 // phpcs:enable
413 $params = array();
414 $params['openid.return_to'] = urlencode($this->URLs['approved']);
415 $params['openid.mode'] = 'checkid_setup';
416 $params['openid.identity'] = urlencode($this->openid_url_identity);
417 $params['openid.trust_root'] = urlencode($this->URLs['trust_root']);
418
419 if (isset($this->fields['required'])
420 && (count($this->fields['required']) > 0)) {
421 $params['openid.sreg.required'] = implode(',', $this->fields['required']);
422 }
423 if (isset($this->fields['optional'])
424 && (count($this->fields['optional']) > 0)) {
425 $params['openid.sreg.optional'] = implode(',', $this->fields['optional']);
426 }
427 return $this->URLs['openid_server']."?".$this->array2url($params);
428 }
429
430 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
436 public function Redirect()
437 {
438 // phpcs:enable
439 $redirect_to = $this->GetRedirectURL();
440 if (headers_sent()) { // Use JavaScript to redirect if content has been previously sent (not recommended, but safe)
441 echo '<script nonce="'.getNonce().'" type="text/javascript">window.location=\'';
442 echo $redirect_to;
443 echo '\';</script>';
444 } else { // Default Header Redirect
445 header('Location: '.$redirect_to);
446 }
447 }
448
454 public function validateWithServer()
455 {
456 $params = array(
457 'openid.assoc_handle' => urlencode(GETPOST('openid_assoc_handle')),
458 'openid.signed' => urlencode(GETPOST('openid_signed')),
459 'openid.sig' => urlencode(GETPOST('openid_sig'))
460 );
461 // Send only required parameters to confirm validity
462 $arr_signed = explode(",", str_replace('sreg.', 'sreg_', GETPOST('openid_signed')));
463 $num = count($arr_signed);
464 for ($i = 0; $i < $num; $i++) {
465 $s = str_replace('sreg_', 'sreg.', $arr_signed[$i]);
466 $c = GETPOST('openid_'.$arr_signed[$i]);
467 // if ($c != ""){
468 $params['openid.'.$s] = urlencode($c);
469 // }
470 }
471 $params['openid.mode'] = "check_authentication";
472
473 $openid_server = $this->GetOpenIDServer();
474 if ($openid_server == false) {
475 return false;
476 }
477
478 if (is_array($params)) {
479 $params = $this->array2url($params);
480 }
481
482 $result = getURLContent($openid_server, 'POST', $params);
483
484 $response = $result['content'];
485
486 $data = $this->splitResponse($response);
487 if ($data['is_valid'] == "true") {
488 return true;
489 } else {
490 return false;
491 }
492 }
493
494
495
496
503 public function sendDiscoveryRequestToGetXRDS($url = '')
504 {
505 global $conf;
506
507 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
508 if (empty($url)) {
509 $url = getDolGlobalString('MAIN_AUTHENTICATION_OPENID_URL');
510 }
511
512 dol_syslog(get_class($this).'::sendDiscoveryRequestToGetXRDS get XRDS');
513
514 $addheaders = array('Accept: application/xrds+xml');
515 $response = getURLContent($url, 'GET', '', 1, $addheaders, array('http', 'https'), 0);
516 /* response should like this:
517 <?xml version="1.0" encoding="UTF-8"?>
518 <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
519 <XRD>
520 <Service priority="0">
521 <Type>http://specs.openid.net/auth/2.0/server</Type>
522 <Type>http://openid.net/srv/ax/1.0</Type>
523 ...
524 <URI>https://www.google.com/accounts/o8/ud</URI>
525 </Service>
526 </XRD>
527 </xrds:XRDS>
528 */
529 $content = $response['content'];
530
531 $server = '';
532 if (preg_match('/'.preg_quote('<URI>', '/').'(.*)'.preg_quote('</URI>', '/').'/is', $content, $reg)) {
533 $server = $reg[1];
534 }
535
536 if (empty($server)) {
537 $this->ErrorStore('OPENID_NOSERVERSFOUND');
538 return false;
539 } else {
540 dol_syslog(get_class($this).'::sendDiscoveryRequestToGetXRDS found endpoint = '.$server);
541 $this->SetOpenIDServer($server);
542 return $server;
543 }
544 }
545}
Class to manage OpenID.
GetRedirectURL()
GetRedirectURL.
Redirect()
Redirect.
GetOpenIDServer($url='')
Get openid server.
SetApprovedURL($a)
SetApprovedURL.
__construct()
Constructor.
OpenID_Standarize($openid_identity=null)
OpenID_Standarize.
SetOptionalFields($a)
SetOptionalFields.
HTML2OpenIDServer($content)
HTML2OpenIDServer.
splitResponse($response)
splitResponse
SetCancelURL($a)
SetOpenIDServer.
GetError()
SetOpenIDServer.
SetTrustRoot($a)
SetOpenIDServer.
array2url($arr)
array2url
IsError()
IsError.
SetIdentity($a)
SetIdentity.
FSOCK_Request($url, $method="GET", $params="")
FSOCK_Request.
ErrorStore($code, $desc=null)
ErrorStore.
SetRequiredFields($a)
SetRequiredFields.
SetOpenIDServer($a)
SetOpenIDServer.
GetIdentity()
GetIdentity.
sendDiscoveryRequestToGetXRDS($url='')
Get XRDS response and set possible servers.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
getURLContent($url, $postorget='GET', $param='', $followlocation=1, $addheaders=array(), $allowedschemes=array('http', 'https'), $localurl=0, $ssl_verifypeer=-1)
Function to get a content from an URL (use proxy if proxy defined).
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79