openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 9ba92ac52e6b0c5fb62163c5d53dc9883b8d64a6
parent b3694c5a70552129a0a285f5b23e4f91f6dfea4a
Author: dankert <devnull@localhost>
Date:   Sun, 21 Oct 2007 22:33:37 +0200

Umstellung der OpenId-Authentifizierung auf den eigenen HTTP-Client.

Diffstat:
actionClasses/IndexAction.class.php | 130++++++++++++++++++++++++++++++++++++++-----------------------------------------
serviceClasses/Http.class.php | 390++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
2 files changed, 381 insertions(+), 139 deletions(-)

diff --git a/actionClasses/IndexAction.class.php b/actionClasses/IndexAction.class.php @@ -367,9 +367,14 @@ class IndexAction extends Action /** - * Login mit Open-Id.<br> - * Im 2. Schritt erfolgt ein Redirect vom Open-Id Provider an OpenRat zurück.<br> - * Es muss noch beim Provider die Bestätigung eingeholt werden, danach ist der + * Open-Id Login, Überprüfen der Anmeldung.<br> + * Spezifikation: http://openid.net/specs/openid-authentication-1_1.html<br> + * Kapitel "4.4. check_authentication"<br> + * <br> + * Im 2. Schritt (Mode "id_res") erfolgte ein Redirect vom Open-Id Provider an OpenRat zurück.<br> + * Wir befinden uns nun im darauf folgenden Request des Browsers.<br> + * <br> + * Es muss noch beim OpenId-Provider die Bestätigung eingeholt werden, danach ist der * Benutzer angemeldet.<br> */ function openid() @@ -382,9 +387,6 @@ class IndexAction extends Action $openid_delegate = Session::get('openid_delegate'); $openid_handle = Session::get('openid_handle' ); -// global $REQ; -// print_r($REQ); - if ( $this->getRequestVar('openid_invalidate_handle') != $openid_handle ) { $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user),array('Association-Handle mismatch.') ); @@ -398,17 +400,7 @@ class IndexAction extends Action // $this->callSubAction('showlogin'); // return; // } - - $server = parse_url($openid_server); -// $socket = fsockopen($server['host'],80); - $socket = fsockopen($server['host'],443); - - if ( $socket===FALSE ) - { - $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$this->getRequestVar('login_name')),array('Connection failed: '.$openid_server.':80') ); - $this->callSubAction('showlogin'); - return; - } + $params = array(); @@ -420,59 +412,42 @@ class IndexAction extends Action $params['openid.'.substr($request_key,7) ] = $request_value; } $params['openid.mode'] = 'check_authentication'; -// Html::debug($params); - $param_string = ''; - - foreach( $params as $p_name=>$p_value) - { - $param_string .= '&'.$p_name.'='.urlencode($p_value); - } - $param_string = substr($param_string,1); -// $nl = "\r\n"; -// $http_post_cmd = 'POST '.$server['path']." HTTP/1.0".$nl. -// "Connection: Close".$nl. -// "User-Agent: OpenRat CMS".$nl. -// "Host: ".$server['host'].$nl. -// $nl. -// $param_string; -// echo "<pre>".$http_post_cmd."</pre>"; -// -// fputs($socket,$http_post_cmd); -// -// $body = ''; -// do -// { -// $body .= fgets($socket,128); -// } while (!feof($socket)); -// $response = explode("\n",$body); -// -// fclose($socket); -// die('Open-Id Response: '.htmlentities($response)); + $checkRequest = new Http($openid_server); - $url = $openid_server.'?'.$param_string; - $response = file($url); + $checkRequest->method = 'POST'; // Spezifikation verlangt POST, auch wenn GET meistens trotzdem funktioniert. + $checkRequest->requestParameter = $params; + + if ( ! $checkRequest->request() ) + { + // Der HTTP-Request ging in die Hose. + $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$this->getRequestVar('login_name')),array($checkRequest->error) ); + $this->callSubAction('showlogin'); + return; + } + // Analyse der HTTP-Antwort, Parsen des BODYs. + // Die Anmeldung ist bestätigt, wenn im BODY die Zeile "is_valid:true" vorhanden ist. + // Siehe Spezifikation Kapitel 4.4.2 $valid = null; - foreach( $response as $line ) + foreach( explode("\n",$checkRequest->body) as $line ) { $pair = explode(':',trim($line)); if (count($pair)==2 && strtolower($pair[0])=='is_valid') $valid = (strtolower($pair[1])=='true'); } -// die('URL: '.$url.' / Response: '.htmlentities($response)); -// Html::debug($url); -// Html::debug($response); - if ( is_null($valid) ) { + // Zeile nicht gefunden. $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user),array_merge(array('Undefined Open-Id response: '),$response) ); $this->callSubAction('showlogin'); return; } elseif ( $valid ) { + // Anmeldung wurde mit "is_valid:true" bestätigt. + // Der Benutzer ist jetzt eingeloggt. $openid_sreg_email = $this->getRequestVar('openid_sreg_email' ); $openid_sreg_fullname = $this->getRequestVar('openid_sreg_fullname'); $openid_sreg_nickname = $this->getRequestVar('openid_sreg_nickname'); @@ -481,27 +456,31 @@ class IndexAction extends Action if ( $user->userid <=0) { - if ( $conf['security']['openid']['add']) + // Benutzer ist (noch) nicht vorhanden. + if ( $conf['security']['openid']['add']) // Anlegen? { $user->name = $openid_user; $user->mail = $openid_sreg_email; $user->fullname = $openid_sreg_fullname; $user->add(); + $user->save(); // Um E-Mail zu speichern (wird bei add() nicht gemacht) } else { + // Benutzer ist nicht in Benutzertabelle vorhanden (und angelegt werden soll er auch nicht). $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user) ); $this->callSubAction('showlogin'); return; } } - $user->setCurrent(); + $user->setCurrent(); // Benutzer ist jetzt in der Sitzung. return; } else { + // Bestätigung wurde durch den OpenId-Provider abgelehnt. $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user) ); $this->callSubAction('showlogin'); return; @@ -535,25 +514,37 @@ class IndexAction extends Action $newPassword1 = $this->getRequestVar('password1' ); $newPassword2 = $this->getRequestVar('password2' ); + // Login mit Open-Id. if ( !empty($openid_user) ) { - $seite = implode('',file('http://'.$openid_user)); + $http = new Http(); + $http->url['host'] = $openid_user; + + if ( ! $http->request() ) + { + $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user),array('Unable to get delegate information',$http->error) ); + $this->callSubAction('showlogin'); + return; + } + $seite = $http->body; $treffer = array(); preg_match('/rel="openid.server"\s+href="(\S+)"/',$seite,$treffer); if ( count($treffer) >= 1 ) $openid_server = $treffer[1]; -// Html::debug($treffer); $treffer = array(); preg_match('/rel="openid.delegate"\s+href="(\S+)"/',$seite,$treffer); if ( count($treffer) >= 1 ) $openid_delegate = $treffer[1]; + else + $openid_delegate = 'http://'.$openid_user; - if ( empty($openid_server) || empty($openid_delegate) ) + if ( empty($openid_server) ) { - $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user),array('Unable to locate OpenId-Server and OpenId-Delegate') ); + $this->addNotice('user',$openid_user,'LOGIN_OPENID_FAILED','error',array('name'=>$openid_user),array('Unable to locate a OpenId-Server in URL') ); $this->callSubAction('showlogin'); + return; } $openid_handle = md5(microtime().session_id()); @@ -562,16 +553,21 @@ class IndexAction extends Action Session::set('openid_delegate',$openid_delegate); Session::set('openid_handle' ,$openid_handle ); - $redirect_url = $openid_server.'?openid.mode=checkid_setup'; -// $redirect_url .= '&openid.identity='.$openid_delegate; - $redirect_url .= '&openid.identity=https://'.$openid_user; - $redirect_url .= '&openid.sreg.optional=email,nickname,fullname'; - $redirect_url .= '&openid.trust_root=http://'.getenv('SERVER_NAME').dirname(getenv('REQUEST_URI')).'/';; - $redirect_url .= '&openid.return_to=http://'.getenv('SERVER_NAME').dirname(getenv('REQUEST_URI')).'/openid.'.PHP_EXT; - $redirect_url .= '&openid.assoc_handle='.$openid_handle; + $redirHttp = new Http($openid_server); + $redirHttp->requestParameter['openid.mode' ] = 'checkid_setup'; + $redirHttp->requestParameter['openid.identity' ] = $openid_delegate; // Richtig. +// $redirHttp->requestParameter['openid.identity' ] = 'https://'.$openid_user; // Das ist falsch. -// die('Location: '.$redirect_url); - header('Location: '.$redirect_url); + $redirHttp->requestParameter['openid.sreg.optional'] = 'email,nickname,fullname'; + $trustRoot = @$conf['security']['openid']['trust_root']; + $server = Http::getServer(); + if ( empty($trustRoot) ) + $trustRoot = $server.'/'; + $redirHttp->requestParameter['openid.trust_root' ] = $trustRoot; + $redirHttp->requestParameter['openid.return_to' ] = $server.'/openid.'.PHP_EXT; + $redirHttp->requestParameter['openid.assoc_handle' ] = $openid_handle; + + $redirHttp->sendRedirect(); exit; } diff --git a/serviceClasses/Http.class.php b/serviceClasses/Http.class.php @@ -1,7 +1,8 @@ <?php /** - * Bereitstellen von HTTP-Methoden + * Kapselung einer HTTP-Anfrage.<br> + * Unter Berücksichtigung von RFC 1945.<br> * * @author $Author$ * @version $Revision$ @@ -11,125 +12,370 @@ class Http { var $url = array(); var $header = array(); + var $responseHeader = array(); + var $requestParameter = array(); + var $urlParameter = array(); + + /** + * HTTP-Request-Typ.<br> + * Muss entweder "GET" oder "POST" sein.<br> + * Default: "GET". + * + * @var String Request-Typ + */ var $method = 'GET'; var $error = ''; var $status = ''; var $body = ''; - - - + + + + /** + * Erzeugt eine HTTP-Anfrage. + * + * @param String URL + * @return Http + */ function Http( $url = '' ) { + $this->setURL( $url ); + $this->header[] = 'User-Agent: Mozilla/5.0 (OpenRat CMS)'; + $this->header[] = 'Connection: close'; + } + + + + /** + * Setzt die URL. + * + * @param String URL + */ + function setURL( $url ) + { +// Html::debug($url,"neue url"); $this->url = parse_url($url); +// Html::debug($this->url,"direkt nach parse_url"); + + if ( empty($this->url['host']) && !empty($this->url['path']) ) + { + $this->url['host'] = basename($this->url['path']); + $this->url['path'] = '/'; + } + + if ( empty($this->url['path']) ) + $this->url['path'] = '/'; + + if ( !isset($this->url['port']) ) + if ( !isset($this->url['scheme']) ) + { + $this->url['scheme'] = 'http'; // Standard-Port. + $this->url['port'] = 80; // Standard-Port. + } + elseif ( $this->url['scheme'] == 'https' ) + $this->url['port'] = 443; // SSL-Port. + else + $this->url['port'] = 80; // Standard-Port. + + if ( !empty($this->url['query']) ) + parse_str( $this->url['query'],$this->urlParameter ); - if ( !isset($this->url['port'])) - $this->url['port'] = 80; // Standard-Port 80. - - $this->header[] = 'User-Agent: Mozilla/5.0 (OpenRat HTTP-Client)'; - $this->header[] = 'Connection: close'; } - - - + + + + /** + * Setzt Authentisierungsinformationen in den HTTP-Request.<br> + * + * @param String Benutzername + * @param String Kennwort + */ function setBasicAuthentication( $user, $password ) { $this->header[] = 'Authorization: Basic '.base64_encode($user.':'.$password); } + + + /** + * Erzeugt eine Zeichenkette mit allen Parametern. + * @param withPraefixQuestionMark Praefix mit Fragezeichen (für GET-Anfragen) + * @return String URL-Parameter + */ + function getParameterString( $withPraefixQuestionMark=false ) + { + $parameterString = ''; + $parameter = $this->urlParameter + $this->requestParameter; + + if ( ! empty($parameter) ) + { + foreach( $this->requestParameter as $paramName => $paramValue ) + { + if ( strlen($parameterString) > 0) + $parameterString .= '&'; + elseif ( $withPraefixQuestionMark ) + $parameterString .= '?'; + + $parameterString .= urlencode($paramName) . '=' .urlencode($paramValue); + } + } + + return $parameterString; + } + - + /** + * Sendet eine Redirect-Anweisung an den Browser. + * @return String URL + */ + function getUrl() + { + $location = $this->url['scheme']; + $location .= '://'; + $location .= $this->url['host']; + if ( $this->url['scheme'] == 'http' && $this->url['port'] != 80 || + $this->url['scheme'] == 'https' && $this->url['port'] != 443 ) + $location .= ':'.$this->url['port']; + $location .= $this->url['path']; + + $location .= $this->getParameterString(true); + + if ( isset($this->url['fragment']) ) + $location .= '#'.$this->url['fragment']; + + return $location; + } + + + /** + * Sendet eine Redirect-Anweisung mit der aktuellen URL an den Browser. + */ + function sendRedirect() + { + $location = $this->getUrl(); + + header('Location: '.$location); + exit; + } + + + /** + * Erzeugt den HTTP-Request + * + * @return boolean Erfolg der Anfrage. + */ function request() { - $this->body = ''; $this->error = ''; $this->status = ''; - + $errno = 0; $errstr = ''; - - $fp = @fsockopen ($this->url['host'],$this->url['port'], $errno, $errstr, 30); - if ( !$fp ) + if ( empty($this->url['host']) ) { - // Keine Verbindung zum Host moeglich. - $this->error = "Connection refused: '".$this->url['host'].':'.$this->url['host']." - $errstr ($errno)"; + $this->error = "No hostname specified"; return false; } - else + + // RFC 1945 (Section 9.3) says: + // A user agent should never automatically redirect a request + // more than 5 times, since such redirections usually indicate an infinite loop. + for( $r=1; $r<=5; $r++ ) { - $lb = "\r\n"; - $http_get = $this->url['path']; - if ( !empty($this->url['query']) ) - $http_get .= '?'.$this->url['query']; + // Die Funktion fsockopen() erwartet eine Protokollangabe (bei TCP optional, bei SSL notwendig). + if ( $this->url['scheme'] == 'https' || $this->url['port'] == '443' ) + $prx_proto = 'ssl://'; // SSL + else + $prx_proto = 'tcp://'; // Default + + $fp = @fsockopen ($prx_proto.$this->url['host'],$this->url['port'], $errno, $errstr, 30); + + if ( !$fp || !is_resource($fp) ) + { + // Keine Verbindung zum Host moeglich. + $this->error = "Connection refused: '".$prx_proto.$this->url['host'].':'.$this->url['port']." - $errstr ($errno)"; + return false; + } + else + { + $lb = "\r\n"; + $http_get = $this->url['path']; + + $parameterString = $this->getParameterString(); + + if ( $this->method == 'GET') + if ( !empty($parameterString) ) + $http_get .= '?'.$parameterString; + + if ( $this->method == 'POST' ) + { + $this->header[] = 'Content-Type: application/x-www-form-urlencoded'; + $this->header[] = 'Content-Length: '.strlen($parameterString); + } - $request_header = array( $this->method.' '.$http_get.' HTTP/1.0', - 'Host: '.$this->url['host']) + $this->header; - $http_request = implode($lb,$request_header).$lb.$lb; + $this->header[] = 'Host: '.$this->url['host']; + $this->header[] = 'Accept: */*'; + $request_header = array( $this->method.' '.$http_get.' HTTP/1.0') + $this->header; + $http_request = implode($lb,$request_header).$lb.$lb; + + if ( $this->method == 'POST' ) + $http_request .= $parameterString; - fputs($fp, $http_request); + if (!is_resource($fp)) { + $this->error = 'Connection lost after connect: '.$prx_proto.$this->url['host'].':'.$this->url['port']; + return false; + } + fputs($fp, $http_request); // Die HTTP-Anfrage zum Server senden. - $inhalt = array(); - while (!feof($fp)) { - $inhalt[] = fgets($fp,128); - } - fclose($fp); - - $this->body = implode('',$inhalt); // HTTP-Antwort - + // Jetzt erfolgt das Auslesen der HTTP-Antwort. + $isHeader = true; - // RFC 1945 (Section 6.1) schreibt als Statuszeile folgendes Format vor - // "HTTP/" 1*DIGIT "." 1*DIGIT SP 3DIGIT SP - - $this->status = substr($this->body,9,3); + // RFC 1945 (Section 6.1) schreibt als Statuszeile folgendes Format vor + // "HTTP/" 1*DIGIT "." 1*DIGIT SP 3DIGIT SP + if (!is_resource($fp)) { + $this->error = 'Connection lost during transfer: '.$this->url['host'].':'.$this->url['port']; + return false; + } + elseif (!feof($fp)) { + $line = fgets($fp,1028); + $this->status = substr($line,9,3); + } + else + { + $this->error = 'Unexpected EOF while reading HTTP-Response'; + return false; + } + + while (!feof($fp)) { + $line = fgets($fp,1028); + if ( $isHeader && trim($line)=='' ) // Leerzeile nach Header. + { + $isHeader = false; + } + elseif( $isHeader ) + { + list($headerName,$headerValue) = explode(': ',$line) + array(1=>''); + $this->responseHeader[$headerName] = trim($headerValue); + } + else + { + $this->body .= $line; + } + } +// Html::debug($this->url,"URL"); +// Html::debug($http_request,"REQUEST komplett"); +// Html::debug($this->responseHeader); +// Html::debug($this->body,'BODY'); +// echo "<pre>"; +// echo "REQUEST=".nl2br($http_request); +// echo "HEADER=".nl2br(htmlentities(implode("\n",$this->responseHeader))); +// echo "BODY=".nl2br(htmlentities($this->body)); +// echo "</pre>"; + fclose($fp); // Verbindung brav schließen. - // RFC 1945 (Section 6.1.1) schreibt - // "[...] However, applications must understand the class of any status code, as - // indicated by the first digit" - // Daher interessiert uns nur die erste Stelle des 3-stelligen HTTP-Status. - - // RFC 1945 (Section 6.1.1) schreibt - // "2xx: Success - The action was successfully received, understood, and accepted." - if ( substr($this->status,0,1) == '2' ) - { - return true; - } - else - { - $this->error = 'Received no 2XX-Status from host: '.$this->status; - return false; + + // RFC 1945 (Section 6.1.1) schreibt + // "[...] However, applications must understand the class of any status code, as + // indicated by the first digit" + // Daher interessiert uns nur die erste Stelle des 3-stelligen HTTP-Status. + + // 301 Moved Permanently + // 302 Moved Temporarily + if ( $this->status == '301' || + $this->status == '302' ) + { + $location = @$this->responseHeader['Location']; + if ( empty($location) ) + { + $this->error = '301/302 Response without Location-header'; + return false; + } + +// Html::debug($this->url,"alte URL"); +// Html::debug($location,"NEUES REDIRECT AUF"); + $this->setURL($location); +// Html::debug($this->url,"NEUE URL NACH REDIRECT"); + continue; // Nächster Versuch mit umgeleiteter Adresse. + } + // RFC 1945 (Section 6.1.1) schreibt + // "2xx: Success - The action was successfully received, understood, and accepted." + elseif ( substr($this->status,0,1) == '2' ) + { + return true; + } + elseif ( substr($this->status,0,1) == '4' ) + { + $this->error = 'Client Error: '.$this->status; + return false; + } + elseif ( substr($this->status,0,1) == '5' ) + { + $this->error = 'Server Error: '.$this->status; + return false; + } + else + { + $this->error = 'Unexpected HTTP-Status: '.$this->status. '; this is mostly a client error, sorry.'; + return false; + } } + + $this->error = 'Too much redirects, infinite loop assumed. Exiting. Last URL: '.$http_get; + return false; + } - + } - - + + /** * Aus dem HTTP-Header werden die vom Browser angeforderten Sprachen - * gelesen. + * gelesen.<br> + * Es wird eine Liste von Sprachen erzeugt.<br> + * Beispiel: 'de_DE','de','en_GB','en' ... usw.<br> + * Wenn der Browser 'de_DE' anfordert, wird hier auch 'de' (als Fallback) ermittelt. * + * @static * @return Array */ function getLanguages() { - global $SESS, - $HTTP_SERVER_VARS, - $conf_php, - $conf; - + global $HTTP_SERVER_VARS; + $languages = array(); $http_languages = @$HTTP_SERVER_VARS['HTTP_ACCEPT_LANGUAGE']; foreach( explode(',',$http_languages) as $l ) { - $parts = explode(';',$l); - $languages[] = trim($parts[0]); - // aus "xx_yy" das "xx" extrahieren. - $languages[] = current(explode('_',trim($parts[0]))); - $languages[] = current(explode('-',trim($parts[0]))); - + list($part) = explode(';',$l); // Prioritäten ignorieren. + $languages[] = trim($part); + + // Aus "de_DE" das "de" extrahieren. + $languages[] = current(explode('_',str_replace('-','_',trim($part)))); } - + return array_unique( $languages ); } + + + /** + * Ermittelt die aktuelle URL des Requests. + * + * @static + * @return String URL + */ + function getServer() + { + $https = getenv('HTTPS'); + + if ( $https ) + $server = 'https://'; + else + $server = 'http://'; + + $server .= getenv('SERVER_NAME').dirname(getenv('REQUEST_URI')); + + return $server; + } } ?> \ No newline at end of file