PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/db/library/openid.php

https://github.com/voitto/dbscript
PHP | 518 lines | 344 code | 67 blank | 107 comment | 76 complexity | 3da15f7716d47e345fbbec0ce5a699e5 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /*
  3. FREE TO USE
  4. Simple OpenID PHP Class
  5. Contributed by http://www.fivestores.com/
  6. Some modifications by Eddie Roosenmaallen, eddie@roosenmaallen.com
  7. Some OpenID 2.0 specifications added by Steve Love (stevelove.org)
  8. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  9. This Class was written to make easy for you to integrate OpenID on your website.
  10. This is just a client, which checks for user's identity. This Class Requires CURL Module.
  11. It should be easy to use some other HTTP Request Method, but remember, often OpenID servers
  12. are using SSL.
  13. We need to be able to perform SSL Verification on the background to check for valid signature.
  14. HOW TO USE THIS CLASS:
  15. STEP 1)
  16. $openid = new SimpleOpenID;
  17. :: SET IDENTITY ::
  18. $openid->SetIdentity($_POST['openid_url']);
  19. :: SET RETURN URL ::
  20. $openid->SetApprovedURL('http://www.yoursite.com/return.php'); // Script which handles a response from OpenID Server
  21. :: SET TRUST ROOT ::
  22. $openid->SetTrustRoot('http://www.yoursite.com/');
  23. :: FETCH SERVER URL FROM IDENTITY PAGE :: [Note: It is recomended to cache this (Session, Cookie, Database)]
  24. $openid->GetOpenIDServer(); // Returns false if server is not found
  25. :: REDIRECT USER TO OPEN ID SERVER FOR APPROVAL ::
  26. :: (OPTIONAL) SET OPENID SERVER ::
  27. $openid->SetOpenIDServer($server_url); // If you have cached previously this, you don't have to call GetOpenIDServer and set value this directly
  28. STEP 2)
  29. Once user gets returned we must validate signature
  30. :: VALIDATE REQUEST ::
  31. true|false = $openid->ValidateWithServer();
  32. ERRORS:
  33. array = $openid->GetError(); // Get latest Error code
  34. FIELDS:
  35. OpenID allowes you to retreive a profile. To set what fields you'd like to get use (accepts either string or array):
  36. $openid->SetRequiredFields(array('email','fullname','dob','gender','postcode','country','language','timezone'));
  37. or
  38. $openid->SetOptionalFields('postcode');
  39. IMPORTANT TIPS:
  40. OPENID as is now, is not trust system. It is a great single-sign on method. If you want to
  41. store information about OpenID in your database for later use, make sure you handle url identities
  42. properly.
  43. For example:
  44. https://steve.myopenid.com/
  45. https://steve.myopenid.com
  46. http://steve.myopenid.com/
  47. http://steve.myopenid.com
  48. ... are representing one single user. Some OpenIDs can be in format openidserver.com/users/user/ - keep this in mind when storing identities
  49. To help you store an OpenID in your DB, you can use function:
  50. $openid_db_safe = $openid->OpenID_Standarize($upenid);
  51. This may not be comatible with current specs, but it works in current enviroment. Use this function to get openid
  52. in one format like steve.myopenid.com (without trailing slashes and http/https).
  53. Use output to insert Identity to database. Don't use this for validation - it may fail.
  54. */
  55. add_include_path( library_path().DIRECTORY_SEPARATOR.'Yadis', true );
  56. require_once 'Services/Yadis/Yadis.php';
  57. class SimpleOpenID{
  58. var $openid_url_identity;
  59. var $URLs = array();
  60. var $error = array();
  61. var $fields = array(
  62. 'required' => array(),
  63. 'optional' => array(),
  64. );
  65. var $arr_ax_types = array(
  66. 'nickname' => 'http://axschema.org/namePerson/friendly',
  67. 'email' => 'http://axschema.org/contact/email',
  68. 'fullname' => 'http://axschema.org/namePerson',
  69. 'dob' => 'http://axschema.org/birthDate',
  70. 'gender' => 'http://axschema.org/person/gender',
  71. 'postcode' => 'http://axschema.org/contact/postalCode/home',
  72. 'country' => 'http://axschema.org/contact/country/home',
  73. 'language' => 'http://axschema.org/pref/language',
  74. 'timezone' => 'http://axschema.org/pref/timezone',
  75. 'prefix' => 'http://axschema.org/namePerson/prefix',
  76. 'firstname' => 'http://axschema.org/namePerson/first',
  77. 'lastname' => 'http://axschema.org/namePerson/last',
  78. 'suffix' => 'http://axschema.org/namePerson/suffix'
  79. );
  80. function SimpleOpenID(){
  81. if (!function_exists('curl_exec')) {
  82. die('Error: Class SimpleOpenID requires curl extension to work');
  83. }
  84. }
  85. function SetOpenIDServer($a){
  86. $this->URLs['openid_server'] = $a;
  87. }
  88. function SetServiceType($a){
  89. // Hopefully the provider is using OpenID 2.0 but let's check
  90. // the protocol version in order to handle backwards compatibility.
  91. // Probably not the best method, but it works for now.
  92. if(stristr($a, "2.0")){
  93. $ns = "http://specs.openid.net/auth/2.0";
  94. $version = "2.0";
  95. }
  96. else if(stristr($a, "1.1")){
  97. $ns = "http://openid.net/signon/1.1";
  98. $version = "1.1";
  99. }else{
  100. $ns = "http://openid.net/signon/1.0";
  101. $version = "1.0";
  102. }
  103. $this->openid_ns = $ns;
  104. $this->openid_version = $version;
  105. }
  106. function SetTrustRoot($a){
  107. $this->URLs['trust_root'] = $a;
  108. }
  109. function SetCancelURL($a){
  110. $this->URLs['cancel'] = $a;
  111. }
  112. function SetApprovedURL($a){
  113. $this->URLs['approved'] = $a;
  114. }
  115. function SetRequiredFields($a){
  116. if (is_array($a)){
  117. $this->fields['required'] = $a;
  118. }else{
  119. $this->fields['required'][] = $a;
  120. }
  121. }
  122. function SetOptionalFields($a){
  123. if (is_array($a)){
  124. $this->fields['optional'] = $a;
  125. }else{
  126. $this->fields['optional'][] = $a;
  127. }
  128. }
  129. function SetPapePolicies($a){
  130. if (is_array($a)){
  131. $this->fields['pape_policies'] = $a;
  132. }else{
  133. $this->fields['pape_policies'][] = $a;
  134. }
  135. }
  136. function SetPapeMaxAuthAge($a){
  137. // Numeric value greater than or equal to zero in seconds
  138. // How much time should the user be given to authenticate?
  139. if(preg_match("/^[1-9]+[0-9]*$/",$a)){
  140. $this->fields['pape_max_auth_age'] = $a;
  141. }else{
  142. die('Error: SetPapeMaxAuthAge requires a numeric value greater than zero.');
  143. }
  144. }
  145. function SetIdentity($a){ // Set Identity URL
  146. /* XRI support not ready yet.
  147. $xriIdentifiers = array('=', '$', '!', '@', '+');
  148. $xriProxy = 'http://xri.net/';
  149. // Is this an XRI string?
  150. // Check for "xri://" prefix or XRI Global Constant Symbols
  151. if (stripos($a, 'xri://') || in_array($a[0], $xriIdentifiers)){
  152. // Attempts to convert an XRI into a URI by removing the "xri://" prefix and
  153. // appending the remainder to the URI of an XRI proxy such as "http://xri.net"
  154. if (stripos($a, 'xri://') == 0) {
  155. if (stripos($a, 'xri://$ip*') == 0) {
  156. $a = substr($a, 10);
  157. } elseif (stripos($a, 'xri://$dns*') == 0) {
  158. $a = substr($a, 11);
  159. } else {
  160. $a = substr($a, 6);
  161. }
  162. }
  163. $a = $xriProxy.$a;
  164. }*/
  165. if ((in_string('http://',$a) === false)
  166. && (in_string('https://',$a) === false)){
  167. $a = 'http://'.$a;
  168. }
  169. if (in_string('gmail',$a) || in_string('google',$a)){
  170. $a = "http://gmail.com";
  171. }
  172. $this->openid_url_identity = $a;
  173. }
  174. function GetIdentity(){ // Get Identity
  175. return $this->openid_url_identity;
  176. }
  177. function GetError(){
  178. $e = $this->error;
  179. return array('code'=>$e[0],'description'=>$e[1]);
  180. }
  181. function ErrorStore($code, $desc = null){
  182. $errs['OPENID_NOSERVERSFOUND'] = 'Cannot find OpenID Server TAG on Identity page.';
  183. if ($desc == null){
  184. $desc = $errs[$code];
  185. }
  186. $this->error = array($code,$desc);
  187. }
  188. function IsError(){
  189. if (count($this->error) > 0){
  190. return true;
  191. }else{
  192. return false;
  193. }
  194. }
  195. function splitResponse($response) {
  196. $r = array();
  197. $response = explode("\n", $response);
  198. foreach($response as $line) {
  199. $line = trim($line);
  200. if ($line != "") {
  201. list($key, $value) = explode(":", $line, 2);
  202. $r[trim($key)] = trim($value);
  203. }
  204. }
  205. return $r;
  206. }
  207. function OpenID_Standarize($openid_identity = null){
  208. if ($openid_identity === null)
  209. $openid_identity = $this->openid_url_identity;
  210. $u = parse_url(strtolower(trim($openid_identity)));
  211. if (!isset($u['path']) || ($u['path'] == '/')) {
  212. $u['path'] = '';
  213. }
  214. if(substr($u['path'],-1,1) == '/'){
  215. $u['path'] = substr($u['path'], 0, strlen($u['path'])-1);
  216. }
  217. if (isset($u['query'])){ // If there is a query string, then use identity as is
  218. return $u['host'] . $u['path'] . '?' . $u['query'];
  219. }else{
  220. return $u['host'] . $u['path'];
  221. }
  222. }
  223. function array2url($arr){ // converts associated array to URL Query String
  224. if (!is_array($arr)){
  225. return false;
  226. }
  227. $query = '';
  228. foreach($arr as $key => $value){
  229. $query .= $key . "=" . $value . "&";
  230. }
  231. return $query;
  232. }
  233. function FSOCK_Request($url, $method="GET", $params = ""){
  234. $fp = fsockopen("ssl://www.myopenid.com", 443, $errno, $errstr, 3); // Connection timeout is 3 seconds
  235. if (!$fp) {
  236. $this->ErrorStore('OPENID_SOCKETERROR', $errstr);
  237. return false;
  238. } else {
  239. $request = $method . " /server HTTP/1.0\r\n";
  240. $request .= "User-Agent: Simple OpenID PHP Class (http://www.phpclasses.org/simple_openid)\r\n";
  241. $request .= "Connection: close\r\n\r\n";
  242. fwrite($fp, $request);
  243. stream_set_timeout($fp, 4); // Connection response timeout is 4 seconds
  244. $res = fread($fp, 2000);
  245. $info = stream_get_meta_data($fp);
  246. fclose($fp);
  247. if ($info['timed_out']) {
  248. $this->ErrorStore('OPENID_SOCKETTIMEOUT');
  249. } else {
  250. return $res;
  251. }
  252. }
  253. }
  254. function CURL_Request($url, $method="GET", $params = "") { // Remember, SSL MUST BE SUPPORTED
  255. if (is_array($params)) $params = $this->array2url($params);
  256. $curl = curl_init($url . ($method == "GET" && $params != "" ? "?" . $params : ""));
  257. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  258. curl_setopt($curl, CURLOPT_HEADER, false);
  259. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  260. curl_setopt($curl, CURLOPT_HTTPGET, ($method == "GET"));
  261. curl_setopt($curl, CURLOPT_POST, ($method == "POST"));
  262. if ($method == "POST") curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
  263. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  264. $response = curl_exec($curl);
  265. if (curl_errno($curl) == 0){
  266. $response;
  267. }else{
  268. $this->ErrorStore('OPENID_CURL', curl_error($curl));
  269. }
  270. return $response;
  271. }
  272. function HTML2OpenIDServer($content) {
  273. $ret = array();
  274. // Get details of their OpenID server and (optional) delegate
  275. preg_match_all('/<link[^>]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  276. preg_match_all('/<link[^>]*rel=[\'"]openid2.provider[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2);
  277. preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches3);
  278. preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid2.provider[\'"][^>]*\/?>/i', $content, $matches4);
  279. $servers = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]);
  280. preg_match_all('/<link[^>]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
  281. preg_match_all('/<link[^>]*rel=[\'"]openid2.local_id[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2);
  282. preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches3);
  283. preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid2.local_id[\'"][^>]*\/?>/i', $content, $matches4);
  284. $delegates = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]);
  285. $ret = array($servers, $delegates);
  286. return $ret;
  287. }
  288. function GetOpenIDServer(){
  289. //Try Yadis Protocol discovery first
  290. $http_response = array();
  291. $fetcher = Services_Yadis_Yadis::getHTTPFetcher();
  292. $yadis_object = Services_Yadis_Yadis::discover($this->openid_url_identity, $http_response, $fetcher);
  293. $yadis = 0;
  294. // Yadis object is returned if discovery is successful
  295. if($yadis_object != null){
  296. $service_list = $yadis_object->services();
  297. $types = $service_list[0]->getTypes();
  298. $servers = $service_list[0]->getURIs();
  299. $delegates = $service_list[0]->getElements('openid:Delegate');
  300. $yadis = 1;
  301. }
  302. if ( count($servers) == 0 ){
  303. $response = $this->CURL_Request($this->openid_url_identity);
  304. list($servers, $delegates) = $this->HTML2OpenIDServer($response);
  305. }
  306. if ( count($servers) == 0 ) {
  307. $curl = curl_init($this->openid_url_identity);
  308. // workaround CURLOPT_FOLLOWLOCATION not working on some hosts
  309. // need to use this elsewhere, OMB remote-subscribe etc XXX
  310. // curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  311. curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, false );
  312. curl_setopt( $curl, CURLOPT_HTTPGET, true );
  313. $response = curl_redir_exec( $curl );
  314. list($servers, $delegates) = $this->HTML2OpenIDServer($response);
  315. }
  316. if (count($servers) == 0){
  317. $this->ErrorStore('OPENID_NOSERVERSFOUND', 'response = '.$response.'<br /><br />openid = '.$this->openid_url_identity.'<br /><br />yadis object = '.serialize($yadis_object).'<br /><br />fetcher = '.serialize($fetcher).'<br /><br />service_list = '.serialize($service_list));
  318. return false;
  319. }
  320. if (empty($servers[0])) {
  321. if ($yadis)
  322. trigger_error('Yadis object was found but getURIs() failed for OpenID: '.$this->openid_url_identity.'<br /><br />'.$this->error, E_USER_ERROR);
  323. else
  324. trigger_error('No Yadis object found, CURL_Request failed for OpenID: '.$this->openid_url_identity.' and the response was '.$response.'<br /><br />'.$this->error, E_USER_ERROR);
  325. }
  326. if (isset($types[0])
  327. && ($types[0] != "")){
  328. $this->SetServiceType($types[0]);
  329. }
  330. if (isset($delegates[0])
  331. && ($delegates[0] != "")){
  332. $this->SetIdentity($delegates[0]);
  333. }
  334. $this->SetOpenIDServer($servers[0]);
  335. return $servers[0];
  336. }
  337. function GetRedirectURL(){
  338. $params = array();
  339. $params['openid.return_to'] = urlencode($this->URLs['approved']);
  340. $params['openid.identity'] = urlencode($this->openid_url_identity);
  341. if($this->openid_version == "2.0"){
  342. $params['openid.ns'] = urlencode($this->openid_ns);
  343. $params['openid.claimed_id'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select");
  344. $params['openid.identity'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select");
  345. $params['openid.realm'] = urlencode($this->URLs['trust_root']);
  346. }else{
  347. $params['openid.trust_root'] = urlencode($this->URLs['trust_root']);
  348. }
  349. $params['openid.mode'] = 'checkid_setup';
  350. // User Info Request: Setup
  351. if (isset($this->fields['required']) || isset($this->fields['optional'])) {
  352. $params['openid.ns.ax'] = urlencode("http://openid.net/srv/ax/1.0");
  353. $params['openid.ax.mode'] = "fetch_request";
  354. $params['openid.ns.sreg'] = urlencode("http://openid.net/extensions/sreg/1.1");
  355. }
  356. // MyOpenID.com is using an outdated AX schema URI
  357. if (stristr($this->URLs['openid_server'], 'myopenid.com')){
  358. $this->arr_ax_types = preg_replace("/axschema.org/","schema.openid.net",$this->arr_ax_types);
  359. }
  360. // User Info Request: Required data
  361. if (isset($this->fields['required'])
  362. && (count($this->fields['required']) > 0)) {
  363. // Set required params for Attribute Exchange (AX) protocol
  364. $params['openid.ax.required'] = implode(',',$this->fields['required']);;
  365. foreach($this->fields['required'] as $field){
  366. if(array_key_exists($field,$this->arr_ax_types)){
  367. $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]);
  368. }
  369. }
  370. // Set required params for Simple Registration (SREG) protocol
  371. $params['openid.sreg.required'] = implode(',',$this->fields['required']);
  372. }
  373. // User Info Request: Optional data
  374. if (isset($this->fields['optional'])
  375. && (count($this->fields['optional']) > 0)) {
  376. // Set optional params for Attribute Exchange (AX) protocol
  377. $params['openid.ax.if_available'] = implode(',',$this->fields['optional']);
  378. foreach($this->fields['optional'] as $field){
  379. if(array_key_exists($field,$this->arr_ax_types)){
  380. $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]);
  381. }
  382. }
  383. // Set optional params for Simple Registration (SREG) protocol
  384. $params['openid.sreg.optional'] = implode(',',$this->fields['optional']);
  385. }
  386. // Add PAPE params if exists
  387. if (isset($this->fields['pape_policies'])
  388. && (count($this->fields['pape_policies']) > 0)) {
  389. $params['openid.ns.pape'] = urlencode("http://specs.openid.net/extensions/pape/1.0");
  390. $params['openid.pape.preferred_auth_policies'] = urlencode(implode(' ',$this->fields['pape_policies']));
  391. if($this->fields['pape_max_auth_age']) {
  392. $params['openid.pape.max_auth_age'] = $this->fields['pape_max_auth_age'];
  393. }
  394. }
  395. $urlJoiner = (strstr($this->URLs['openid_server'], "?")) ? "&" : "?";
  396. return $this->URLs['openid_server'] . $urlJoiner . $this->array2url($params);
  397. }
  398. function Redirect(){
  399. $redirect_to = $this->GetRedirectURL();
  400. if (headers_sent()){ // Use JavaScript to redirect if content has been previously sent (not recommended, but safe)
  401. echo '<script language="JavaScript" type="text/javascript">window.location=\'';
  402. echo $redirect_to;
  403. echo '\';</script>';
  404. }else{ // Default Header Redirect
  405. header('Location: ' . $redirect_to);
  406. }
  407. }
  408. function ValidateWithServer(){
  409. $params = array();
  410. // Find keys that include dots and set them aside
  411. preg_match_all("/([\w]+[\.])/",$_GET['openid_signed'],$arr_periods);
  412. $arr_periods = array_unique(array_shift($arr_periods));
  413. // Duplicate the dot keys, but replace the dot with an underscore
  414. $arr_underscores = preg_replace("/\./","_",$arr_periods);
  415. $arr_getSignedKeys = explode(",",str_replace($arr_periods, $arr_underscores, $_GET['openid_signed']));
  416. // Send only required parameters to confirm validity
  417. foreach($arr_getSignedKeys as $key){
  418. $paramKey = str_replace($arr_underscores, $arr_periods, $key);
  419. $params["openid.$paramKey"] = urlencode($_GET["openid_$key"]);
  420. }
  421. if($this->openid_version != "2.0"){
  422. $params['openid.assoc_handle'] = urlencode($_GET['openid_assoc_handle']);
  423. $params['openid.signed'] = urlencode($_GET['openid_signed']);
  424. }
  425. $params['openid.sig'] = urlencode($_GET['openid_sig']);
  426. $params['openid.mode'] = "check_authentication";
  427. $openid_server = $this->GetOpenIDServer();
  428. if ($openid_server == false){
  429. return false;
  430. }
  431. $response = $this->CURL_Request($openid_server,'POST',$params);
  432. $data = $this->splitResponse($response);
  433. if ($data['is_valid'] == "true") {
  434. return true;
  435. }else{
  436. return false;
  437. }
  438. }
  439. }