• Main Page
  • Related Pages
  • Namespaces
  • Data Structures
  • Files
  • Examples
  • File List
  • Globals

cms/openid/class.dopeopenid.php

Go to the documentation of this file.
00001 <?php
00026 /*
00027 * The Yadis library is necessary for OpenID 2.0 specifications.
00028 * The default path assumes the library is located in the same directory
00029 * as the Dope OpenID class file. Feel free to change the path to this 
00030 * library if necessary.
00031 */
00032 require_once 'Services/Yadis/Yadis.php';
00033 
00034 
00035 class Dope_OpenID
00036 {
00037         public $fields = array('required' => array(),'optional' => array());
00038 
00039         public $arr_userinfo = array();
00040         
00041         // An associative array of AX schema definitions
00042         private $arr_ax_types = array(
00043                                                 'nickname'  => 'http://axschema.org/namePerson/friendly',
00044                                                 'email'     => 'http://axschema.org/contact/email',
00045                                                 'fullname'  => 'http://axschema.org/namePerson',
00046                                                 'dob'       => 'http://axschema.org/birthDate',
00047                                                 'gender'    => 'http://axschema.org/person/gender',
00048                                                 'postcode'  => 'http://axschema.org/contact/postalCode/home',
00049                                                 'country'   => 'http://axschema.org/contact/country/home',
00050                                                 'language'  => 'http://axschema.org/pref/language',
00051                                                 'timezone'  => 'http://axschema.org/pref/timezone',
00052                                                 'prefix'    => 'http://axschema.org/namePerson/prefix',
00053                                                 'firstname' => 'http://axschema.org/namePerson/first',
00054                                                 'lastname'  => 'http://axschema.org/namePerson/last',
00055                                                 'suffix'    => 'http://axschema.org/namePerson/suffix'
00056                         );
00057         
00058         private $openid_url_identity;
00059         private $openid_ns;
00060     private $openid_version;
00061         
00062         private $error = array();
00063         private $URLs  = array();
00064         
00065         // PHP 4 compatible constructor calls the PHP 5 constructor.
00066         public function Dope_OpenID($identity)
00067         {
00068                 $this->__construct($identity);
00069         }
00070         
00071         // The PHP 5 constructor
00072         public function __construct($identity)
00073         {
00074                 if ( ! $identity) {
00075                         $this->errorStore('OPENID_NOIDENTITY','No identity passed to Dope OpenID constructor.');
00076                         return FALSE;
00077                 }
00078                 
00079                 // cURL is required for Dope OpenID to work.
00080                 if ( ! function_exists('curl_exec')) {
00081                         die('Error: Dope OpenID requires the PHP cURL extension.');
00082                 }
00083                 
00084                 // Set user's identity.
00085                 $this->setIdentity($identity);
00086         }
00087         
00088         public function setReturnURL($url)
00089         {
00090                 $this->URLs['return'] = $url;
00091         }
00092         
00093         public function setTrustRoot($url)
00094         {
00095                 $this->URLs['trust_root'] = $url;
00096         }
00097         
00098         public function setCancelURL($url)
00099         {
00100                 $this->URLs['cancel'] = $url;
00101         }
00102         
00103         public function setRequiredInfo($fields)
00104         {
00105                 if (is_array($fields)){
00106                         $this->fields['required'] = $fields;
00107                 }
00108                 else {
00109                         $this->fields['required'][] = $fields;
00110                 }
00111         }
00112     
00113         public function setOptionalInfo($fields)
00114         {
00115                 if (is_array($fields)) {
00116                         $this->fields['optional'] = $fields;
00117                 }
00118                 else {
00119                         $this->fields['optional'][] = $fields;
00120                 }
00121         }
00122         
00123         public function setPapePolicies($policies)
00124         {
00125                 if (is_array($policies)) {
00126                         $this->fields['pape_policies'] = $policies;
00127                 }
00128                 else {
00129                         $this->fields['pape_policies'][] = $policies;
00130                 }
00131         }
00132         
00133         public function setPapeMaxAuthAge($seconds){
00134             // Numeric value greater than or equal to zero in seconds
00135             // How much time should the user be given to authenticate?
00136             if(preg_match("/^[1-9]+[0-9]*$/",$seconds)){
00137                 $this->fields['pape_max_auth_age'] = $seconds;
00138             }
00139             else {
00140                 $this->errorStore('OPENID_MAXAUTHAGE','Max Auth Age must be a numeric value greater than or equal to zero in seconds.');
00141                         return FALSE;
00142             }
00143         }
00144         
00145         public function isError()
00146         {
00147                 if ( ! empty($this->error)) {
00148                         return TRUE;
00149                 }
00150                 else{
00151                         return FALSE;
00152                 }
00153         }
00154         
00155         public function getError()
00156         {
00157                 $the_error = $this->error;
00158                 return array('code'=>$the_error[0],'description'=>$the_error[1]);
00159         }
00160         
00161         /*
00162         * Method to discover the OpenID Provider's endpoint location
00163         */
00164         public function getOpenIDEndpoint()
00165         {
00166                 //Try Yadis Protocol discovery first
00167                 $http_response = array();
00168                 $fetcher = Services_Yadis_Yadis::getHTTPFetcher();
00169                 $yadis_object = Services_Yadis_Yadis::discover($this->openid_url_identity, $http_response, $fetcher);
00170                 
00171                 // Yadis object is returned if discovery is successful
00172                 if($yadis_object != NULL) {
00173                         
00174                         $service_list  = $yadis_object->services();
00175                         $service_types = $service_list[0]->getTypes();
00176                         
00177                         $servers   = $service_list[0]->getURIs();
00178                         $delegates = $service_list[0]->getElements('openid:Delegate');
00179                 
00180                 }
00181                 // Else try HTML discovery
00182                 else { 
00183                         $response = $this->makeCURLRequest($this->openid_url_identity);
00184                         list($servers, $delegates) = $this->parseHTML($response);
00185                 }
00186                 
00187                 // If no servers were discovered by Yadis or by parsing HTML, error out
00188                 if (empty($servers)){
00189                         $this->errorStore('OPENID_NOSERVERSFOUND');
00190                         return FALSE;
00191                 }
00192                 
00193                 // If $service_type has at least one non-null character
00194                 if (isset($service_types[0]) && ($service_types[0] != "")) {
00195                         $this->setServiceType($service_types[0]);
00196                 }
00197                 
00198                 // If $delegates has at least one non-null character
00199                 if (isset($delegates[0]) && ($delegates[0] != "")) {
00200                         $this->setIdentity($delegates[0]);
00201                 }
00202                 
00203                 $this->setOpenIDEndpoint($servers[0]);
00204                 
00205                 return $servers[0];
00206         }
00207         
00208         /*
00209         * Method to redirect user to their OpenID Provider's endpoint
00210         */
00211         public function redirect()
00212         {
00213         $redirect_to = $this->getRedirectURL();
00214         // If headers() have already been sent
00215         if (headers_sent()) {
00216                 // PHP header() redirect won't work if headers already sent.
00217                 // JavaScript redirect is pretty much only option in this case.
00218             echo '<script language="JavaScript" type="text/javascript">window.location=\'';
00219             echo $redirect_to;
00220             echo '\';</script>';
00221         }
00222         // Else we can use PHP header() redirect
00223         else {
00224             header('Location: ' . $redirect_to);
00225         }
00226     }
00227     
00228     /*
00229     * Method to validate information with the OpenID Provider
00230     */
00231     public function validateWithServer()
00232     {
00233         
00234                 $params = array();
00235                 
00236                 // Find keys that include dots and store them in an array
00237                 preg_match_all("/([\w]+[\.])/",$_GET['openid_signed'],$arr_periods);
00238                 $arr_periods = array_unique(array_shift($arr_periods));
00239                 
00240                 // Duplicate the dot keys array, but replace the dot with an underscore
00241                 $arr_underscores = preg_replace("/\./","_",$arr_periods);
00242                 
00243                 // The OpenID Provider returns a list of signed keys we need to validate
00244                 $arr_get_signed_keys = explode(",",str_replace($arr_periods, $arr_underscores, $_GET['openid_signed']));
00245                 
00246                 // Send back only the signed keys to confirm validity
00247                 foreach($arr_get_signed_keys as $key) {
00248                         $paramKey = str_replace($arr_underscores, $arr_periods, $key);
00249                         $params["openid.$paramKey"] = urlencode($_GET["openid_$key"]);
00250                 }
00251                 
00252                 // If we're using OpenID 2.0 specs, we must include these values also
00253                 if($this->openid_version != "2.0"){
00254                         $params['openid.assoc_handle'] = urlencode($_GET['openid_assoc_handle']);
00255                         $params['openid.signed']       = urlencode($_GET['openid_signed']);
00256                 }
00257                 
00258                 $params['openid.sig']  = urlencode($_GET['openid_sig']);
00259                 $params['openid.mode'] = "check_authentication";
00260                 
00261                 $endpoint_url = $this->getOpenIDEndpoint();
00262                 
00263                 if ($endpoint_url == FALSE) {
00264                         return FALSE;
00265                 }
00266                 
00267                 // Send the signed keys back to the OpenID Provider using cURL
00268                 $response = $this->makeCURLRequest($endpoint_url,'POST',$params);
00269                 $data = $this->splitResponse($response);
00270                 
00271                 // If the response is successful, OpenID Provider will return [is_valid => 'true']
00272                 if ($data['is_valid'] == "true") {
00273                         return TRUE;
00274                 }else{
00275                         return FALSE;
00276                 }
00277         }
00278         
00279         /*
00280         * Method to filter through $_GET array for requested user info.
00281         * TODO: Add documentation.
00282         */
00283         public function filterUserInfo($arr_get)
00284         {
00285                 foreach($arr_get as $key => $value){
00286                         $trimmed_key = substr($key,strrpos($key,"_")+1);
00287                         if(stristr($key, 'openid_ext1_value') && isset($value[1])) {
00288                                 $this->arr_userinfo[$trimmed_key] = $value;
00289                         }
00290                         if( (stristr($key, 'sreg_') || stristr($key, 'ax_value_')) &&
00291                             array_key_exists($trimmed_key, $this->arr_ax_types)) {
00292                                 $this->arr_userinfo[$trimmed_key] = $value;
00293                         }
00294                 }
00295                 return $this->arr_userinfo;
00296         }
00297         
00298         /*
00299         * Method to set the user's OpenID identity url.
00300         * As of OpenID 2.0, this identifier could be an XRI Identifier
00301         * TODO: Add XRI support.
00302         */
00303         private function setIdentity($identity)
00304         {
00305                 /* XRI support is not ready yet.
00306                 $xriIdentifiers = array('=', '$', '!', '@', '+');
00307                 $xriProxy = 'http://xri.net/';
00308 
00309                 // Is this an XRI string?
00310                 // Check for "xri://" prefix or XRI Global Constant Symbols
00311                 if (stripos($identity, 'xri://') OR in_array($identity[0], $xriIdentifiers)){   
00312                         // Attempts to convert an XRI into a URI by removing the "xri://" prefix and
00313                         // appending the remainder to the URI of an XRI proxy such as "http://xri.net"
00314                         if (stripos($identity, 'xri://') == 0) {
00315                                 if (stripos($identity, 'xri://$ip*') == 0) {
00316                                         $identity = substr($identity, 10);
00317                                 } elseif (stripos($identity, 'xri://$dns*') == 0) {
00318                                         $identity = substr($identity, 11);
00319                                 } else {
00320                                         $identity = substr($identity, 6);
00321                                 }
00322                         }
00323                         $identity = $xriProxy.$identity;
00324                 }*/
00325                 
00326                 // Append "http://" to the identity string if not already present.
00327                 if ((stripos($identity, 'http://') === FALSE) && 
00328                         (stripos($identity, 'https://') === FALSE)) {
00329                                 $identity = 'http://'.$identity;
00330                 }
00331                 
00332                 // Google is not publishing its XRDS document yet, so the OpenID
00333                 // endpoint must be set manually for now.
00334                 if (stripos($identity, 'gmail') OR stripos($identity, 'google')) {
00335                         $identity = "https://www.google.com/accounts/o8/id";
00336                 }
00337                 $this->openid_url_identity = $identity;
00338         }
00339         
00340         private function getIdentity()
00341         {       // Get Identity
00342                 return $this->openid_url_identity;
00343         }
00344         
00345         /*
00346         * Method to make cURL request.
00347         */
00348         private function makeCURLRequest($url, $method="GET", $params = "")
00349         {
00350                 if (is_array($params)) {
00351                         $params = $this->createQueryString($params);
00352                 }
00353                 
00354                 $curl = curl_init($url . ($method == "GET" && $params != "" ? "?" . $params : ""));
00355                 
00356                 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
00357                 curl_setopt($curl, CURLOPT_HEADER, FALSE);
00358                 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
00359                 curl_setopt($curl, CURLOPT_HTTPGET, ($method == "GET"));
00360                 curl_setopt($curl, CURLOPT_POST, ($method == "POST"));
00361                 curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
00362                 curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
00363                 if ($method == "POST") {
00364                         curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
00365                 }
00366                 
00367                 $response = curl_exec($curl);
00368                 
00369                 if (curl_errno($curl) == 0) {
00370                         $response;
00371                 }
00372                 else {
00373                         $this->errorStore('OPENID_CURL', curl_error($curl));
00374                 }
00375                 
00376                 return $response;
00377         }
00378         
00379         private function parseHTML($content)
00380         {
00381                 $ret = array();
00382                 
00383                 // Get details of their OpenID server and (optional) delegate
00384                 preg_match_all('/<link[^>]*rel=[\'"]openid.server[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
00385                 preg_match_all('/<link[^>]*rel=[\'"]openid2.provider[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2);
00386                 preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid.server[\'"][^>]*\/?>/i', $content, $matches3);
00387                 preg_match_all('/<link[^>]*href=\'"([^\'"]+)[\'"][^>]*rel=[\'"]openid2.provider[\'"][^>]*\/?>/i', $content, $matches4);
00388                 
00389                 $servers = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]);
00390                 
00391                 preg_match_all('/<link[^>]*rel=[\'"]openid.delegate[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches1);
00392                 preg_match_all('/<link[^>]*rel=[\'"]openid2.local_id[\'"][^>]*href=[\'"]([^\'"]+)[\'"][^>]*\/?>/i', $content, $matches2);
00393                 preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid.delegate[\'"][^>]*\/?>/i', $content, $matches3);
00394                 preg_match_all('/<link[^>]*href=[\'"]([^\'"]+)[\'"][^>]*rel=[\'"]openid2.local_id[\'"][^>]*\/?>/i', $content, $matches4);
00395                 
00396                 $delegates = array_merge($matches1[1], $matches2[1], $matches3[1], $matches4[1]);
00397                 
00398                 $ret = array($servers, $delegates);
00399                 return $ret;
00400         }
00401         
00402         private function splitResponse($response)
00403         {
00404                 $r = array();
00405                 $response = explode("\n", $response);
00406                 foreach($response as $line) {
00407                         $line = trim($line);
00408                         if ($line != "") {
00409                                 list($key, $value) = explode(":", $line, 2);
00410                                 $r[trim($key)] = trim($value);
00411                         }
00412                 }
00413                 return $r;
00414         }
00415         
00416         private function createQueryString($array_params)
00417         {
00418                 if ( ! is_array($array_params)) {
00419                         return FALSE;
00420                 }
00421                 
00422                 $query = "";
00423                 
00424                 foreach($array_params as $key => $value){
00425                         $query .= $key . "=" . $value . "&";
00426                 }
00427                 
00428                 return $query;
00429         }
00430         
00431         private function setOpenIDEndpoint($url)
00432         {
00433                 $this->URLs['openid_server'] = $url;
00434         }
00435     
00436     private function setServiceType($url)
00437     {
00438         /* 
00439         * Hopefully the provider is using OpenID 2.0 but let's check
00440         * the protocol version in order to handle backwards compatibility.
00441         * Probably not the most efficient method, but it works for now.
00442         */
00443         if (stristr($url, "2.0")) {
00444             $ns = "http://specs.openid.net/auth/2.0";
00445             $version = "2.0";
00446         }
00447         else if (stristr($url, "1.1")) {
00448             $ns = "http://openid.net/signon/1.1";
00449             $version = "1.1";
00450         }
00451         else {
00452             $ns = "http://openid.net/signon/1.0";
00453             $version = "1.0";
00454         }
00455         $this->openid_ns      = $ns;
00456         $this->openid_version = $version;
00457     }
00458     
00459     function getRedirectURL()
00460     {
00461         $params = array();
00462         
00463         $params['openid.return_to'] = urlencode($this->URLs['return']);
00464         $params['openid.identity']  = urlencode($this->openid_url_identity);
00465         
00466         if($this->openid_version == "2.0"){
00467                 $params['openid.ns']         = urlencode($this->openid_ns);
00468                 $params['openid.claimed_id'] = urlencode("http://specs.openid.net/auth/2.0/identifier_select");
00469                 $params['openid.identity']   = urlencode("http://specs.openid.net/auth/2.0/identifier_select");
00470                 $params['openid.realm']      = urlencode($this->URLs['trust_root']);
00471         }
00472         else {
00473                 $params['openid.trust_root'] = urlencode($this->URLs['trust_root']);
00474         }
00475         
00476         $params['openid.mode'] = 'checkid_setup';
00477         
00481                 $info_request = FALSE;
00482         
00483         // User Info Request: Setup
00484         if (isset($this->fields['required']) OR isset($this->fields['optional'])) {             
00485                 $params['openid.ns.ax']   = "http://openid.net/srv/ax/1.0";
00486                 $params['openid.ax.mode'] = "fetch_request";
00487                 $params['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
00488 
00489                 $info_request = TRUE;
00490         }
00491         
00492         // MyOpenID.com is using an outdated AX schema URI
00493         if (stristr($this->URLs['openid_server'], 'myopenid.com') && $info_request == TRUE) {
00494                 $this->arr_ax_types = preg_replace("/axschema.org/","schema.openid.net",$this->arr_ax_types);
00495         }
00496         
00497         // If we're requesting user info from Google, it MUST be specified as "required"
00498         // Will not work otherwise.
00499         if (stristr($this->URLs['openid_server'], 'google.com') && $info_request == TRUE) {
00500                 $this->fields['required'] = array_unique(array_merge($this->fields['optional'], $this->fields['required']));
00501                 $this->fields['optional'] = array();
00502         }
00503         
00504         // User Info Request: Required data
00505         if (isset($this->fields['required']) && ( ! empty($this->fields['required']))) {
00506                 
00507                 // Set required params for Attribute Exchange (AX) protocol
00508                 $params['openid.ax.required']   = implode(',',$this->fields['required']);
00509                 
00510                 foreach($this->fields['required'] as $field) {
00511                         if(array_key_exists($field,$this->arr_ax_types)) {
00512                                 $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]);
00513                         }
00514                 }
00515                 
00516                 // Set required params for Simple Registration (SREG) protocol
00517                 $params['openid.sreg.required'] = implode(',',$this->fields['required']);
00518         }
00519         
00520         // User Info Request: Optional data
00521         if (isset($this->fields['optional']) && ( ! empty($this->fields['optional']))) {
00522                 // Set optional params for Attribute Exchange (AX) protocol
00523                 $params['openid.ax.if_available'] = implode(',',$this->fields['optional']);
00524                 
00525                 foreach($this->fields['optional'] as $field) {
00526                         if(array_key_exists($field,$this->arr_ax_types)) {
00527                                 $params["openid.ax.type.$field"] = urlencode($this->arr_ax_types[$field]);
00528                         }
00529                 }
00530                 // Set optional params for Simple Registration (SREG) protocol
00531                 $params['openid.sreg.optional'] = implode(',',$this->fields['optional']);
00532         }
00533         
00534         // Add PAPE params if exists
00535         if (isset($this->fields['pape_policies']) && ( ! empty($this->fields['pape_policies']))) {
00536                 $params['openid.ns.pape'] = "http://specs.openid.net/extensions/pape/1.0";
00537                 $params['openid.pape.preferred_auth_policies'] = urlencode(implode(' ',$this->fields['pape_policies']));
00538                 
00539                 if($this->fields['pape_max_auth_age']) {
00540                         $params['openid.pape.max_auth_age'] = $this->fields['pape_max_auth_age'];
00541                 }
00542         }
00543         
00544         $urlJoiner = (strstr($this->URLs['openid_server'], "?")) ? "&" : "?";
00545         
00546         return $this->URLs['openid_server'] . $urlJoiner . $this->createQueryString($params);
00547         
00548     }
00549         
00550         private function errorStore($code, $desc =      NULL)
00551         {
00552                 $errs['OPENID_NOSERVERSFOUND'] = 'Cannot find OpenID Server TAG on Identity page.';
00553                 
00554                 if ($desc == NULL){
00555                         $desc = $errs[$code];
00556                 }
00557                 
00558                 $this->error = array($code,$desc);
00559         }
00560 }
00561 
00562 ?>

Generated on Sun Jan 2 2011 04:55:32 for Pragyan CMS by  doxygen 1.7.1