openid.class.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  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. * HTML2OpenIDServer
  307. *
  308. * @param string $content Content
  309. * @return array Array of servers
  310. */
  311. public function HTML2OpenIDServer($content)
  312. {
  313. // phpcs:enable
  314. $get = array();
  315. $matches1 = array(); $matches2 = array();
  316. // Get details of their OpenID server and (optional) delegate
  317. preg_match_all('/<link[^>]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  318. preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches2);
  319. $servers = array_merge($matches1[1], $matches2[1]);
  320. preg_match_all('/<link[^>]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  321. preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches2);
  322. $delegates = array_merge($matches1[1], $matches2[1]);
  323. $ret = array($servers, $delegates);
  324. return $ret;
  325. }
  326. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  327. /**
  328. * Get openid server
  329. *
  330. * @param string $url Url to found endpoint
  331. * @return string Endpoint
  332. */
  333. public function GetOpenIDServer($url = '')
  334. {
  335. // phpcs:enable
  336. global $conf;
  337. include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
  338. if (empty($url)) {
  339. $url = $conf->global->MAIN_AUTHENTICATION_OPENID_URL;
  340. }
  341. $response = getURLContent($url, 'GET', '', 1, array(), array('http', 'https'));
  342. list($servers, $delegates) = $this->HTML2OpenIDServer($response);
  343. if (count($servers) == 0) {
  344. $this->ErrorStore('OPENID_NOSERVERSFOUND');
  345. return false;
  346. }
  347. if (isset($delegates[0])
  348. && ($delegates[0] != "")) {
  349. $this->SetIdentity($delegates[0]);
  350. }
  351. $this->SetOpenIDServer($servers[0]);
  352. return $servers[0];
  353. }
  354. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  355. /**
  356. * GetRedirectURL
  357. *
  358. * @return string
  359. */
  360. public function GetRedirectURL()
  361. {
  362. // phpcs:enable
  363. $params = array();
  364. $params['openid.return_to'] = urlencode($this->URLs['approved']);
  365. $params['openid.mode'] = 'checkid_setup';
  366. $params['openid.identity'] = urlencode($this->openid_url_identity);
  367. $params['openid.trust_root'] = urlencode($this->URLs['trust_root']);
  368. if (isset($this->fields['required'])
  369. && (count($this->fields['required']) > 0)) {
  370. $params['openid.sreg.required'] = implode(',', $this->fields['required']);
  371. }
  372. if (isset($this->fields['optional'])
  373. && (count($this->fields['optional']) > 0)) {
  374. $params['openid.sreg.optional'] = implode(',', $this->fields['optional']);
  375. }
  376. return $this->URLs['openid_server']."?".$this->array2url($params);
  377. }
  378. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  379. /**
  380. * Redirect
  381. *
  382. * @return void
  383. */
  384. public function Redirect()
  385. {
  386. // phpcs:enable
  387. $redirect_to = $this->GetRedirectURL();
  388. if (headers_sent()) { // Use JavaScript to redirect if content has been previously sent (not recommended, but safe)
  389. echo '<script type="text/javascript">window.location=\'';
  390. echo $redirect_to;
  391. echo '\';</script>';
  392. } else { // Default Header Redirect
  393. header('Location: '.$redirect_to);
  394. }
  395. }
  396. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  397. /**
  398. * ValidateWithServer
  399. *
  400. * @return boolean
  401. */
  402. public function ValidateWithServer()
  403. {
  404. // phpcs:enable
  405. $params = array(
  406. 'openid.assoc_handle' => urlencode($_GET['openid_assoc_handle']),
  407. 'openid.signed' => urlencode($_GET['openid_signed']),
  408. 'openid.sig' => urlencode($_GET['openid_sig'])
  409. );
  410. // Send only required parameters to confirm validity
  411. $arr_signed = explode(",", str_replace('sreg.', 'sreg_', $_GET['openid_signed']));
  412. $num = count($arr_signed);
  413. for ($i = 0; $i < $num; $i++) {
  414. $s = str_replace('sreg_', 'sreg.', $arr_signed[$i]);
  415. $c = $_GET['openid_'.$arr_signed[$i]];
  416. // if ($c != ""){
  417. $params['openid.'.$s] = urlencode($c);
  418. // }
  419. }
  420. $params['openid.mode'] = "check_authentication";
  421. $openid_server = $this->GetOpenIDServer();
  422. if ($openid_server == false) {
  423. return false;
  424. }
  425. if (is_array($params)) {
  426. $params = $this->array2url($params);
  427. }
  428. $result = getURLContent($openid_server, 'POST', $params);
  429. $response = $result['content'];
  430. $data = $this->splitResponse($response);
  431. if ($data['is_valid'] == "true") {
  432. return true;
  433. } else {
  434. return false;
  435. }
  436. }
  437. /**
  438. * Get XRDS response and set possible servers.
  439. *
  440. * @param string $url Url of endpoint to request
  441. * @return string First endpoint OpenID server found. False if it failed to found.
  442. */
  443. public function sendDiscoveryRequestToGetXRDS($url = '')
  444. {
  445. global $conf;
  446. include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
  447. if (empty($url)) {
  448. $url = $conf->global->MAIN_AUTHENTICATION_OPENID_URL;
  449. }
  450. dol_syslog(get_class($this).'::sendDiscoveryRequestToGetXRDS get XRDS');
  451. $addheaders = array('Accept: application/xrds+xml');
  452. $response = getURLContent($url, 'GET', '', 1, $addheaders, array('http', 'https'), 0);
  453. /* response should like this:
  454. <?xml version="1.0" encoding="UTF-8"?>
  455. <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
  456. <XRD>
  457. <Service priority="0">
  458. <Type>http://specs.openid.net/auth/2.0/server</Type>
  459. <Type>http://openid.net/srv/ax/1.0</Type>
  460. ...
  461. <URI>https://www.google.com/accounts/o8/ud</URI>
  462. </Service>
  463. </XRD>
  464. </xrds:XRDS>
  465. */
  466. $content = $response['content'];
  467. $server = '';
  468. if (preg_match('/'.preg_quote('<URI>', '/').'(.*)'.preg_quote('</URI>', '/').'/is', $content, $reg)) {
  469. $server = $reg[1];
  470. }
  471. if (empty($server)) {
  472. $this->ErrorStore('OPENID_NOSERVERSFOUND');
  473. return false;
  474. } else {
  475. dol_syslog(get_class($this).'::sendDiscoveryRequestToGetXRDS found endpoint = '.$server);
  476. $this->SetOpenIDServer($server);
  477. return $server;
  478. }
  479. }
  480. }