. **/ /* * The Yadis library is necessary for OpenID 2.0 specifications. * The default path assumes the library is located in the same directory * as the Dope OpenID class file. Feel free to change the path to this * library if necessary. */ require_once 'Services/Yadis/Yadis.php'; class Dope_OpenID { public $fields = array('required' => array(),'optional' => array()); public $arr_userinfo = array(); // An associative array of AX schema definitions private $arr_ax_types = array( 'nickname' => 'http://axschema.org/namePerson/friendly', 'email' => 'http://axschema.org/contact/email', 'fullname' => 'http://axschema.org/namePerson', 'dob' => 'http://axschema.org/birthDate', 'gender' => 'http://axschema.org/person/gender', 'postcode' => 'http://axschema.org/contact/postalCode/home', 'country' => 'http://axschema.org/contact/country/home', 'language' => 'http://axschema.org/pref/language', 'timezone' => 'http://axschema.org/pref/timezone', 'prefix' => 'http://axschema.org/namePerson/prefix', 'firstname' => 'http://axschema.org/namePerson/first', 'lastname' => 'http://axschema.org/namePerson/last', 'suffix' => 'http://axschema.org/namePerson/suffix' ); private $openid_url_identity; private $openid_ns; private $openid_version; private $error = array(); private $URLs = array(); // PHP 4 compatible constructor calls the PHP 5 constructor. public function Dope_OpenID($identity) { $this->__construct($identity); } // The PHP 5 constructor public function __construct($identity) { if ( ! $identity) { $this->errorStore('OPENID_NOIDENTITY','No identity passed to Dope OpenID constructor.'); return FALSE; } // cURL is required for Dope OpenID to work. if ( ! function_exists('curl_exec')) { die('Error: Dope OpenID requires the PHP cURL extension.'); } // Set user's identity. $this->setIdentity($identity); } public function setReturnURL($url) { $this->URLs['return'] = $url; } public function setTrustRoot($url) { $this->URLs['trust_root'] = $url; } public function setCancelURL($url) { $this->URLs['cancel'] = $url; } public function setRequiredInfo($fields) { if (is_array($fields)){ $this->fields['required'] = $fields; } else { $this->fields['required'][] = $fields; } } public function setOptionalInfo($fields) { if (is_array($fields)) { $this->fields['optional'] = $fields; } else { $this->fields['optional'][] = $fields; } } public function setPapePolicies($policies) { if (is_array($policies)) { $this->fields['pape_policies'] = $policies; } else { $this->fields['pape_policies'][] = $policies; } } public function setPapeMaxAuthAge($seconds){ // Numeric value greater than or equal to zero in seconds // How much time should the user be given to authenticate? if(preg_match("/^[1-9]+[0-9]*$/",$seconds)){ $this->fields['pape_max_auth_age'] = $seconds; } else { $this->errorStore('OPENID_MAXAUTHAGE','Max Auth Age must be a numeric value greater than or equal to zero in seconds.'); return FALSE; } } public function isError() { if ( ! empty($this->error)) { return TRUE; } else{ return FALSE; } } public function getError() { $the_error = $this->error; return array('code'=>$the_error[0],'description'=>$the_error[1]); } /* * Method to discover the OpenID Provider's endpoint location */ public function getOpenIDEndpoint() { //Try Yadis Protocol discovery first $http_response = array(); $fetcher = Services_Yadis_Yadis::getHTTPFetcher(); $yadis_object = Services_Yadis_Yadis::discover($this->openid_url_identity, $http_response, $fetcher); // Yadis object is returned if discovery is successful if($yadis_object != NULL) { $service_list = $yadis_object->services(); $service_types = $service_list[0]->getTypes(); $servers = $service_list[0]->getURIs(); $delegates = $service_list[0]->getElements('openid:Delegate'); } // Else try HTML discovery else { $response = $this->makeCURLRequest($this->openid_url_identity); list($servers, $delegates) = $this->parseHTML($response); } // If no servers were discovered by Yadis or by parsing HTML, error out if (empty($servers)){ $this->errorStore('OPENID_NOSERVERSFOUND'); return FALSE; } // If $service_type has at least one non-null character if (isset($service_types[0]) && ($service_types[0] != "")) { $this->setServiceType($service_types[0]); } // If $delegates has at least one non-null character if (isset($delegates[0]) && ($delegates[0] != "")) { $this->setIdentity($delegates[0]); } $this->setOpenIDEndpoint($servers[0]); return $servers[0]; } /* * Method to redirect user to their OpenID Provider's endpoint */ public function redirect() { $redirect_to = $this->getRedirectURL(); // If headers() have already been sent if (headers_sent()) { // PHP header() redirect won't work if headers already sent. // JavaScript redirect is pretty much only option in this case. echo ''; } // Else we can use PHP header() redirect else { header('Location: ' . $redirect_to); } } /* * Method to validate information with the OpenID Provider */ public function validateWithServer() { $params = array(); // Find keys that include dots and store them in an array preg_match_all("/([\w]+[\.])/",$_GET['openid_signed'],$arr_periods); $arr_periods = array_unique(array_shift($arr_periods)); // Duplicate the dot keys array, but replace the dot with an underscore $arr_underscores = preg_replace("/\./","_",$arr_periods); // The OpenID Provider returns a list of signed keys we need to validate $arr_get_signed_keys = explode(",",str_replace($arr_periods, $arr_underscores, $_GET['openid_signed'])); // Send back only the signed keys to confirm validity foreach($arr_get_signed_keys as $key) { $paramKey = str_replace($arr_underscores, $arr_periods, $key); $params["openid.$paramKey"] = urlencode($_GET["openid_$key"]); } // If we're using OpenID 2.0 specs, we must include these values also if($this->openid_version != "2.0"){ $params['openid.assoc_handle'] = urlencode($_GET['openid_assoc_handle']); $params['openid.signed'] = urlencode($_GET['openid_signed']); } $params['openid.sig'] = urlencode($_GET['openid_sig']); $params['openid.mode'] = "check_authentication"; $endpoint_url = $this->getOpenIDEndpoint(); if ($endpoint_url == FALSE) { return FALSE; } // Send the signed keys back to the OpenID Provider using cURL $response = $this->makeCURLRequest($endpoint_url,'POST',$params); $data = $this->splitResponse($response); // If the response is successful, OpenID Provider will return [is_valid => 'true'] if ($data['is_valid'] == "true") { return TRUE; }else{ return FALSE; } } /* * Method to filter through $_GET array for requested user info. * TODO: Add documentation. */ public function filterUserInfo($arr_get) { foreach($arr_get as $key => $value){ $trimmed_key = substr($key,strrpos($key,"_")+1); if(stristr($key, 'openid_ext1_value') && isset($value[1])) { $this->arr_userinfo[$trimmed_key] = $value; } if(stristr($key, 'openid_ax_value') && isset($value[1])) { $this->arr_userinfo[$trimmed_key] = $value; } if(stristr($key, 'sreg_') && array_key_exists($trimmed_key, $this->arr_ax_types)) { $this->arr_userinfo[$trimmed_key] = $value; } } return $this->arr_userinfo; } /* * Method to set the user's OpenID identity url. * As of OpenID 2.0, this identifier could be an XRI Identifier * TODO: Add XRI support. */ private function setIdentity($identity) { /* XRI support is not ready yet. $xriIdentifiers = array('=', '$', '!', '@', '+'); $xriProxy = 'http://xri.net/'; // Is this an XRI string? // Check for "xri://" prefix or XRI Global Constant Symbols if (stripos($identity, 'xri://') OR in_array($identity[0], $xriIdentifiers)){ // Attempts to convert an XRI into a URI by removing the "xri://" prefix and // appending the remainder to the URI of an XRI proxy such as "http://xri.net" if (stripos($identity, 'xri://') == 0) { if (stripos($identity, 'xri://$ip*') == 0) { $identity = substr($identity, 10); } elseif (stripos($identity, 'xri://$dns*') == 0) { $identity = substr($identity, 11); } else { $identity = substr($identity, 6); } } $identity = $xriProxy.$identity; }*/ // Append "http://" to the identity string if not already present. if ((stripos($identity, 'http://') === FALSE) && (stripos($identity, 'https://') === FALSE)) { $identity = 'http://'.$identity; } // Google is not publishing its XRDS document yet, so the OpenID // endpoint must be set manually for now. if (stripos($identity, 'gmail') OR stripos($identity, 'google')) { $identity = "https://www.google.com/accounts/o8/id"; } $this->openid_url_identity = $identity; } private function getIdentity() { // Get Identity return $this->openid_url_identity; } /* * Method to make cURL request. */ private function makeCURLRequest($url, $method="GET", $params = "") { if (is_array($params)) { $params = $this->createQueryString($params); } $curl = curl_init($url . ($method == "GET" && $params != "" ? "?" . $params : "")); //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE); curl_setopt($curl, CURLOPT_HEADER, FALSE); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($curl, CURLOPT_HTTPGET, ($method == "GET")); curl_setopt($curl, CURLOPT_POST, ($method == "POST")); curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); if ($method == "POST") { curl_setopt($curl, CURLOPT_POSTFIELDS, $params); } $response = curl_exec($curl); if (curl_errno($curl) == 0) { $response; } else { $this->errorStore('OPENID_CURL', curl_error($curl)); } return $response; } private function parseHTML($content) { $ret = array(); // Get details of their OpenID server and (optional) delegate preg_match_all('/]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1); preg_match_all('/]*rel=[\'"]openid2.provider[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2); preg_match_all('/]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches3); preg_match_all('/]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid2.provider[\'"][^>]*\/?>/i', $content, $matches4); $servers = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]); preg_match_all('/]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1); preg_match_all('/]*rel=[\'"]openid2.local_id[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2); preg_match_all('/]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches3); preg_match_all('/]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid2.local_id[\'"][^>]*\/?>/i', $content, $matches4); $delegates = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]); $ret = array($servers, $delegates); return $ret; } private function splitResponse($response) { $r = array(); $response = explode("\n", $response); foreach($response as $line) { $line = trim($line); if ($line != "") { list($key, $value) = explode(":", $line, 2); $r[trim($key)] = trim($value); } } return $r; } private function createQueryString($array_params) { if ( ! is_array($array_params)) { return FALSE; } $query = ""; foreach($array_params as $key => $value){ $query .= $key . "=" . $value . "&"; } return $query; } private function setOpenIDEndpoint($url) { $this->URLs['openid_server'] = $url; } private function setServiceType($url) { /* * Hopefully the provider is using OpenID 2.0 but let's check * the protocol version in order to handle backwards compatibility. * Probably not the most efficient method, but it works for now. */ if (stristr($url, "2.0")) { $ns = "http://specs.openid.net/auth/2.0"; $version = "2.0"; } else if (stristr($url, "1.1")) { $ns = "http://openid.net/signon/1.1"; $version = "1.1"; } else { $ns = "http://openid.net/signon/1.0"; $version = "1.0"; } $this->openid_ns = $ns; $this->openid_version = $version; } function getRedirectURL() { $params = array(); $params['openid.return_to'] = urlencode($this->URLs['return']); $params['openid.identity'] = urlencode($this->openid_url_identity); if($this->openid_version == "2.0"){ $params['openid.ns'] = urlencode($this->openid_ns); $params['openid.claimed_id'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select"); $params['openid.identity'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select"); $params['openid.realm'] = urlencode($this->URLs['trust_root']); } else { $params['openid.trust_root'] = urlencode($this->URLs['trust_root']); } $params['openid.mode'] = 'checkid_setup'; /** * Handle user attribute requests. */ $info_request = FALSE; // User Info Request: Setup if (isset($this->fields['required']) OR isset($this->fields['optional'])) { $params['openid.ns.ax'] = "http://openid.net/srv/ax/1.0"; $params['openid.ax.mode'] = "fetch_request"; $params['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1"; $info_request = TRUE; } // MyOpenID.com is using an outdated AX schema URI if (stristr($this->URLs['openid_server'], 'myopenid.com') && $info_request == TRUE) { $this->arr_ax_types = preg_replace("/axschema.org/","schema.openid.net",$this->arr_ax_types); } // If we're requesting user info from Google, it MUST be specified as "required" // Will not work otherwise. if (stristr($this->URLs['openid_server'], 'google.com') && $info_request == TRUE) { $this->fields['required'] = array_unique(array_merge($this->fields['optional'], $this->fields['required'])); $this->fields['optional'] = array(); } // User Info Request: Required data if (isset($this->fields['required']) && ( ! empty($this->fields['required']))) { // Set required params for Attribute Exchange (AX) protocol $params['openid.ax.required'] = implode(',',$this->fields['required']); foreach($this->fields['required'] as $field) { if(array_key_exists($field,$this->arr_ax_types)) { $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]); } } // Set required params for Simple Registration (SREG) protocol $params['openid.sreg.required'] = implode(',',$this->fields['required']); } // User Info Request: Optional data if (isset($this->fields['optional']) && ( ! empty($this->fields['optional']))) { // Set optional params for Attribute Exchange (AX) protocol $params['openid.ax.if_available'] = implode(',',$this->fields['optional']); foreach($this->fields['optional'] as $field) { if(array_key_exists($field,$this->arr_ax_types)) { $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]); } } // Set optional params for Simple Registration (SREG) protocol $params['openid.sreg.optional'] = implode(',',$this->fields['optional']); } // Add PAPE params if exists if (isset($this->fields['pape_policies']) && ( ! empty($this->fields['pape_policies']))) { $params['openid.ns.pape'] = "http://specs.openid.net/extensions/pape/1.0"; $params['openid.pape.preferred_auth_policies'] = urlencode(implode(' ',$this->fields['pape_policies'])); if($this->fields['pape_max_auth_age']) { $params['openid.pape.max_auth_age'] = $this->fields['pape_max_auth_age']; } } $urlJoiner = (strstr($this->URLs['openid_server'], "?")) ? "&" : "?"; return $this->URLs['openid_server'] . $urlJoiner . $this->createQueryString($params); } private function errorStore($code, $desc = NULL) { $errs['OPENID_NOSERVERSFOUND'] = 'Cannot find OpenID Server TAG on Identity page.'; if ($desc == NULL){ $desc = $errs[$code]; } $this->error = array($code,$desc); } } ?>