PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/cms/openid/class.dopeopenid.php

https://github.com/swat/pragyan
PHP | 562 lines | 353 code | 91 blank | 118 comment | 75 complexity | d761a71e9476153f619f377586762c49 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of Dope OpenID.
  4. * Author: Steve Love (http://www.stevelove.org)
  5. *
  6. * Some code has been modified from Simple OpenID:
  7. * http://www.phpclasses.org/browse/package/3290.html
  8. *
  9. * Yadis Library provided by JanRain:
  10. * http://www.openidenabled.com/php-openid/
  11. *
  12. * Dope OpenID is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation, either version 3 of the License, or
  15. * (at your option) any later version.
  16. *
  17. * Dope OpenID is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU General Public License
  23. * along with Dope OpenID. If not, see <http://www.gnu.org/licenses/>.
  24. **/
  25. /*
  26. * The Yadis library is necessary for OpenID 2.0 specifications.
  27. * The default path assumes the library is located in the same directory
  28. * as the Dope OpenID class file. Feel free to change the path to this
  29. * library if necessary.
  30. */
  31. require_once 'Services/Yadis/Yadis.php';
  32. class Dope_OpenID
  33. {
  34. public $fields = array('required' => array(),'optional' => array());
  35. public $arr_userinfo = array();
  36. // An associative array of AX schema definitions
  37. private $arr_ax_types = array(
  38. 'nickname' => 'http://axschema.org/namePerson/friendly',
  39. 'email' => 'http://axschema.org/contact/email',
  40. 'fullname' => 'http://axschema.org/namePerson',
  41. 'dob' => 'http://axschema.org/birthDate',
  42. 'gender' => 'http://axschema.org/person/gender',
  43. 'postcode' => 'http://axschema.org/contact/postalCode/home',
  44. 'country' => 'http://axschema.org/contact/country/home',
  45. 'language' => 'http://axschema.org/pref/language',
  46. 'timezone' => 'http://axschema.org/pref/timezone',
  47. 'prefix' => 'http://axschema.org/namePerson/prefix',
  48. 'firstname' => 'http://axschema.org/namePerson/first',
  49. 'lastname' => 'http://axschema.org/namePerson/last',
  50. 'suffix' => 'http://axschema.org/namePerson/suffix'
  51. );
  52. private $openid_url_identity;
  53. private $openid_ns;
  54. private $openid_version;
  55. private $error = array();
  56. private $URLs = array();
  57. // PHP 4 compatible constructor calls the PHP 5 constructor.
  58. public function Dope_OpenID($identity)
  59. {
  60. $this->__construct($identity);
  61. }
  62. // The PHP 5 constructor
  63. public function __construct($identity)
  64. {
  65. if ( ! $identity) {
  66. $this->errorStore('OPENID_NOIDENTITY','No identity passed to Dope OpenID constructor.');
  67. return FALSE;
  68. }
  69. // cURL is required for Dope OpenID to work.
  70. if ( ! function_exists('curl_exec')) {
  71. die('Error: Dope OpenID requires the PHP cURL extension.');
  72. }
  73. // Set user's identity.
  74. $this->setIdentity($identity);
  75. }
  76. public function setReturnURL($url)
  77. {
  78. $this->URLs['return'] = $url;
  79. }
  80. public function setTrustRoot($url)
  81. {
  82. $this->URLs['trust_root'] = $url;
  83. }
  84. public function setCancelURL($url)
  85. {
  86. $this->URLs['cancel'] = $url;
  87. }
  88. public function setRequiredInfo($fields)
  89. {
  90. if (is_array($fields)){
  91. $this->fields['required'] = $fields;
  92. }
  93. else {
  94. $this->fields['required'][] = $fields;
  95. }
  96. }
  97. public function setOptionalInfo($fields)
  98. {
  99. if (is_array($fields)) {
  100. $this->fields['optional'] = $fields;
  101. }
  102. else {
  103. $this->fields['optional'][] = $fields;
  104. }
  105. }
  106. public function setPapePolicies($policies)
  107. {
  108. if (is_array($policies)) {
  109. $this->fields['pape_policies'] = $policies;
  110. }
  111. else {
  112. $this->fields['pape_policies'][] = $policies;
  113. }
  114. }
  115. public function setPapeMaxAuthAge($seconds){
  116. // Numeric value greater than or equal to zero in seconds
  117. // How much time should the user be given to authenticate?
  118. if(preg_match("/^[1-9]+[0-9]*$/",$seconds)){
  119. $this->fields['pape_max_auth_age'] = $seconds;
  120. }
  121. else {
  122. $this->errorStore('OPENID_MAXAUTHAGE','Max Auth Age must be a numeric value greater than or equal to zero in seconds.');
  123. return FALSE;
  124. }
  125. }
  126. public function isError()
  127. {
  128. if ( ! empty($this->error)) {
  129. return TRUE;
  130. }
  131. else{
  132. return FALSE;
  133. }
  134. }
  135. public function getError()
  136. {
  137. $the_error = $this->error;
  138. return array('code'=>$the_error[0],'description'=>$the_error[1]);
  139. }
  140. /*
  141. * Method to discover the OpenID Provider's endpoint location
  142. */
  143. public function getOpenIDEndpoint()
  144. {
  145. //Try Yadis Protocol discovery first
  146. $http_response = array();
  147. $fetcher = Services_Yadis_Yadis::getHTTPFetcher();
  148. $yadis_object = Services_Yadis_Yadis::discover($this->openid_url_identity, $http_response, $fetcher);
  149. // Yadis object is returned if discovery is successful
  150. if($yadis_object != NULL) {
  151. $service_list = $yadis_object->services();
  152. $service_types = $service_list[0]->getTypes();
  153. $servers = $service_list[0]->getURIs();
  154. $delegates = $service_list[0]->getElements('openid:Delegate');
  155. }
  156. // Else try HTML discovery
  157. else {
  158. $response = $this->makeCURLRequest($this->openid_url_identity);
  159. list($servers, $delegates) = $this->parseHTML($response);
  160. }
  161. // If no servers were discovered by Yadis or by parsing HTML, error out
  162. if (empty($servers)){
  163. $this->errorStore('OPENID_NOSERVERSFOUND');
  164. return FALSE;
  165. }
  166. // If $service_type has at least one non-null character
  167. if (isset($service_types[0]) && ($service_types[0] != "")) {
  168. $this->setServiceType($service_types[0]);
  169. }
  170. // If $delegates has at least one non-null character
  171. if (isset($delegates[0]) && ($delegates[0] != "")) {
  172. $this->setIdentity($delegates[0]);
  173. }
  174. $this->setOpenIDEndpoint($servers[0]);
  175. return $servers[0];
  176. }
  177. /*
  178. * Method to redirect user to their OpenID Provider's endpoint
  179. */
  180. public function redirect()
  181. {
  182. $redirect_to = $this->getRedirectURL();
  183. // If headers() have already been sent
  184. if (headers_sent()) {
  185. // PHP header() redirect won't work if headers already sent.
  186. // JavaScript redirect is pretty much only option in this case.
  187. echo '<script language="JavaScript" type="text/javascript">window.location=\'';
  188. echo $redirect_to;
  189. echo '\';</script>';
  190. }
  191. // Else we can use PHP header() redirect
  192. else {
  193. header('Location: ' . $redirect_to);
  194. }
  195. }
  196. /*
  197. * Method to validate information with the OpenID Provider
  198. */
  199. public function validateWithServer()
  200. {
  201. $params = array();
  202. // Find keys that include dots and store them in an array
  203. preg_match_all("/([\w]+[\.])/",$_GET['openid_signed'],$arr_periods);
  204. $arr_periods = array_unique(array_shift($arr_periods));
  205. // Duplicate the dot keys array, but replace the dot with an underscore
  206. $arr_underscores = preg_replace("/\./","_",$arr_periods);
  207. // The OpenID Provider returns a list of signed keys we need to validate
  208. $arr_get_signed_keys = explode(",",str_replace($arr_periods, $arr_underscores, $_GET['openid_signed']));
  209. // Send back only the signed keys to confirm validity
  210. foreach($arr_get_signed_keys as $key) {
  211. $paramKey = str_replace($arr_underscores, $arr_periods, $key);
  212. $params["openid.$paramKey"] = urlencode($_GET["openid_$key"]);
  213. }
  214. // If we're using OpenID 2.0 specs, we must include these values also
  215. if($this->openid_version != "2.0"){
  216. $params['openid.assoc_handle'] = urlencode($_GET['openid_assoc_handle']);
  217. $params['openid.signed'] = urlencode($_GET['openid_signed']);
  218. }
  219. $params['openid.sig'] = urlencode($_GET['openid_sig']);
  220. $params['openid.mode'] = "check_authentication";
  221. $endpoint_url = $this->getOpenIDEndpoint();
  222. if ($endpoint_url == FALSE) {
  223. return FALSE;
  224. }
  225. // Send the signed keys back to the OpenID Provider using cURL
  226. $response = $this->makeCURLRequest($endpoint_url,'POST',$params);
  227. $data = $this->splitResponse($response);
  228. // If the response is successful, OpenID Provider will return [is_valid => 'true']
  229. if ($data['is_valid'] == "true") {
  230. return TRUE;
  231. }else{
  232. return FALSE;
  233. }
  234. }
  235. /*
  236. * Method to filter through $_GET array for requested user info.
  237. * TODO: Add documentation.
  238. */
  239. public function filterUserInfo($arr_get)
  240. {
  241. foreach($arr_get as $key => $value){
  242. $trimmed_key = substr($key,strrpos($key,"_")+1);
  243. if(stristr($key, 'openid_ext1_value') && isset($value[1])) {
  244. $this->arr_userinfo[$trimmed_key] = $value;
  245. }
  246. if( (stristr($key, 'sreg_') || stristr($key, 'ax_value_')) &&
  247. array_key_exists($trimmed_key, $this->arr_ax_types)) {
  248. $this->arr_userinfo[$trimmed_key] = $value;
  249. }
  250. }
  251. return $this->arr_userinfo;
  252. }
  253. /*
  254. * Method to set the user's OpenID identity url.
  255. * As of OpenID 2.0, this identifier could be an XRI Identifier
  256. * TODO: Add XRI support.
  257. */
  258. private function setIdentity($identity)
  259. {
  260. /* XRI support is not ready yet.
  261. $xriIdentifiers = array('=', '$', '!', '@', '+');
  262. $xriProxy = 'http://xri.net/';
  263. // Is this an XRI string?
  264. // Check for "xri://" prefix or XRI Global Constant Symbols
  265. if (stripos($identity, 'xri://') OR in_array($identity[0], $xriIdentifiers)){
  266. // Attempts to convert an XRI into a URI by removing the "xri://" prefix and
  267. // appending the remainder to the URI of an XRI proxy such as "http://xri.net"
  268. if (stripos($identity, 'xri://') == 0) {
  269. if (stripos($identity, 'xri://$ip*') == 0) {
  270. $identity = substr($identity, 10);
  271. } elseif (stripos($identity, 'xri://$dns*') == 0) {
  272. $identity = substr($identity, 11);
  273. } else {
  274. $identity = substr($identity, 6);
  275. }
  276. }
  277. $identity = $xriProxy.$identity;
  278. }*/
  279. // Append "http://" to the identity string if not already present.
  280. if ((stripos($identity, 'http://') === FALSE) &&
  281. (stripos($identity, 'https://') === FALSE)) {
  282. $identity = 'http://'.$identity;
  283. }
  284. // Google is not publishing its XRDS document yet, so the OpenID
  285. // endpoint must be set manually for now.
  286. if (stripos($identity, 'gmail') OR stripos($identity, 'google')) {
  287. $identity = "https://www.google.com/accounts/o8/id";
  288. }
  289. $this->openid_url_identity = $identity;
  290. }
  291. private function getIdentity()
  292. { // Get Identity
  293. return $this->openid_url_identity;
  294. }
  295. /*
  296. * Method to make cURL request.
  297. */
  298. private function makeCURLRequest($url, $method="GET", $params = "")
  299. {
  300. if (is_array($params)) {
  301. $params = $this->createQueryString($params);
  302. }
  303. $curl = curl_init($url . ($method == "GET" && $params != "" ? "?" . $params : ""));
  304. //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
  305. curl_setopt($curl, CURLOPT_HEADER, FALSE);
  306. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
  307. curl_setopt($curl, CURLOPT_HTTPGET, ($method == "GET"));
  308. curl_setopt($curl, CURLOPT_POST, ($method == "POST"));
  309. curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
  310. curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
  311. if ($method == "POST") {
  312. curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
  313. }
  314. $response = curl_exec($curl);
  315. if (curl_errno($curl) == 0) {
  316. $response;
  317. }
  318. else {
  319. $this->errorStore('OPENID_CURL', curl_error($curl));
  320. }
  321. return $response;
  322. }
  323. private function parseHTML($content)
  324. {
  325. $ret = array();
  326. // Get details of their OpenID server and (optional) delegate
  327. preg_match_all('/<link[^>]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  328. preg_match_all('/<link[^>]*rel=[\'"]openid2.provider[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2);
  329. preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches3);
  330. preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid2.provider[\'"][^>]*\/?>/i', $content, $matches4);
  331. $servers = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]);
  332. preg_match_all('/<link[^>]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  333. preg_match_all('/<link[^>]*rel=[\'"]openid2.local_id[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2);
  334. preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches3);
  335. preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid2.local_id[\'"][^>]*\/?>/i', $content, $matches4);
  336. $delegates = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]);
  337. $ret = array($servers, $delegates);
  338. return $ret;
  339. }
  340. private function splitResponse($response)
  341. {
  342. $r = array();
  343. $response = explode("\n", $response);
  344. foreach($response as $line) {
  345. $line = trim($line);
  346. if ($line != "") {
  347. list($key, $value) = explode(":", $line, 2);
  348. $r[trim($key)] = trim($value);
  349. }
  350. }
  351. return $r;
  352. }
  353. private function createQueryString($array_params)
  354. {
  355. if ( ! is_array($array_params)) {
  356. return FALSE;
  357. }
  358. $query = "";
  359. foreach($array_params as $key => $value){
  360. $query .= $key . "=" . $value . "&";
  361. }
  362. return $query;
  363. }
  364. private function setOpenIDEndpoint($url)
  365. {
  366. $this->URLs['openid_server'] = $url;
  367. }
  368. private function setServiceType($url)
  369. {
  370. /*
  371. * Hopefully the provider is using OpenID 2.0 but let's check
  372. * the protocol version in order to handle backwards compatibility.
  373. * Probably not the most efficient method, but it works for now.
  374. */
  375. if (stristr($url, "2.0")) {
  376. $ns = "http://specs.openid.net/auth/2.0";
  377. $version = "2.0";
  378. }
  379. else if (stristr($url, "1.1")) {
  380. $ns = "http://openid.net/signon/1.1";
  381. $version = "1.1";
  382. }
  383. else {
  384. $ns = "http://openid.net/signon/1.0";
  385. $version = "1.0";
  386. }
  387. $this->openid_ns = $ns;
  388. $this->openid_version = $version;
  389. }
  390. function getRedirectURL()
  391. {
  392. $params = array();
  393. $params['openid.return_to'] = urlencode($this->URLs['return']);
  394. $params['openid.identity'] = urlencode($this->openid_url_identity);
  395. if($this->openid_version == "2.0"){
  396. $params['openid.ns'] = urlencode($this->openid_ns);
  397. $params['openid.claimed_id'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select");
  398. $params['openid.identity'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select");
  399. $params['openid.realm'] = urlencode($this->URLs['trust_root']);
  400. }
  401. else {
  402. $params['openid.trust_root'] = urlencode($this->URLs['trust_root']);
  403. }
  404. $params['openid.mode'] = 'checkid_setup';
  405. /**
  406. * Handle user attribute requests.
  407. */
  408. $info_request = FALSE;
  409. // User Info Request: Setup
  410. if (isset($this->fields['required']) OR isset($this->fields['optional'])) {
  411. $params['openid.ns.ax'] = "http://openid.net/srv/ax/1.0";
  412. $params['openid.ax.mode'] = "fetch_request";
  413. $params['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
  414. $info_request = TRUE;
  415. }
  416. // MyOpenID.com is using an outdated AX schema URI
  417. if (stristr($this->URLs['openid_server'], 'myopenid.com') && $info_request == TRUE) {
  418. $this->arr_ax_types = preg_replace("/axschema.org/","schema.openid.net",$this->arr_ax_types);
  419. }
  420. // If we're requesting user info from Google, it MUST be specified as "required"
  421. // Will not work otherwise.
  422. if (stristr($this->URLs['openid_server'], 'google.com') && $info_request == TRUE) {
  423. $this->fields['required'] = array_unique(array_merge($this->fields['optional'], $this->fields['required']));
  424. $this->fields['optional'] = array();
  425. }
  426. // User Info Request: Required data
  427. if (isset($this->fields['required']) && ( ! empty($this->fields['required']))) {
  428. // Set required params for Attribute Exchange (AX) protocol
  429. $params['openid.ax.required'] = implode(',',$this->fields['required']);
  430. foreach($this->fields['required'] as $field) {
  431. if(array_key_exists($field,$this->arr_ax_types)) {
  432. $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]);
  433. }
  434. }
  435. // Set required params for Simple Registration (SREG) protocol
  436. $params['openid.sreg.required'] = implode(',',$this->fields['required']);
  437. }
  438. // User Info Request: Optional data
  439. if (isset($this->fields['optional']) && ( ! empty($this->fields['optional']))) {
  440. // Set optional params for Attribute Exchange (AX) protocol
  441. $params['openid.ax.if_available'] = implode(',',$this->fields['optional']);
  442. foreach($this->fields['optional'] as $field) {
  443. if(array_key_exists($field,$this->arr_ax_types)) {
  444. $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]);
  445. }
  446. }
  447. // Set optional params for Simple Registration (SREG) protocol
  448. $params['openid.sreg.optional'] = implode(',',$this->fields['optional']);
  449. }
  450. // Add PAPE params if exists
  451. if (isset($this->fields['pape_policies']) && ( ! empty($this->fields['pape_policies']))) {
  452. $params['openid.ns.pape'] = "http://specs.openid.net/extensions/pape/1.0";
  453. $params['openid.pape.preferred_auth_policies'] = urlencode(implode(' ',$this->fields['pape_policies']));
  454. if($this->fields['pape_max_auth_age']) {
  455. $params['openid.pape.max_auth_age'] = $this->fields['pape_max_auth_age'];
  456. }
  457. }
  458. $urlJoiner = (strstr($this->URLs['openid_server'], "?")) ? "&" : "?";
  459. return $this->URLs['openid_server'] . $urlJoiner . $this->createQueryString($params);
  460. }
  461. private function errorStore($code, $desc = NULL)
  462. {
  463. $errs['OPENID_NOSERVERSFOUND'] = 'Cannot find OpenID Server TAG on Identity page.';
  464. if ($desc == NULL){
  465. $desc = $errs[$code];
  466. }
  467. $this->error = array($code,$desc);
  468. }
  469. }
  470. ?>