PageRenderTime 45ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/core/class/openid.class.php

http://github.com/Dolibarr/dolibarr
PHP | 558 lines | 280 code | 49 blank | 229 comment | 59 complexity | b1ee04eae7deb14935e3e6a3db7c6ccd MD5 | raw file
Possible License(s): GPL-2.0, AGPL-3.0, LGPL-2.0, CC-BY-SA-4.0, BSD-3-Clause, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, MIT
  1. <?php
  2. /* Copyright (C) 2013 Laurent Destailleur <eldy@users.sourceforge.net>
  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/class/openid.class.php
  19. * \ingroup core
  20. * \brief Class to manage authentication with OpenId
  21. */
  22. /**
  23. * Class to manage OpenID
  24. */
  25. class SimpleOpenID
  26. {
  27. public $openid_url_identity;
  28. public $URLs = array();
  29. public $error = array();
  30. public $fields = array(
  31. 'required' => array(),
  32. 'optional' => array(),
  33. );
  34. /**
  35. * Constructor
  36. */
  37. public function __construct()
  38. {
  39. if (!function_exists('curl_exec')) {
  40. die('Error: Class SimpleOpenID requires curl extension to work');
  41. }
  42. }
  43. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  44. /**
  45. * SetOpenIDServer
  46. *
  47. * @param string $a Server
  48. * @return void
  49. */
  50. public function SetOpenIDServer($a)
  51. {
  52. // phpcs:enable
  53. $this->URLs['openid_server'] = $a;
  54. }
  55. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  56. /**
  57. * SetOpenIDServer
  58. *
  59. * @param string $a Server
  60. * @return void
  61. */
  62. public function SetTrustRoot($a)
  63. {
  64. // phpcs:enable
  65. $this->URLs['trust_root'] = $a;
  66. }
  67. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  68. /**
  69. * SetOpenIDServer
  70. *
  71. * @param string $a Server
  72. * @return void
  73. */
  74. public function SetCancelURL($a)
  75. {
  76. // phpcs:enable
  77. $this->URLs['cancel'] = $a;
  78. }
  79. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  80. /**
  81. * SetApprovedURL
  82. *
  83. * @param string $a Server
  84. * @return void
  85. */
  86. public function SetApprovedURL($a)
  87. {
  88. // phpcs:enable
  89. $this->URLs['approved'] = $a;
  90. }
  91. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  92. /**
  93. * SetRequiredFields
  94. *
  95. * @param string|array $a Server
  96. * @return void
  97. */
  98. public function SetRequiredFields($a)
  99. {
  100. // phpcs:enable
  101. if (is_array($a)) {
  102. $this->fields['required'] = $a;
  103. } else {
  104. $this->fields['required'][] = $a;
  105. }
  106. }
  107. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  108. /**
  109. * SetOptionalFields
  110. *
  111. * @param string|array $a Server
  112. * @return void
  113. */
  114. public function SetOptionalFields($a)
  115. {
  116. // phpcs:enable
  117. if (is_array($a)) {
  118. $this->fields['optional'] = $a;
  119. } else {
  120. $this->fields['optional'][] = $a;
  121. }
  122. }
  123. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  124. /**
  125. * SetIdentity
  126. *
  127. * @param string $a Server
  128. * @return void
  129. */
  130. public function SetIdentity($a)
  131. {
  132. // phpcs:enable
  133. // Set Identity URL
  134. if ((stripos($a, 'http://') === false)
  135. && (stripos($a, 'https://') === false)) {
  136. $a = 'http://'.$a;
  137. }
  138. /*
  139. $u = parse_url(trim($a));
  140. if (!isset($u['path'])){
  141. $u['path'] = '/';
  142. }else if(substr($u['path'],-1,1) == '/'){
  143. $u['path'] = substr($u['path'], 0, strlen($u['path'])-1);
  144. }
  145. if (isset($u['query'])){ // If there is a query string, then use identity as is
  146. $identity = $a;
  147. }else{
  148. $identity = $u['scheme'] . '://' . $u['host'] . $u['path'];
  149. }
  150. */
  151. $this->openid_url_identity = $a;
  152. }
  153. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  154. /**
  155. * GetIdentity
  156. *
  157. * @return string
  158. */
  159. public function GetIdentity()
  160. {
  161. // phpcs:enable
  162. // Get Identity
  163. return $this->openid_url_identity;
  164. }
  165. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  166. /**
  167. * SetOpenIDServer
  168. *
  169. * @return void
  170. */
  171. public function GetError()
  172. {
  173. // phpcs:enable
  174. $e = $this->error;
  175. return array('code'=>$e[0], 'description'=>$e[1]);
  176. }
  177. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  178. /**
  179. * ErrorStore
  180. *
  181. * @param string $code Code
  182. * @param string $desc Description
  183. * @return void
  184. */
  185. public function ErrorStore($code, $desc = null)
  186. {
  187. // phpcs:enable
  188. $errs['OPENID_NOSERVERSFOUND'] = 'Cannot find OpenID Server TAG on Identity page.';
  189. if ($desc == null) {
  190. $desc = $errs[$code];
  191. }
  192. $this->error = array($code, $desc);
  193. }
  194. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  195. /**
  196. * IsError
  197. *
  198. * @return boolean
  199. */
  200. public function IsError()
  201. {
  202. // phpcs:enable
  203. if (count($this->error) > 0) {
  204. return true;
  205. } else {
  206. return false;
  207. }
  208. }
  209. /**
  210. * splitResponse
  211. *
  212. * @param string $response Server
  213. * @return void
  214. */
  215. public function splitResponse($response)
  216. {
  217. $r = array();
  218. $response = explode("\n", $response);
  219. foreach ($response as $line) {
  220. $line = trim($line);
  221. if ($line != "") {
  222. list($key, $value) = explode(":", $line, 2);
  223. $r[trim($key)] = trim($value);
  224. }
  225. }
  226. return $r;
  227. }
  228. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  229. /**
  230. * OpenID_Standarize
  231. *
  232. * @param string $openid_identity Server
  233. * @return string
  234. */
  235. public function OpenID_Standarize($openid_identity = null)
  236. {
  237. // phpcs:enable
  238. if ($openid_identity === null) {
  239. $openid_identity = $this->openid_url_identity;
  240. }
  241. $u = parse_url(strtolower(trim($openid_identity)));
  242. if (!isset($u['path']) || ($u['path'] == '/')) {
  243. $u['path'] = '';
  244. }
  245. if (substr($u['path'], -1, 1) == '/') {
  246. $u['path'] = substr($u['path'], 0, strlen($u['path']) - 1);
  247. }
  248. if (isset($u['query'])) { // If there is a query string, then use identity as is
  249. return $u['host'].$u['path'].'?'.$u['query'];
  250. } else {
  251. return $u['host'].$u['path'];
  252. }
  253. }
  254. /**
  255. * array2url
  256. *
  257. * @param array $arr An array
  258. * @return false|string false if KO, string of url if OK
  259. */
  260. public function array2url($arr)
  261. {
  262. // converts associated array to URL Query String
  263. if (!is_array($arr)) {
  264. return false;
  265. }
  266. $query = '';
  267. foreach ($arr as $key => $value) {
  268. $query .= $key."=".$value."&";
  269. }
  270. return $query;
  271. }
  272. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  273. /**
  274. * FSOCK_Request
  275. *
  276. * @param string $url URL
  277. * @param string $method Method
  278. * @param string $params Params
  279. * @return boolean|void True if success, False if error
  280. */
  281. public function FSOCK_Request($url, $method = "GET", $params = "")
  282. {
  283. // phpcs:enable
  284. $fp = fsockopen("ssl://www.myopenid.com", 443, $errno, $errstr, 3); // Connection timeout is 3 seconds
  285. if (!$fp) {
  286. $this->ErrorStore('OPENID_SOCKETERROR', $errstr);
  287. return false;
  288. } else {
  289. $request = $method." /server HTTP/1.0\r\n";
  290. $request .= "User-Agent: Dolibarr\r\n";
  291. $request .= "Connection: close\r\n\r\n";
  292. fwrite($fp, $request);
  293. stream_set_timeout($fp, 4); // Connection response timeout is 4 seconds
  294. $res = fread($fp, 2000);
  295. $info = stream_get_meta_data($fp);
  296. fclose($fp);
  297. if ($info['timed_out']) {
  298. $this->ErrorStore('OPENID_SOCKETTIMEOUT');
  299. } else {
  300. return $res;
  301. }
  302. }
  303. }
  304. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  305. /**
  306. * CURL_Request
  307. *
  308. * @param string $url URL
  309. * @param string $method Method
  310. * @param string $params Params
  311. * @return string
  312. */
  313. public function CURL_Request($url, $method = "GET", $params = "")
  314. {
  315. // phpcs:enable
  316. // Remember, SSL MUST BE SUPPORTED
  317. if (is_array($params)) {
  318. $params = $this->array2url($params);
  319. }
  320. $curl = curl_init($url.($method == "GET" && $params != "" ? "?".$params : ""));
  321. @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  322. curl_setopt($curl, CURLOPT_HEADER, false);
  323. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  324. curl_setopt($curl, CURLOPT_HTTPGET, ($method == "GET"));
  325. curl_setopt($curl, CURLOPT_POST, ($method == "POST"));
  326. if ($method == "POST") {
  327. curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
  328. }
  329. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  330. $response = curl_exec($curl);
  331. if (curl_errno($curl) == 0) {
  332. $response;
  333. } else {
  334. $this->ErrorStore('OPENID_CURL', curl_error($curl));
  335. }
  336. return $response;
  337. }
  338. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  339. /**
  340. * HTML2OpenIDServer
  341. *
  342. * @param string $content Content
  343. * @return array Array of servers
  344. */
  345. public function HTML2OpenIDServer($content)
  346. {
  347. // phpcs:enable
  348. $get = array();
  349. // Get details of their OpenID server and (optional) delegate
  350. preg_match_all('/<link[^>]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  351. preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches2);
  352. $servers = array_merge($matches1[1], $matches2[1]);
  353. preg_match_all('/<link[^>]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  354. preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches2);
  355. $delegates = array_merge($matches1[1], $matches2[1]);
  356. $ret = array($servers, $delegates);
  357. return $ret;
  358. }
  359. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  360. /**
  361. * Get openid server
  362. *
  363. * @param string $url Url to found endpoint
  364. * @return string Endpoint
  365. */
  366. public function GetOpenIDServer($url = '')
  367. {
  368. // phpcs:enable
  369. global $conf;
  370. include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
  371. if (empty($url)) {
  372. $url = $conf->global->MAIN_AUTHENTICATION_OPENID_URL;
  373. }
  374. $response = getURLContent($url, 'GET', '', 1, array(), array('http', 'https'));
  375. list($servers, $delegates) = $this->HTML2OpenIDServer($response);
  376. if (count($servers) == 0) {
  377. $this->ErrorStore('OPENID_NOSERVERSFOUND');
  378. return false;
  379. }
  380. if (isset($delegates[0])
  381. && ($delegates[0] != "")) {
  382. $this->SetIdentity($delegates[0]);
  383. }
  384. $this->SetOpenIDServer($servers[0]);
  385. return $servers[0];
  386. }
  387. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  388. /**
  389. * GetRedirectURL
  390. *
  391. * @return string
  392. */
  393. public function GetRedirectURL()
  394. {
  395. // phpcs:enable
  396. $params = array();
  397. $params['openid.return_to'] = urlencode($this->URLs['approved']);
  398. $params['openid.mode'] = 'checkid_setup';
  399. $params['openid.identity'] = urlencode($this->openid_url_identity);
  400. $params['openid.trust_root'] = urlencode($this->URLs['trust_root']);
  401. if (isset($this->fields['required'])
  402. && (count($this->fields['required']) > 0)) {
  403. $params['openid.sreg.required'] = implode(',', $this->fields['required']);
  404. }
  405. if (isset($this->fields['optional'])
  406. && (count($this->fields['optional']) > 0)) {
  407. $params['openid.sreg.optional'] = implode(',', $this->fields['optional']);
  408. }
  409. return $this->URLs['openid_server']."?".$this->array2url($params);
  410. }
  411. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  412. /**
  413. * Redirect
  414. *
  415. * @return void
  416. */
  417. public function Redirect()
  418. {
  419. // phpcs:enable
  420. $redirect_to = $this->GetRedirectURL();
  421. if (headers_sent()) { // Use JavaScript to redirect if content has been previously sent (not recommended, but safe)
  422. echo '<script language="JavaScript" type="text/javascript">window.location=\'';
  423. echo $redirect_to;
  424. echo '\';</script>';
  425. } else { // Default Header Redirect
  426. header('Location: '.$redirect_to);
  427. }
  428. }
  429. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  430. /**
  431. * ValidateWithServer
  432. *
  433. * @return boolean
  434. */
  435. public function ValidateWithServer()
  436. {
  437. // phpcs:enable
  438. $params = array(
  439. 'openid.assoc_handle' => urlencode($_GET['openid_assoc_handle']),
  440. 'openid.signed' => urlencode($_GET['openid_signed']),
  441. 'openid.sig' => urlencode($_GET['openid_sig'])
  442. );
  443. // Send only required parameters to confirm validity
  444. $arr_signed = explode(",", str_replace('sreg.', 'sreg_', $_GET['openid_signed']));
  445. $num = count($arr_signed);
  446. for ($i = 0; $i < $num; $i++) {
  447. $s = str_replace('sreg_', 'sreg.', $arr_signed[$i]);
  448. $c = $_GET['openid_'.$arr_signed[$i]];
  449. // if ($c != ""){
  450. $params['openid.'.$s] = urlencode($c);
  451. // }
  452. }
  453. $params['openid.mode'] = "check_authentication";
  454. $openid_server = $this->GetOpenIDServer();
  455. if ($openid_server == false) {
  456. return false;
  457. }
  458. $response = $this->CURL_Request($openid_server, 'POST', $params);
  459. $data = $this->splitResponse($response);
  460. if ($data['is_valid'] == "true") {
  461. return true;
  462. } else {
  463. return false;
  464. }
  465. }
  466. /**
  467. * Get XRDS response and set possible servers.
  468. *
  469. * @param string $url Url of endpoint to request
  470. * @return string First endpoint OpenID server found. False if it failed to found.
  471. */
  472. public function sendDiscoveryRequestToGetXRDS($url = '')
  473. {
  474. global $conf;
  475. include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
  476. if (empty($url)) {
  477. $url = $conf->global->MAIN_AUTHENTICATION_OPENID_URL;
  478. }
  479. dol_syslog(get_class($this).'::sendDiscoveryRequestToGetXRDS get XRDS');
  480. $addheaders = array('Accept: application/xrds+xml');
  481. $response = getURLContent($url, 'GET', '', 1, $addheaders, array('http', 'https'), 0);
  482. /* response should like this:
  483. <?xml version="1.0" encoding="UTF-8"?>
  484. <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
  485. <XRD>
  486. <Service priority="0">
  487. <Type>http://specs.openid.net/auth/2.0/server</Type>
  488. <Type>http://openid.net/srv/ax/1.0</Type>
  489. ...
  490. <URI>https://www.google.com/accounts/o8/ud</URI>
  491. </Service>
  492. </XRD>
  493. </xrds:XRDS>
  494. */
  495. $content = $response['content'];
  496. $server = '';
  497. if (preg_match('/'.preg_quote('<URI>', '/').'(.*)'.preg_quote('</URI>', '/').'/is', $content, $reg)) {
  498. $server = $reg[1];
  499. }
  500. if (empty($server)) {
  501. $this->ErrorStore('OPENID_NOSERVERSFOUND');
  502. return false;
  503. } else {
  504. dol_syslog(get_class($this).'::sendDiscoveryRequestToGetXRDS found endpoint = '.$server);
  505. $this->SetOpenIDServer($server);
  506. return $server;
  507. }
  508. }
  509. }