openid.class.php 14 KB

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