479 lines
17 KiB
PHP
479 lines
17 KiB
PHP
<?php
|
|
|
|
/****************************************************************************
|
|
* The GoogleOpenID class
|
|
*
|
|
* This class implements a gateway to the Google OpenID federated login API
|
|
*
|
|
* By Andrew Peace (http://www.andrewpeace.com)
|
|
* December 6, 2008
|
|
* Contact me at http://www.andrewpeace.com/contact.html
|
|
*
|
|
*
|
|
* Usage:
|
|
*
|
|
* To redirect users to a Google login page, simply create a GoogleOpenID:
|
|
*
|
|
* $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/check.php",
|
|
* "ABCDEFG",
|
|
* true);
|
|
*
|
|
* The first argument is the URL you want Google to redirect to when it sends
|
|
* its response
|
|
*
|
|
* The second argument is the association handle you've obtained from Google.
|
|
* This parameter is OPTIONAL and can be set to null. However, if it is not
|
|
* given or it is set to null, GoogleOpenID will automatically request an
|
|
* association handle when it is constructed. This is wastefull! Association
|
|
* handles last for two weeks. So, it is better to get and save an association
|
|
* handle on your own using GoogleOpenID::getAssociationHandle().
|
|
*
|
|
* The third argument should be set to true if you want the response to include
|
|
* the user's email address. It is optional.
|
|
*
|
|
* Example:
|
|
*
|
|
* $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/checkauth.php", "ABCDEFG", true);
|
|
* $googleGateway->redirect();
|
|
*
|
|
* OR
|
|
*
|
|
* $handle = GoogleOpenID::getAssociationHandle(); // <--save this! valid for two weeks!
|
|
*
|
|
* $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/checkauth.php", $handle, true);
|
|
* $googleGateway->redirect();
|
|
*
|
|
*
|
|
* When you want to recieve a Google OpenID response, simply pass the given
|
|
* parameters to GoogleOpenID::create(). In most cases, you should just be able
|
|
* to pass the $_GET variable.
|
|
*
|
|
* To continue the previous example, the following code would go in checkauth.php
|
|
*
|
|
* $googleResponse = GoogleOpenID::create($_GET);
|
|
* $sucess = $googleResponse->success();//true or false
|
|
* $user_identity = $googleResponse->identity();//the user's ID
|
|
* $user_email = $googleResponse->email();//the user's email
|
|
*
|
|
* OR, even easier
|
|
*
|
|
* $googleResponse = GoogleOpenID::getResponse(); // <-- automatically reads $_GET
|
|
*
|
|
* Advanced users can create a slightly more customized request using the create
|
|
* method. It accepts an associative array of openid parameters and sets those
|
|
* that it deems appropriate to set (mostly, the parameters that don't have a
|
|
* definite value when interacting with Google)
|
|
*
|
|
*
|
|
* Full class signature:
|
|
*
|
|
* public static createRequest(String, [String], [Boolean])
|
|
* public static getResponse()
|
|
* public static create(Array)
|
|
* public static getAssociationHandle([String])
|
|
* public static getEndPoint()
|
|
* public redirect()
|
|
* public getArray()
|
|
* public endPoint()
|
|
* public success()
|
|
* public assoc_handle()
|
|
* public email()
|
|
* public identity()
|
|
*
|
|
* Features to implement:
|
|
*
|
|
* -In constructor, fix relative->absolute URL conversion (it is messy/buggy)
|
|
* -In getAssociationHandle(), use encryption
|
|
* -Verify Google's response with signed, sig etc
|
|
****************************************************************************/
|
|
|
|
class GoogleOpenID{
|
|
//the google discover url
|
|
const google_discover_url = "https://www.google.com/accounts/o8/id";
|
|
|
|
//some constant parameters
|
|
const openid_ns = "http://specs.openid.net/auth/2.0";
|
|
//required for email attribute exchange
|
|
const openid_ns_ext1 = "http://openid.net/srv/ax/1.0";
|
|
const openid_ext1_mode = "fetch_request";
|
|
const openid_ext1_type_email = "http://schema.openid.net/contact/email";
|
|
const openid_ext1_required = "email";
|
|
|
|
//parameters set by constructor
|
|
private $mode;//the mode (checkid_setup, id_res, or cancel)
|
|
private $response_nonce;
|
|
private $return_to;//return URL
|
|
private $realm;//the realm that the user is being asked to trust
|
|
private $assoc_handle;//the association handle between this service and Google
|
|
private $claimed_id;//the id claimed by the user
|
|
private $identity;//for google, this is the same as claimed_id
|
|
private $signed;
|
|
private $sig;
|
|
private $email;//the user's email address
|
|
|
|
//if true, fetch email address
|
|
private $require_email;
|
|
|
|
//private constructor
|
|
private function GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email){
|
|
|
|
//if assoc_handle is null, fetch one
|
|
if(is_null($assoc_handle))
|
|
$assoc_handle = GoogleOpenID::getAssociationHandle();
|
|
|
|
//if return_to is a relative URL, make it absolute
|
|
if(stripos($return_to, "http://")!==0 &&
|
|
stripos($return_to, "https://")!==0){
|
|
//if the first character is a slash, delete it
|
|
if(substr($return_to, 0, 1)=="/")
|
|
$return_to = substr($return_to, 1);
|
|
//get the position of server_name
|
|
$server_name_pos = stripos($return_to, $_SERVER['SERVER_NAME']);
|
|
//if server_name is already at position zero
|
|
if($server_name_pos != false && $server_name_pos==0){
|
|
$return_to = "http://".$return_to;
|
|
} else {
|
|
$return_to = "http://".$_SERVER['SERVER_NAME']."/".$return_to;
|
|
}//else (server name not at position zero)
|
|
}//if return_to is relative
|
|
|
|
//if realm is null, attempt to set it via return_to
|
|
if(is_null($realm)){
|
|
//if return_to is set
|
|
if(!is_null($return_to)){
|
|
$pieces = parse_url($return_to);
|
|
$realm = $pieces['scheme']."://".$pieces['host'];
|
|
}//if return_to set
|
|
}//if realm null
|
|
|
|
$this->mode = $mode;
|
|
$this->op_endpoint = $op_endpoint;
|
|
$this->response_nonce = $response_nonce;
|
|
$this->return_to = $return_to;
|
|
$this->realm = $realm;
|
|
$this->assoc_handle = $assoc_handle;
|
|
$this->claimed_id = $claimed_id;
|
|
$this->identity = $claimed_id;
|
|
$this->signed = $signed;
|
|
$this->sig = $sig;
|
|
$this->email = $email;
|
|
$this->require_email = ($require_email) ? true : false;
|
|
}//GoogleOpenID
|
|
|
|
//static creator that accepts only a return_to URL
|
|
//this creator should be used when creating a GoogleOpenID for a redirect
|
|
public static function createRequest($return_to, $assoc_handle=null, $require_email=false){
|
|
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);
|
|
}//createRequest
|
|
|
|
//static creator that accepts an associative array of parameters and
|
|
//sets only the setable attributes (does not overwrite constants)
|
|
public static function create($params){
|
|
//loop through each parameter
|
|
foreach($params as $param => $value){
|
|
switch($param){
|
|
case "openid_mode":
|
|
//check validity of mode
|
|
if($value=="checkid_setup" ||
|
|
$value=="id_res" ||
|
|
$value=="cancel")
|
|
$mode = $value;
|
|
else
|
|
$mode = "cancel";
|
|
continue 2;
|
|
|
|
case "openid_op_endpoint":
|
|
$op_endpoint = $value;
|
|
continue 2;
|
|
|
|
case "openid_response_nonce":
|
|
$response_nonce = $value;
|
|
continue 2;
|
|
|
|
case "openid_return_to":
|
|
$return_to = $value;
|
|
continue 2;
|
|
|
|
case "openid_realm":
|
|
$realm = $value;
|
|
continue 2;
|
|
|
|
case "openid_assoc_handle":
|
|
$assoc_handle = $value;
|
|
continue 2;
|
|
|
|
case "openid_claimed_id":
|
|
$claimed_id = $value;
|
|
continue 2;
|
|
|
|
case "openid_identity":
|
|
$claimed_id = $value;
|
|
continue 2;
|
|
|
|
case "openid_signed":
|
|
$signed = $value;
|
|
continue 2;
|
|
|
|
case "openid_sig":
|
|
$sig = $value;
|
|
continue 2;
|
|
|
|
case "openid_ext1_value_email":
|
|
$email = $value;
|
|
continue 2;
|
|
|
|
case "require_email":
|
|
$require_email = $value;
|
|
continue 2;
|
|
|
|
default:
|
|
continue 2;
|
|
}//switch param
|
|
}//loop through params
|
|
|
|
//if require email is not set, set it to false
|
|
if(!is_bool($require_email))
|
|
$require_email = false;
|
|
//if mode is not set, set to default for redirection
|
|
if(is_null($mode))
|
|
$mode = "checkid_setup";
|
|
//if return_to is not set and mode is checkid_setup, throw an error
|
|
if(is_null($return_to) && $mode=="checkid_setup")
|
|
throw new Exception("GoogleOpenID.create() needs parameter openid.return_to");
|
|
|
|
//return a new GoogleOpenID with the given parameters
|
|
return new GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email);
|
|
}//create
|
|
|
|
//creates and returns a GoogleOpenID from the $_GET variable
|
|
public static function getResponse(){
|
|
return GoogleOpenID::create($_GET);
|
|
}//getResponse
|
|
|
|
//fetches an association handle from google. association handles are valid
|
|
//for two weeks, so coders should do their best to save association handles
|
|
//externally and pass them to createRequest()
|
|
//NOTE: This function does not use encryption, but it SHOULD! At the time
|
|
//I wrote this I wanted it done fast, and I couldn't seem to find a good
|
|
//two-way SHA-1 or SHA-256 library for PHP. Encryption is not my thing, so
|
|
//it remains unimplemented.
|
|
public static function getAssociationHandle($endpoint=null){
|
|
//if no endpoint given
|
|
if(is_null($endpoint))
|
|
//fetch one from Google
|
|
$request_url = GoogleOpenID::getEndPoint();
|
|
//if endpoint given, set it
|
|
else
|
|
$request_url = $endpoint;
|
|
|
|
//append parameters (these never change)
|
|
$request_url .= "?openid.ns=".urlencode(GoogleOpenID::openid_ns);
|
|
$request_url .= "&openid.mode=associate";
|
|
$request_url .= "&openid.assoc_type=HMAC-SHA1";
|
|
$request_url .= "&openid.session_type=no-encryption";
|
|
|
|
//create a CURL session with the request URL
|
|
$c = curl_init($request_url);
|
|
|
|
//set a few options
|
|
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($c, CURLOPT_HEADER, false);
|
|
|
|
//get the contents of request URL
|
|
$request_contents = curl_exec($c);
|
|
|
|
//close the CURL session
|
|
curl_close($c);
|
|
|
|
//a handle to be returned
|
|
$assoc_handle = null;
|
|
|
|
//split the response into lines
|
|
$lines = explode("\n", $request_contents);
|
|
|
|
//loop through each line
|
|
foreach($lines as $line){
|
|
//if this line is assoc_handle
|
|
if(substr($line, 0, 13)=="assoc_handle:"){
|
|
//save the assoc handle
|
|
$assoc_handle = substr($line, 13);
|
|
//exit the loop
|
|
break;
|
|
}//if this line is assoc_handle
|
|
}//loop through lines
|
|
|
|
//return the handle
|
|
return $assoc_handle;
|
|
}//getAssociationHandle
|
|
|
|
//fetches an endpoint from Google
|
|
public static function getEndPoint(){
|
|
//fetch the request URL
|
|
$request_url = GoogleOpenID::google_discover_url;
|
|
|
|
//create a CURL session with the request URL
|
|
$c = curl_init($request_url);
|
|
|
|
//set a few options
|
|
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($c, CURLOPT_HEADER, false);
|
|
|
|
//fetch the contents of the request URL
|
|
$request_contents = curl_exec($c);
|
|
|
|
//close the CURL session
|
|
curl_close($c);
|
|
|
|
//create a DOM document so we can extract the URI element
|
|
$domdoc = new DOMDocument();
|
|
$domdoc->loadXML($request_contents);
|
|
|
|
//fetch the contents of the URI element
|
|
$uri = $domdoc->getElementsByTagName("URI");
|
|
$uri = $uri->item(0)->nodeValue;
|
|
|
|
//return the given URI
|
|
return $uri;
|
|
}//getEndPoint
|
|
|
|
//returns an associative array of all openid parameters for this openid
|
|
//session. the array contains all the GET attributes that would be sent
|
|
//or that have been recieved, meaning:
|
|
//
|
|
//if mode = "cancel" returns only the mode and ns attributes
|
|
//if mode = "id_res" returns all attributes that are not null
|
|
//if mode = "checkid_setup" returns only attributes that need to be sent
|
|
// in the HTTP request
|
|
public function getArray(){
|
|
//an associative array to return
|
|
$ret = array();
|
|
|
|
$ret['openid.ns'] = GoogleOpenID::openid_ns;
|
|
|
|
//if mode is cancel, return only ns and mode
|
|
if($this->mode=="cancel"){
|
|
$ret['openid.mode'] = "cancel";
|
|
return $ret;
|
|
}//if cancel
|
|
|
|
//set attributes that are returned for all cases
|
|
if(!is_null($this->claimed_id)){
|
|
$ret['openid.claimed_id'] = $this->claimed_id;
|
|
$ret['openid.identity'] = $this->claimed_id;
|
|
}
|
|
if(!is_null($this->return_to))
|
|
$ret['openid.return_to'] = $this->return_to;
|
|
if(!is_null($this->realm))
|
|
$ret['openid.realm'] = $this->realm;
|
|
if(!is_null($this->assoc_handle))
|
|
$ret['openid.assoc_handle'] = $this->assoc_handle;
|
|
if(!is_null($this->mode))
|
|
$ret['openid.mode'] = $this->mode;
|
|
|
|
//set attributes that are returned only if this is a request
|
|
//and if getting email is required OR if this is a response and the
|
|
//email is given
|
|
if(($this->mode=="checkid_setup" AND $this->require_email) OR
|
|
($this->mode=="id_res" AND !is_null($this->email))){
|
|
$ret['openid.ns.ext1'] = GoogleOpenID::openid_ns_ext1;
|
|
$ret['openid.ext1.mode'] = GoogleOpenID::openid_ext1_mode;
|
|
$ret['openid.ext1.type.email'] = GoogleOpenID::openid_ext1_type_email;
|
|
$ret['openid.ext1.required'] = GoogleOpenID::openid_ext1_required;
|
|
if(!is_null($this->email))
|
|
$ret['openid.ext1.value.email'] = $this->email;
|
|
}//if redirect and get email
|
|
|
|
//set attributes that are returned only if this is a response
|
|
if($this->mode=="id_res"){
|
|
$ret['openid.op_endpoint'] = $this->op_endpoint;
|
|
if(!is_null($this->response_nonce))
|
|
$ret['openid.response_nonce'] = $this->response_nonce;
|
|
if(!is_null($this->signed))
|
|
$ret['openid.signed'] = $this->signed;
|
|
if(!is_null($this->sig))
|
|
$ret['openid.sig'] = $this->sig;
|
|
}
|
|
|
|
//return the array
|
|
return $ret;
|
|
}//getArray
|
|
|
|
//sends a request to google and fetches the url to which google is asking
|
|
//us to redirect (unless the endpoint is already known, in which case the
|
|
//function simply returns it)
|
|
public function endPoint(){
|
|
//if we know the value of op_endpoint already
|
|
if(!is_null($this->op_endpoint))
|
|
return $this->op_endpoint;
|
|
|
|
//fetch the endpoint from Google
|
|
$endpoint = GoogleOpenID::getEndPoint();
|
|
|
|
//save it
|
|
$this->op_endpoint = $endpoint;
|
|
|
|
//return the endpoint
|
|
return $endpoint;
|
|
}//getedPoint
|
|
|
|
//returns the URL to which we should send a request (including all GET params)
|
|
public function getRequestURL(){
|
|
//get all parameters
|
|
$params = $this->getArray();
|
|
|
|
//the base URL
|
|
$url = $this->endPoint();
|
|
|
|
//flag indicating whether to set a '?' or an '&'
|
|
$first_attribute = true;
|
|
|
|
//loop through all params
|
|
foreach($params as $param => $value){
|
|
//if first attribute print a ?, else print a &
|
|
if($first_attribute){
|
|
$url .= "?";
|
|
$first_attribute = false;
|
|
} else {
|
|
$url .= "&";
|
|
}//else (not first attribute)
|
|
|
|
$url .= urlencode($param) . "=" . urlencode($value);
|
|
}//loop through params
|
|
|
|
//return the URL
|
|
return $url;
|
|
}//getRequestURL
|
|
|
|
//redirects the browser to the appropriate request URL
|
|
public function redirect(){
|
|
header("Location: ".$this->getRequestURL());
|
|
}//redirect
|
|
|
|
//returns true if the response was a success
|
|
public function success(){
|
|
return ($this->mode=="id_res");
|
|
}//success
|
|
|
|
//returns the identity given in the response
|
|
public function identity(){
|
|
if($this->mode!="id_res")
|
|
return null;
|
|
else
|
|
return $this->claimed_id;
|
|
}//identity
|
|
|
|
//returns the email given in the response
|
|
public function email(){
|
|
if($this->mode!="id_res")
|
|
return null;
|
|
else
|
|
return $this->email;
|
|
}//email
|
|
|
|
//returns the assoc_handle
|
|
public function assoc_handle(){
|
|
return $this->assoc_handle();
|
|
}//assoc_handle
|
|
}//class GoogleOpenID
|
|
?>
|