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