client = new Client(); $this->_storage = new StorageFile(); } /** * Fonction login * * Return true ou false * > Ne retourne rien si true car redirige vers l'adresse du serveur OID * */ //FIXME : le paramètre immediate ? // A vérifier mais ça doit permettre de passer directement le mot de passe. Il reste plus qu'à trouver le nom de la variable mot de passe. function login($id, $immediate = false) { // L'original retourne la fonction checkId, avec le parametre immediate = true // Je ne comprends pas l'utilité, je fusionne les deux pour l'instant // FIXME : si pas de comportement étrange, valider. //Tests et arrêt si non validé : //Normaliser (traite si XRI ou URL, normalize URL) //FIXME : voir avec JP pour équivalent dans framework if (!$this->normalize($id)) { return false; } //Discovery // Récupérer les informations sur le serveur OPEN ID /* FIXME : ca change la valeur de l'id !!! if (!$this->_discovery($id, $server, $version)) { trigger_error('Discovery failed'); return false; }*/ $retour_url = $this->client->consulter($id); //Le retour contient les balises suivantes : /* * * * */ $metaServeur = $this->verifierVersion($retour_url); //TODO : Voir avec JP : la classe client ne permet pas de vérifer le statut ?? if ($retour_url === false) { trigger_error('L\'adresse $id est inacessible', E_USER_ERROR); return false; } if ($metaServeur === false) { return false; } if (!$this->_associate($metaServeur['serveur'], $metaServeur['version'])) { trigger_error('Impossible de s\'associer avec le serveur'); } /*TODO : fonctionnement différent if (!$this->_getAssociation( $server, $handle, $macFunc, $secret, $expires)) { /* Use dumb mode * unset($handle); unset($macFunc); unset($secret); unset($expires);* }*/ //on a la version, l'adresse du serveur et le realId si c'est une 2.0 dans metaServeur if (isset($metaServeur['realId'])) { $id = $metaServeur['realId']; } //Associate //getAssociation //Organisation des paramètres : $params = array(); if ($metaServeur['version'] >= 2.0) { $params['openid.ns'] = self::NS_2_0; } $params['openid.mode'] = $immediate ? 'checkid_immediate' : 'checkid_setup'; $params['openid.identity'] = $id; //FIXME : Ex : $params['openid.claimed_id'] = $claimedId; > jvois pas l'intéret $params['openid.claimed_id'] = $id; /* * TODO : gérer les sessions et namespace * if ($metaServeur['version'] <= 2.0) { if ($this->_session !== null) { $this->_session->identity = $id; $this->_session->claimed_id = $claimedId; } else if (defined('SID')) { $_SESSION["zend_openid"] = array( "identity" => $id, "claimed_id" => $claimedId); } else { require_once "Zend/Session/Namespace.php"; $this->_session = new Zend_Session_Namespace("zend_openid"); $this->_session->identity = $id; $this->_session->claimed_id = $claimedId; } }*/ if (isset($handle)) { $params['openid.assoc_handle'] = $handle; } //FIXME : $params['openid.return_to'] = $this->absoluteUrl($returnTo); $params['openid.return_to'] = $this->absoluteUrl(null); if (empty($root)) { $root = $this->selfUrl(); if ($root[strlen($root)-1] != '/') { $root = dirname($root); } } if ($metaServeur['version'] >= 2.0) { $params['openid.realm'] = $root; } else { $params['openid.trust_root'] = $root; } /*FIXME :: if (!Zend_OpenId_Extension::forAll($extensions, 'prepareRequest', $params)) { $this->_setError("Extension::prepareRequest failure"); return false; }*/ $this->redirect($metaServeur['serveur'], $params); return true; //Renvoyer vers l'url } /** * Verifies authentication response from OpenID server. * * This is the second step of OpenID authentication process. * The function returns true on successful authentication and false on * failure. * * @param array $params HTTP query data from OpenID server * @param string &$identity this argument is set to end-user's claimed * identifier or OpenID provider local identifier. * @param mixed $extensions extension object or array of extensions objects * @return bool */ public function verify($params, &$identity = "", $extensions = null) { if (isset($params['openid_ns']) && $params['openid_ns'] == $this->NS_2_0) { $version = 2.0; } if (isset($params["openid_claimed_id"])) { $identity = $params["openid_claimed_id"]; } else if (isset($params["openid_identity"])){ $identity = $params["openid_identity"]; } else { $identity = ""; } if ($version < 2.0 && !isset($params["openid_claimed_id"])) { if ($this->_session !== null) { if ($this->_session->identity === $identity) { $identity = $this->_session->claimed_id; } } else if (defined('SID')) { if (isset($_SESSION["zend_openid"]["identity"]) && isset($_SESSION["zend_openid"]["claimed_id"]) && $_SESSION["zend_openid"]["identity"] === $identity) { $identity = $_SESSION["zend_openid"]["claimed_id"]; } } else { require_once "Zend/Session/Namespace.php"; $this->_session = new Zend_Session_Namespace("zend_openid"); if ($this->_session->identity === $identity) { $identity = $this->_session->claimed_id; } } } if (empty($params['openid_mode'])) { $this->_setError("Missing openid.mode"); return false; } if (empty($params['openid_return_to'])) { $this->_setError("Missing openid.return_to"); return false; } if (empty($params['openid_signed'])) { $this->_setError("Missing openid.signed"); return false; } if (empty($params['openid_sig'])) { $this->_setError("Missing openid.sig"); return false; } if ($params['openid_mode'] != 'id_res') { $this->_setError("Wrong openid.mode '".$params['openid_mode']."' != 'id_res'"); return false; } if (empty($params['openid_assoc_handle'])) { $this->_setError("Missing openid.assoc_handle"); return false; } if ($params['openid_return_to'] != $this->selfUrl()) { /* Ignore query part in openid.return_to */ $pos = strpos($params['openid_return_to'], '?'); if ($pos === false || SUBSTR($params['openid_return_to'], 0 , $pos) != $this->selfUrl()) { /*$this->_setError("Wrong openid.return_to '". $params['openid_return_to']."' != '" . $this->selfUrl() ."'");*/ trigger_error('Wrong openid.return_to', E_USER_ERROR); return false; } } if ($version >= 2.0) { if (empty($params['openid_response_nonce'])) { trigger_error('Missing openid.response_nonce', E_USER_ERROR); return false; } if (empty($params['openid_op_endpoint'])) { trigger_error('Missing openid.op_endpoint', E_USER_ERROR); return false; /* OpenID 2.0 (11.3) Checking the Nonce */ } else if (!$this->_storage->isUniqueNonce($params['openid_op_endpoint'], $params['openid_response_nonce'])) { trigger_error('Duplicate openid.response_nonce', E_USER_ERROR); return false; } } if (!empty($params['openid_invalidate_handle'])) { if ($this->_storage->getAssociationByHandle( $params['openid_invalidate_handle'], $url, $macFunc, $secret, $expires)) { $this->_storage->delAssociation($url); } } if ($this->_storage->getAssociationByHandle( $params['openid_assoc_handle'], $url, $macFunc, $secret, $expires)) { $signed = explode(',', $params['openid_signed']); $data = ''; foreach ($signed as $key) { $data .= $key . ':' . $params['openid_' . strtr($key,'.','_')] . "\n"; } if (base64_decode($params['openid_sig']) == Zend_OpenId::hashHmac($macFunc, $data, $secret)) { /* * FIXME dépendance je sais pas pour quoi : a voir : * if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) { $this->_setError("Extension::parseResponse failure"); return false; }*/ /* OpenID 2.0 (11.2) Verifying Discovered Information */ if (isset($params['openid_claimed_id'])) { $id = $params['openid_claimed_id']; if (!$this->normalize($id)) { $this->_setError("Normalization failed"); return false; } else if (!$this->_discovery($id, $discovered_server, $discovered_version)) { $this->_setError("Discovery failed: " . $this->getError()); return false; } else if ((!empty($params['openid_identity']) && $params["openid_identity"] != $id) || (!empty($params['openid_op_endpoint']) && $params['openid_op_endpoint'] != $discovered_server) || $discovered_version != $version) { $this->_setError("Discovery information verification failed"); return false; } } return true; } $this->_storage->delAssociation($url); $this->_setError("Signature check failed"); return false; } else { /* Use dumb mode */ if (isset($params['openid_claimed_id'])) { $id = $params['openid_claimed_id']; } else if (isset($params['openid_identity'])) { $id = $params['openid_identity']; } else { $this->_setError("Missing openid.claimed_id and openid.identity"); return false; } if (!$this->normalize($id)) { trigger_error('Normalization failed', E_USER_ERROR); return false; } else if (!$this->_discovery($id, $server, $discovered_version)) { trigger_error('Discovery failed', E_USER_ERROR); return false; } /* OpenID 2.0 (11.2) Verifying Discovered Information */ if ((isset($params['openid_identity']) && $params["openid_identity"] != $id) || (isset($params['openid_op_endpoint']) && $params['openid_op_endpoint'] != $server) || $discovered_version != $version) { trigger_error('Discovery information verification failed', E_USER_ERROR); return false; } $params2 = array(); foreach ($params as $key => $val) { if (strpos($key, 'openid_ns_') === 0) { $key = 'openid.ns.' . substr($key, strlen('openid_ns_')); } else if (strpos($key, 'openid_sreg_') === 0) { $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_')); } else if (strpos($key, 'openid_') === 0) { $key = 'openid.' . substr($key, strlen('openid_')); } $params2[$key] = $val; } $params2['openid.mode'] = 'check_authentication'; $ret = $this->client->modifier($serveur, $params2); //_httpRequest($server, 'POST', $params2, $status); if ($ret === false) { trigger_error("'Dumb' signature verification HTTP request failed", E_USER_ERROR); return false; } $r = array(); if (is_string($ret)) { foreach(explode("\n", $ret) as $line) { $line = trim($line); if (!empty($line)) { $x = explode(':', $line, 2); if (is_array($x) && count($x) == 2) { list($key, $value) = $x; $r[trim($key)] = trim($value); } } } } $ret = $r; if (!empty($ret['invalidate_handle'])) { if ($this->_storage->getAssociationByHandle( $ret['invalidate_handle'], $url, $macFunc, $secret, $expires)) { $this->_storage->delAssociation($url); } } if (isset($ret['is_valid']) && $ret['is_valid'] == 'true') { if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) { $this->_setError("Extension::parseResponse failure"); return false; } return true; } $this->_setError("'Dumb' signature verification failed"); return false; } } /** * Performs discovery of identity and finds OpenID URL, OpenID server URL * and OpenID protocol version. Returns true on succees and false on * failure. * * @param string &$id OpenID identity URL * @param string &$server OpenID server URL * @param float &$version OpenID protocol version * @return bool * @todo OpenID 2.0 (7.3) XRI and Yadis discovery */ protected function _discovery(&$id, &$server, &$version) { $realId = $id; if ($this->_storage->getDiscoveryInfo( $id, $realId, $server, $version, $expire)) { $id = $realId; return true; } /* TODO: OpenID 2.0 (7.3) XRI and Yadis discovery */ /* HTML-based discovery */ $clientDiscovery = new Client(); //TODO : rajouter un test sur le statut de la réponse // Nécessite la prise en compte des entetes dans le framework /*if ($status != 200 || !is_string($response)) { return false; }*/ $reponse = $clientDiscovery->consulter($id); $metaServeur = $this->verifierVersion($reponse); if (!isset($metaServeur) || empty($reponse)) { trigger_error('Aucune donnée OpenId', E_USER_ERROR); return false; } $expire = time() + 60 * 60; $this->_storage->addDiscoveryInfo($id, $metaServeur['realId'], $metaServeur['serveur'], $metaServeur['version'], $expire); if (!empty($metaServeur['realId'])) { $id = $metaServeur['realId']; } return true; } //Parser l'HTML de réponse pour trouver la version du serveur OPEN ID function verifierVersion($reponseHtml) { // TODO : remplacer l'arlgorythme suivant par cette solution : //1. Chercher l'existence d'une balise openidN.provider //2. Déterminer la version en fonction de la chaine : openid2.provider => 2.0; openid.provider => 1.1 //3. Récupérer l'url du serveur href="serveur" //4. SI 2.0, récupérer la valeur réelle de l'ID //TODO : penser à tester les deux versions du serveur $metaServeur = Array(); if (preg_match( '/]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['version'] = 2.0; $metaServeur['serveur'] = $r[3]; } else if (preg_match( '/]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\3[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['version'] = 2.0; $metaServeur['serveur'] = $r[1]; } else if (preg_match( '/]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['version'] = 1.1; $metaServeur['serveur'] = $r[3]; } else if (preg_match( '/]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\3[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['version'] = 1.1; $metaServeur['serveur'] = $r[2]; } else { return false; } if ($metaServeur['version'] >= 2.0) { if (preg_match( '/]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['realId'] = $r[3]; } else if (preg_match( '/]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\3[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['realId'] = $r[2]; } } else { if (preg_match( '/]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['realId'] = $r[3]; } else if (preg_match( '/]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\3[^>]*\/?>/i', $reponseHtml, $r)) { $metaServeur['realId'] = $r[2]; } } return $metaServeur; } /** * Create (or reuse existing) association between OpenID consumer and * OpenID server based on Diffie-Hellman key agreement. Returns true * on success and false on failure. * * @param string $url OpenID server url * @param float $version OpenID protocol version * @param string $priv_key for testing only * @return bool */ protected function _associate($url, $version, $priv_key=null) { /* Check if we already have association in chace or storage */ /* * TODO : Utiliser le stockage plutot * */ if ($this->_getAssociation( $url, $handle, $macFunc, $secret, $expires)) { return true; } /* * TODO : utiliser le fichier de config * if ($this->_dumbMode) { return true; }*/ $params = array(); if ($version >= 2.0) { $params = array( 'openid.ns' => $this->NS_2_0, 'openid.mode' => 'associate', 'openid.assoc_type' => 'HMAC-SHA256', 'openid.session_type' => 'DH-SHA256', ); } else { $params = array( 'openid.mode' => 'associate', 'openid.assoc_type' => 'HMAC-SHA1', 'openid.session_type' => 'DH-SHA1', ); } $dh = DiffieHellmanUtil::createDhKey(pack('H*', DiffieHellmanUtil::DH_P), pack('H*', DiffieHellmanUtil::DH_G), $priv_key); $dh_details = DiffieHellmanUtil::getDhKeyDetails($dh); $params['openid.dh_modulus'] = base64_encode( DiffieHellmanUtil::btwoc($dh_details['p'])); $params['openid.dh_gen'] = base64_encode( DiffieHellmanUtil::btwoc($dh_details['g'])); $params['openid.dh_consumer_public'] = base64_encode( DiffieHellmanUtil::btwoc($dh_details['pub_key'])); while(1) { //FIXME : c'est pas une modification ... $ret = $this->client->modifier($url, $params); // FIXME : a quoi sert status ?, $status); if ($ret === false) { //$this->_setError("HTTP request failed"); trigger_error('La requête a échoué', E_USER_ERROR); return false; } $r = array(); $bad_response = false; foreach(explode("\n", $ret) as $line) { $line = trim($line); if (!empty($line)) { $x = explode(':', $line, 2); if (is_array($x) && count($x) == 2) { list($key, $value) = $x; $r[trim($key)] = trim($value); } else { $bad_response = true; } } } if ($bad_response && strpos($ret, 'Unknown session type') !== false) { $r['error_code'] = 'unsupported-type'; } $ret = $r; if (isset($ret['error_code']) && $ret['error_code'] == 'unsupported-type') { if ($params['openid.session_type'] == 'DH-SHA256') { $params['openid.session_type'] = 'DH-SHA1'; $params['openid.assoc_type'] = 'HMAC-SHA1'; } else if ($params['openid.session_type'] == 'DH-SHA1') { $params['openid.session_type'] = 'no-encryption'; } else { trigger_error("The OpenID service responded with: " . $ret['error_code'], E_USER_ERROR); return false; } } else { break; } } /* FIXME : gestion du statut avec la classe client ?? if ($status != 200) { $this->_setError("The server responded with status code: " . $status); return false; }*/ if ($version >= 2.0 && isset($ret['ns']) && $ret['ns'] != $this->NS_2_0) { $this->_setError("Wrong namespace definition in the server response"); return false; } if (!isset($ret['assoc_handle']) || !isset($ret['expires_in']) || !isset($ret['assoc_type']) || $params['openid.assoc_type'] != $ret['assoc_type']) { if ($params['openid.assoc_type'] != $ret['assoc_type']) { $this->_setError("The returned assoc_type differed from the supplied openid.assoc_type"); } else { $this->_setError("Missing required data from provider (assoc_handle, expires_in, assoc_type are required)"); } return false; } $handle = $ret['assoc_handle']; $expiresIn = $ret['expires_in']; if ($ret['assoc_type'] == 'HMAC-SHA1') { $macFunc = 'sha1'; } else if ($ret['assoc_type'] == 'HMAC-SHA256' && $version >= 2.0) { $macFunc = 'sha256'; } else { $this->_setError("Unsupported assoc_type"); return false; } if ((empty($ret['session_type']) || ($version >= 2.0 && $ret['session_type'] == 'no-encryption')) && isset($ret['mac_key'])) { $secret = base64_decode($ret['mac_key']); } else if (isset($ret['session_type']) && $ret['session_type'] == 'DH-SHA1' && !empty($ret['dh_server_public']) && !empty($ret['enc_mac_key'])) { $dhFunc = 'sha1'; } else if (isset($ret['session_type']) && $ret['session_type'] == 'DH-SHA256' && $version >= 2.0 && !empty($ret['dh_server_public']) && !empty($ret['enc_mac_key'])) { $dhFunc = 'sha256'; } else { $this->_setError("Unsupported session_type"); return false; } if (isset($dhFunc)) { $serverPub = base64_decode($ret['dh_server_public']); $dhSec = DiffieHellmanUtil::computeDhSecret($serverPub, $dh); if ($dhSec === false) { $this->_setError("DH secret comutation failed"); return false; } $sec = $this->digest($dhFunc, $dhSec); if ($sec === false) { $this->_setError("Could not create digest"); return false; } $secret = $sec ^ base64_decode($ret['enc_mac_key']); } if ($macFunc == 'sha1') { if (DiffieHellmanUtil::strlen($secret) != 20) { $this->_setError("The length of the sha1 secret must be 20"); return false; } } else if ($macFunc == 'sha256') { if (DiffieHellmanUtil::strlen($secret) != 32) { $this->_setError("The length of the sha256 secret must be 32"); return false; } } $this->_addAssociation( $url, $handle, $macFunc, $secret, time() + $expiresIn); /* $this->association['url'] = $url; $this->association['handle'] = $handle; $this->association['macFunc'] = $macFunc; $this->association['secret'] = $secret; $this->association['expiresIn'] = time() + $expiresIn;*/ return true; } /** * Store assiciation in internal chace and external storage * * @param string $url OpenID server url * @param string $handle association handle * @param string $macFunc HMAC function (sha1 or sha256) * @param string $secret shared secret * @param integer $expires expiration UNIX time * @return void */ protected function _addAssociation($url, $handle, $macFunc, $secret, $expires) { $this->_cache[$url] = array($handle, $macFunc, $secret, $expires); return $this->_storage->addAssociation( $url, $handle, $macFunc, $secret, $expires); } /** * Retrive assiciation information for given $url from internal cahce or * external storage * * @param string $url OpenID server url * @param string &$handle association handle * @param string &$macFunc HMAC function (sha1 or sha256) * @param string &$secret shared secret * @param integer &$expires expiration UNIX time * @return void */ protected function _getAssociation($url, &$handle, &$macFunc, &$secret, &$expires) { if (isset($this->_cache[$url])) { $handle = $this->_cache[$url][0]; $macFunc = $this->_cache[$url][1]; $secret = $this->_cache[$url][2]; $expires = $this->_cache[$url][3]; return true; } if ($this->_storage->getAssociation( $url, $handle, $macFunc, $secret, $expires)) { $this->_cache[$url] = array($handle, $macFunc, $secret, $expires); return true; } return false; } /** * Normalizes URL according to RFC 3986 to use it in comparison operations. * The function gets URL argument by reference and modifies it. * It returns true on success and false of failure. * * @param string &$id url to be normalized * @return bool */ static public function normalizeUrl(&$id) { // RFC 3986, 6.2.2. Syntax-Based Normalization // RFC 3986, 6.2.2.2 Percent-Encoding Normalization $i = 0; $n = strlen($id); $res = ''; while ($i < $n) { if ($id[$i] == '%') { if ($i + 2 >= $n) { return false; } ++$i; if ($id[$i] >= '0' && $id[$i] <= '9') { $c = ord($id[$i]) - ord('0'); } else if ($id[$i] >= 'A' && $id[$i] <= 'F') { $c = ord($id[$i]) - ord('A') + 10; } else if ($id[$i] >= 'a' && $id[$i] <= 'f') { $c = ord($id[$i]) - ord('a') + 10; } else { return false; } ++$i; if ($id[$i] >= '0' && $id[$i] <= '9') { $c = ($c << 4) | (ord($id[$i]) - ord('0')); } else if ($id[$i] >= 'A' && $id[$i] <= 'F') { $c = ($c << 4) | (ord($id[$i]) - ord('A') + 10); } else if ($id[$i] >= 'a' && $id[$i] <= 'f') { $c = ($c << 4) | (ord($id[$i]) - ord('a') + 10); } else { return false; } ++$i; $ch = chr($c); if (($ch >= 'A' && $ch <= 'Z') || ($ch >= 'a' && $ch <= 'z') || $ch == '-' || $ch == '.' || $ch == '_' || $ch == '~') { $res .= $ch; } else { $res .= '%'; if (($c >> 4) < 10) { $res .= chr(($c >> 4) + ord('0')); } else { $res .= chr(($c >> 4) - 10 + ord('A')); } $c = $c & 0xf; if ($c < 10) { $res .= chr($c + ord('0')); } else { $res .= chr($c - 10 + ord('A')); } } } else { $res .= $id[$i++]; } } if (!preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?#]*)?((?:[?](?:[^#]*))?)((?:#.*)?)$|', $res, $reg)) { return false; } $scheme = $reg[1]; $auth = $reg[2]; $host = $reg[3]; $port = $reg[4]; $path = $reg[5]; $query = $reg[6]; $fragment = $reg[7]; /* strip it */ if (empty($scheme) || empty($host)) { return false; } // RFC 3986, 6.2.2.1. Case Normalization $scheme = strtolower($scheme); $host = strtolower($host); // RFC 3986, 6.2.2.3. Path Segment Normalization if (!empty($path)) { $i = 0; $n = strlen($path); $res = ""; while ($i < $n) { if ($path[$i] == '/') { ++$i; while ($i < $n && $path[$i] == '/') { ++$i; } if ($i < $n && $path[$i] == '.') { ++$i; if ($i < $n && $path[$i] == '.') { ++$i; if ($i == $n || $path[$i] == '/') { if (($pos = strrpos($res, '/')) !== false) { $res = substr($res, 0, $pos); } } else { $res .= '/..'; } } else if ($i != $n && $path[$i] != '/') { $res .= '/.'; } } else { $res .= '/'; } } else { $res .= $path[$i++]; } } $path = $res; } // RFC 3986,6.2.3. Scheme-Based Normalization if ($scheme == 'http') { if ($port == 80) { $port = ''; } } else if ($scheme == 'https') { if ($port == 443) { $port = ''; } } if (empty($path)) { $path = '/'; } $id = $scheme . '://' . $auth . $host . (empty($port) ? '' : (':' . $port)) . $path . $query; return true; } /** * Normaliser l'identifiant OpenId qui peut être une URL ou nom XRI * Retourne true ou false en cas d'erreur. * * Règles de normalisation : * 1. If the user's input starts with one of the "xri://", "xri://$ip*", * or "xri://$dns*" prefixes, they MUST be stripped off, so that XRIs * are used in the canonical form, and URI-authority XRIs are further * considered URL identifiers. * 2. If the first character of the resulting string is an XRI Global * Context Symbol ("=", "@", "+", "$", "!"), then the input SHOULD be * treated as an XRI. * 3. Otherwise, the input SHOULD be treated as an http URL; if it does * not include a "http" or "https" scheme, the Identifier MUST be * prefixed with the string "http://". * 4. URL identifiers MUST then be further normalized by both following * redirects when retrieving their content and finally applying the * rules in Section 6 of [RFC3986] to the final destination URL. * @param string &$id identifier to be normalized * @return bool */ static public function normalize(&$id) { $id = trim($id); if (strlen($id) === 0) { return true; } // 7.2.1 if (strpos($id, 'xri://$ip*') === 0) { $id = substr($id, strlen('xri://$ip*')); } else if (strpos($id, 'xri://$dns*') === 0) { $id = substr($id, strlen('xri://$dns*')); } else if (strpos($id, 'xri://') === 0) { $id = substr($id, strlen('xri://')); } // 7.2.2 if ($id[0] == '=' || $id[0] == '@' || $id[0] == '+' || $id[0] == '$' || $id[0] == '!') { return true; } // 7.2.3 if (strpos($id, "://") === false) { $id = 'http://' . $id; } // 7.2.4 return self::normalizeURL($id); } /** * Generates a hash value (message digest) according to given algorithm. * It returns RAW binary string. * * This is a wrapper function that uses one of available internal function * dependent on given PHP configuration. It may use various functions from * ext/openssl, ext/hash, ext/mhash or ext/standard. * * @param string $func digest algorithm * @param string $data data to sign * @return string RAW digital signature * @throws Zend_OpenId_Exception */ public function digest($func, $data) { if (function_exists('openssl_digest')) { return openssl_digest($data, $func, true); } else if (function_exists('hash')) { return hash($func, $data, true); } else if ($func === 'sha1') { return sha1($data, true); } else if ($func === 'sha256') { if (function_exists('mhash')) { return mhash(MHASH_SHA256 , $data); } } /*require_once "Zend/OpenId/Exception.php"; throw new Zend_OpenId_Exception( 'Unsupported digest algorithm "' . $func . '".', Zend_OpenId_Exception::UNSUPPORTED_DIGEST);*/ trigger_error('Unsupported digest algorithm '.$func , E_USER_ERROR); } /** * Returns a full URL that was requested on current HTTP request. * * @return string */ public function selfUrl() { /*FIXME : * if ($this->$selfUrl !== null) { return $this->$selfUrl; } */ if (isset($_SERVER['SCRIPT_URI'])) { return $_SERVER['SCRIPT_URI']; } $url = ''; $port = ''; if (isset($_SERVER['HTTP_HOST'])) { if (($pos = strpos($_SERVER['HTTP_HOST'], ':')) === false) { if (isset($_SERVER['SERVER_PORT'])) { $port = ':' . $_SERVER['SERVER_PORT']; } $url = $_SERVER['HTTP_HOST']; } else { $url = substr($_SERVER['HTTP_HOST'], 0, $pos); $port = substr($_SERVER['HTTP_HOST'], $pos); } } else if (isset($_SERVER['SERVER_NAME'])) { $url = $_SERVER['SERVER_NAME']; if (isset($_SERVER['SERVER_PORT'])) { $port = ':' . $_SERVER['SERVER_PORT']; } } if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { $url = 'https://' . $url; if ($port == ':443') { $port = ''; } } else { $url = 'http://' . $url; if ($port == ':80') { $port = ''; } } $url .= $port; if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { $url .= $_SERVER['HTTP_X_REWRITE_URL']; } elseif (isset($_SERVER['REQUEST_URI'])) { $query = strpos($_SERVER['REQUEST_URI'], '?'); if ($query === false) { $url .= $_SERVER['REQUEST_URI']; } else { $url .= substr($_SERVER['REQUEST_URI'], 0, $query); } } else if (isset($_SERVER['SCRIPT_URL'])) { $url .= $_SERVER['SCRIPT_URL']; } else if (isset($_SERVER['REDIRECT_URL'])) { $url .= $_SERVER['REDIRECT_URL']; } else if (isset($_SERVER['PHP_SELF'])) { $url .= $_SERVER['PHP_SELF']; } else if (isset($_SERVER['SCRIPT_NAME'])) { $url .= $_SERVER['SCRIPT_NAME']; if (isset($_SERVER['PATH_INFO'])) { $url .= $_SERVER['PATH_INFO']; } } return $url; } //TODO : vérifier si les fonctions FWK & ZEND sont bien équivalente /** * Retourne l'url absolue d'une url donnée * * @param string $url absilute or relative URL * @return string */ public function absoluteUrl($url) { if (!empty($ur)) { $urlAbsolue = new Url($url); $urlAbsolue->normaliser(); $url = $urlAbsolue->getUrl(); } else { $url = $this->selfUrl(); } return $url; /* if (empty($url)) { return $this->selfUrl(); } else if (!preg_match('|^([^:]+)://|', $url)) { if (preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?]*)?((?:[?](?:[^#]*))?(?:#.*)?)$|', $this->selfUrl(), $reg)) { $scheme = $reg[1]; $auth = $reg[2]; $host = $reg[3]; $port = $reg[4]; $path = $reg[5]; $query = $reg[6]; if ($url[0] == '/') { return $scheme . '://' . $auth . $host . (empty($port) ? '' : (':' . $port)) . $url; } else { $dir = dirname($path); return $scheme . '://' . $auth . $host . (empty($port) ? '' : (':' . $port)) . (strlen($dir) > 1 ? $dir : '') . '/' . $url; } } } return $url;*/ } //TODO : voir si on ne peut pas glisser ça dans client ? //FIXME : je met une fonction SIMPLISSIME a améliorer et reécrire // La fonction de Zend est plus poussée est prend en compte le cas ou l'header ne peut pas etre envoyé /** * Rediriger vers la page du serveur avec les paramètres de confiration * * @param string $url URL de retour * @param array $params paramètres additionnels */ public function redirect($url, $params) { //1. fabriquer l'url Get $urlRedirection = new Url($url); $urlRedirection->setRequete($params); //echo $urlRedirection->getUrl(); try { header('Location:'.$urlRedirection->getUrl()); } catch (Exception $e) { //TODO : voir autres méthodes de redirection // > balise META // > formulaire HTML // > JS } } } ?>