PageRenderTime 38ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/GoogleOpenID.php

https://bitbucket.org/lkalif/singularity-crash-processor
PHP | 478 lines | 235 code | 67 blank | 176 comment | 33 complexity | 9d0aecac2feff7bbc7395107042b7a7e MD5 | raw file
  1. <?php
  2. /****************************************************************************
  3. * The GoogleOpenID class
  4. *
  5. * This class implements a gateway to the Google OpenID federated login API
  6. *
  7. * By Andrew Peace (http://www.andrewpeace.com)
  8. * December 6, 2008
  9. * Contact me at http://www.andrewpeace.com/contact.html
  10. *
  11. *
  12. * Usage:
  13. *
  14. * To redirect users to a Google login page, simply create a GoogleOpenID:
  15. *
  16. * $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/check.php",
  17. * "ABCDEFG",
  18. * true);
  19. *
  20. * The first argument is the URL you want Google to redirect to when it sends
  21. * its response
  22. *
  23. * The second argument is the association handle you've obtained from Google.
  24. * This parameter is OPTIONAL and can be set to null. However, if it is not
  25. * given or it is set to null, GoogleOpenID will automatically request an
  26. * association handle when it is constructed. This is wastefull! Association
  27. * handles last for two weeks. So, it is better to get and save an association
  28. * handle on your own using GoogleOpenID::getAssociationHandle().
  29. *
  30. * The third argument should be set to true if you want the response to include
  31. * the user's email address. It is optional.
  32. *
  33. * Example:
  34. *
  35. * $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/checkauth.php", "ABCDEFG", true);
  36. * $googleGateway->redirect();
  37. *
  38. * OR
  39. *
  40. * $handle = GoogleOpenID::getAssociationHandle(); // <--save this! valid for two weeks!
  41. *
  42. * $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/checkauth.php", $handle, true);
  43. * $googleGateway->redirect();
  44. *
  45. *
  46. * When you want to recieve a Google OpenID response, simply pass the given
  47. * parameters to GoogleOpenID::create(). In most cases, you should just be able
  48. * to pass the $_GET variable.
  49. *
  50. * To continue the previous example, the following code would go in checkauth.php
  51. *
  52. * $googleResponse = GoogleOpenID::create($_GET);
  53. * $sucess = $googleResponse->success();//true or false
  54. * $user_identity = $googleResponse->identity();//the user's ID
  55. * $user_email = $googleResponse->email();//the user's email
  56. *
  57. * OR, even easier
  58. *
  59. * $googleResponse = GoogleOpenID::getResponse(); // <-- automatically reads $_GET
  60. *
  61. * Advanced users can create a slightly more customized request using the create
  62. * method. It accepts an associative array of openid parameters and sets those
  63. * that it deems appropriate to set (mostly, the parameters that don't have a
  64. * definite value when interacting with Google)
  65. *
  66. *
  67. * Full class signature:
  68. *
  69. * public static createRequest(String, [String], [Boolean])
  70. * public static getResponse()
  71. * public static create(Array)
  72. * public static getAssociationHandle([String])
  73. * public static getEndPoint()
  74. * public redirect()
  75. * public getArray()
  76. * public endPoint()
  77. * public success()
  78. * public assoc_handle()
  79. * public email()
  80. * public identity()
  81. *
  82. * Features to implement:
  83. *
  84. * -In constructor, fix relative->absolute URL conversion (it is messy/buggy)
  85. * -In getAssociationHandle(), use encryption
  86. * -Verify Google's response with signed, sig etc
  87. ****************************************************************************/
  88. class GoogleOpenID{
  89. //the google discover url
  90. const google_discover_url = "https://www.google.com/accounts/o8/id";
  91. //some constant parameters
  92. const openid_ns = "http://specs.openid.net/auth/2.0";
  93. //required for email attribute exchange
  94. const openid_ns_ext1 = "http://openid.net/srv/ax/1.0";
  95. const openid_ext1_mode = "fetch_request";
  96. const openid_ext1_type_email = "http://schema.openid.net/contact/email";
  97. const openid_ext1_required = "email";
  98. //parameters set by constructor
  99. private $mode;//the mode (checkid_setup, id_res, or cancel)
  100. private $response_nonce;
  101. private $return_to;//return URL
  102. private $realm;//the realm that the user is being asked to trust
  103. private $assoc_handle;//the association handle between this service and Google
  104. private $claimed_id;//the id claimed by the user
  105. private $identity;//for google, this is the same as claimed_id
  106. private $signed;
  107. private $sig;
  108. private $email;//the user's email address
  109. //if true, fetch email address
  110. private $require_email;
  111. //private constructor
  112. private function GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email){
  113. //if assoc_handle is null, fetch one
  114. if(is_null($assoc_handle))
  115. $assoc_handle = GoogleOpenID::getAssociationHandle();
  116. //if return_to is a relative URL, make it absolute
  117. if(stripos($return_to, "http://")!==0 &&
  118. stripos($return_to, "https://")!==0){
  119. //if the first character is a slash, delete it
  120. if(substr($return_to, 0, 1)=="/")
  121. $return_to = substr($return_to, 1);
  122. //get the position of server_name
  123. $server_name_pos = stripos($return_to, $_SERVER['SERVER_NAME']);
  124. //if server_name is already at position zero
  125. if($server_name_pos != false && $server_name_pos==0){
  126. $return_to = "http://".$return_to;
  127. } else {
  128. $return_to = "http://".$_SERVER['SERVER_NAME']."/".$return_to;
  129. }//else (server name not at position zero)
  130. }//if return_to is relative
  131. //if realm is null, attempt to set it via return_to
  132. if(is_null($realm)){
  133. //if return_to is set
  134. if(!is_null($return_to)){
  135. $pieces = parse_url($return_to);
  136. $realm = $pieces['scheme']."://".$pieces['host'];
  137. }//if return_to set
  138. }//if realm null
  139. $this->mode = $mode;
  140. $this->op_endpoint = $op_endpoint;
  141. $this->response_nonce = $response_nonce;
  142. $this->return_to = $return_to;
  143. $this->realm = $realm;
  144. $this->assoc_handle = $assoc_handle;
  145. $this->claimed_id = $claimed_id;
  146. $this->identity = $claimed_id;
  147. $this->signed = $signed;
  148. $this->sig = $sig;
  149. $this->email = $email;
  150. $this->require_email = ($require_email) ? true : false;
  151. }//GoogleOpenID
  152. //static creator that accepts only a return_to URL
  153. //this creator should be used when creating a GoogleOpenID for a redirect
  154. public static function createRequest($return_to, $assoc_handle=null, $require_email=false){
  155. return new GoogleOpenID("checkid_setup", null, null, $return_to, null, $assoc_handle, "http://specs.openid.net/auth/2.0/identifier_select", null, null, null, $require_email);
  156. }//createRequest
  157. //static creator that accepts an associative array of parameters and
  158. //sets only the setable attributes (does not overwrite constants)
  159. public static function create($params){
  160. //loop through each parameter
  161. foreach($params as $param => $value){
  162. switch($param){
  163. case "openid_mode":
  164. //check validity of mode
  165. if($value=="checkid_setup" ||
  166. $value=="id_res" ||
  167. $value=="cancel")
  168. $mode = $value;
  169. else
  170. $mode = "cancel";
  171. continue 2;
  172. case "openid_op_endpoint":
  173. $op_endpoint = $value;
  174. continue 2;
  175. case "openid_response_nonce":
  176. $response_nonce = $value;
  177. continue 2;
  178. case "openid_return_to":
  179. $return_to = $value;
  180. continue 2;
  181. case "openid_realm":
  182. $realm = $value;
  183. continue 2;
  184. case "openid_assoc_handle":
  185. $assoc_handle = $value;
  186. continue 2;
  187. case "openid_claimed_id":
  188. $claimed_id = $value;
  189. continue 2;
  190. case "openid_identity":
  191. $claimed_id = $value;
  192. continue 2;
  193. case "openid_signed":
  194. $signed = $value;
  195. continue 2;
  196. case "openid_sig":
  197. $sig = $value;
  198. continue 2;
  199. case "openid_ext1_value_email":
  200. $email = $value;
  201. continue 2;
  202. case "require_email":
  203. $require_email = $value;
  204. continue 2;
  205. default:
  206. continue 2;
  207. }//switch param
  208. }//loop through params
  209. //if require email is not set, set it to false
  210. if(!is_bool($require_email))
  211. $require_email = false;
  212. //if mode is not set, set to default for redirection
  213. if(is_null($mode))
  214. $mode = "checkid_setup";
  215. //if return_to is not set and mode is checkid_setup, throw an error
  216. if(is_null($return_to) && $mode=="checkid_setup")
  217. throw new Exception("GoogleOpenID.create() needs parameter openid.return_to");
  218. //return a new GoogleOpenID with the given parameters
  219. return new GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email);
  220. }//create
  221. //creates and returns a GoogleOpenID from the $_GET variable
  222. public static function getResponse(){
  223. return GoogleOpenID::create($_GET);
  224. }//getResponse
  225. //fetches an association handle from google. association handles are valid
  226. //for two weeks, so coders should do their best to save association handles
  227. //externally and pass them to createRequest()
  228. //NOTE: This function does not use encryption, but it SHOULD! At the time
  229. //I wrote this I wanted it done fast, and I couldn't seem to find a good
  230. //two-way SHA-1 or SHA-256 library for PHP. Encryption is not my thing, so
  231. //it remains unimplemented.
  232. public static function getAssociationHandle($endpoint=null){
  233. //if no endpoint given
  234. if(is_null($endpoint))
  235. //fetch one from Google
  236. $request_url = GoogleOpenID::getEndPoint();
  237. //if endpoint given, set it
  238. else
  239. $request_url = $endpoint;
  240. //append parameters (these never change)
  241. $request_url .= "?openid.ns=".urlencode(GoogleOpenID::openid_ns);
  242. $request_url .= "&openid.mode=associate";
  243. $request_url .= "&openid.assoc_type=HMAC-SHA1";
  244. $request_url .= "&openid.session_type=no-encryption";
  245. //create a CURL session with the request URL
  246. $c = curl_init($request_url);
  247. //set a few options
  248. curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
  249. curl_setopt($c, CURLOPT_HEADER, false);
  250. //get the contents of request URL
  251. $request_contents = curl_exec($c);
  252. //close the CURL session
  253. curl_close($c);
  254. //a handle to be returned
  255. $assoc_handle = null;
  256. //split the response into lines
  257. $lines = explode("\n", $request_contents);
  258. //loop through each line
  259. foreach($lines as $line){
  260. //if this line is assoc_handle
  261. if(substr($line, 0, 13)=="assoc_handle:"){
  262. //save the assoc handle
  263. $assoc_handle = substr($line, 13);
  264. //exit the loop
  265. break;
  266. }//if this line is assoc_handle
  267. }//loop through lines
  268. //return the handle
  269. return $assoc_handle;
  270. }//getAssociationHandle
  271. //fetches an endpoint from Google
  272. public static function getEndPoint(){
  273. //fetch the request URL
  274. $request_url = GoogleOpenID::google_discover_url;
  275. //create a CURL session with the request URL
  276. $c = curl_init($request_url);
  277. //set a few options
  278. curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
  279. curl_setopt($c, CURLOPT_HEADER, false);
  280. //fetch the contents of the request URL
  281. $request_contents = curl_exec($c);
  282. //close the CURL session
  283. curl_close($c);
  284. //create a DOM document so we can extract the URI element
  285. $domdoc = new DOMDocument();
  286. $domdoc->loadXML($request_contents);
  287. //fetch the contents of the URI element
  288. $uri = $domdoc->getElementsByTagName("URI");
  289. $uri = $uri->item(0)->nodeValue;
  290. //return the given URI
  291. return $uri;
  292. }//getEndPoint
  293. //returns an associative array of all openid parameters for this openid
  294. //session. the array contains all the GET attributes that would be sent
  295. //or that have been recieved, meaning:
  296. //
  297. //if mode = "cancel" returns only the mode and ns attributes
  298. //if mode = "id_res" returns all attributes that are not null
  299. //if mode = "checkid_setup" returns only attributes that need to be sent
  300. // in the HTTP request
  301. public function getArray(){
  302. //an associative array to return
  303. $ret = array();
  304. $ret['openid.ns'] = GoogleOpenID::openid_ns;
  305. //if mode is cancel, return only ns and mode
  306. if($this->mode=="cancel"){
  307. $ret['openid.mode'] = "cancel";
  308. return $ret;
  309. }//if cancel
  310. //set attributes that are returned for all cases
  311. if(!is_null($this->claimed_id)){
  312. $ret['openid.claimed_id'] = $this->claimed_id;
  313. $ret['openid.identity'] = $this->claimed_id;
  314. }
  315. if(!is_null($this->return_to))
  316. $ret['openid.return_to'] = $this->return_to;
  317. if(!is_null($this->realm))
  318. $ret['openid.realm'] = $this->realm;
  319. if(!is_null($this->assoc_handle))
  320. $ret['openid.assoc_handle'] = $this->assoc_handle;
  321. if(!is_null($this->mode))
  322. $ret['openid.mode'] = $this->mode;
  323. //set attributes that are returned only if this is a request
  324. //and if getting email is required OR if this is a response and the
  325. //email is given
  326. if(($this->mode=="checkid_setup" AND $this->require_email) OR
  327. ($this->mode=="id_res" AND !is_null($this->email))){
  328. $ret['openid.ns.ext1'] = GoogleOpenID::openid_ns_ext1;
  329. $ret['openid.ext1.mode'] = GoogleOpenID::openid_ext1_mode;
  330. $ret['openid.ext1.type.email'] = GoogleOpenID::openid_ext1_type_email;
  331. $ret['openid.ext1.required'] = GoogleOpenID::openid_ext1_required;
  332. if(!is_null($this->email))
  333. $ret['openid.ext1.value.email'] = $this->email;
  334. }//if redirect and get email
  335. //set attributes that are returned only if this is a response
  336. if($this->mode=="id_res"){
  337. $ret['openid.op_endpoint'] = $this->op_endpoint;
  338. if(!is_null($this->response_nonce))
  339. $ret['openid.response_nonce'] = $this->response_nonce;
  340. if(!is_null($this->signed))
  341. $ret['openid.signed'] = $this->signed;
  342. if(!is_null($this->sig))
  343. $ret['openid.sig'] = $this->sig;
  344. }
  345. //return the array
  346. return $ret;
  347. }//getArray
  348. //sends a request to google and fetches the url to which google is asking
  349. //us to redirect (unless the endpoint is already known, in which case the
  350. //function simply returns it)
  351. public function endPoint(){
  352. //if we know the value of op_endpoint already
  353. if(!is_null($this->op_endpoint))
  354. return $this->op_endpoint;
  355. //fetch the endpoint from Google
  356. $endpoint = GoogleOpenID::getEndPoint();
  357. //save it
  358. $this->op_endpoint = $endpoint;
  359. //return the endpoint
  360. return $endpoint;
  361. }//getedPoint
  362. //returns the URL to which we should send a request (including all GET params)
  363. public function getRequestURL(){
  364. //get all parameters
  365. $params = $this->getArray();
  366. //the base URL
  367. $url = $this->endPoint();
  368. //flag indicating whether to set a '?' or an '&'
  369. $first_attribute = true;
  370. //loop through all params
  371. foreach($params as $param => $value){
  372. //if first attribute print a ?, else print a &
  373. if($first_attribute){
  374. $url .= "?";
  375. $first_attribute = false;
  376. } else {
  377. $url .= "&";
  378. }//else (not first attribute)
  379. $url .= urlencode($param) . "=" . urlencode($value);
  380. }//loop through params
  381. //return the URL
  382. return $url;
  383. }//getRequestURL
  384. //redirects the browser to the appropriate request URL
  385. public function redirect(){
  386. header("Location: ".$this->getRequestURL());
  387. }//redirect
  388. //returns true if the response was a success
  389. public function success(){
  390. return ($this->mode=="id_res");
  391. }//success
  392. //returns the identity given in the response
  393. public function identity(){
  394. if($this->mode!="id_res")
  395. return null;
  396. else
  397. return $this->claimed_id;
  398. }//identity
  399. //returns the email given in the response
  400. public function email(){
  401. if($this->mode!="id_res")
  402. return null;
  403. else
  404. return $this->email;
  405. }//email
  406. //returns the assoc_handle
  407. public function assoc_handle(){
  408. return $this->assoc_handle();
  409. }//assoc_handle
  410. }//class GoogleOpenID
  411. ?>