openid.class.php 16 KB

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