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:
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