openrat-cms

# OpenRat Content Management System
git clone http://git.code.weiherhei.de/openrat-cms.git
Log | Files | Refs

OpenIdAuth.class.php (12638B)


      1 <?php
      2 
      3 
      4 /**
      5  * Open-Id Authentisierung gem�� OpenId-Spezifikation 1.0.
      6  *
      7  */
      8 class OpenIdAuth implements Auth
      9 {
     10 	function username()
     11 	{
     12 		return null;
     13 	}
     14 	
     15 	
     16 	function login( $username, $password, $token )
     17 	{
     18 		return false;
     19 	}
     20 	
     21 	
     22 	function redirect()
     23 	{
     24 		$this->login2();
     25 		return $this->getRedirectUrl();
     26 	}
     27 	
     28 	
     29 	function checkToken()
     30 	{
     31 		$this->checkAuthentication();
     32 	}
     33 	
     34 	/**
     35 	 * Open-Id Server, an den die Authentisierungsanfrage gestellt wird.
     36 	 *
     37 	 * @var String
     38 	 */
     39 	var $server;
     40 	
     41 	
     42 	/**
     43 	 * Informationen zum Benutzer.
     44 	 *
     45 	 * @var Array
     46 	 */
     47 	var $info;
     48 	
     49 	/**
     50 	 * Open-Id Identity.
     51 	 *
     52 	 * @var String
     53 	 */
     54 	var $identity;
     55 	
     56 	/**
     57 	 * Fehlermeldung (falls vorhanden).
     58 	 *
     59 	 * @var String
     60 	 */
     61 	var $error;
     62 
     63 	/**
     64 	 * OpenId-Benutzername.
     65 	 *
     66 	 * @var String
     67 	 */
     68 	var $user;
     69 
     70 	/**
     71 	 * OpenId-Provider.
     72 	 *
     73 	 * @var String
     74 	 */
     75 	var $provider;
     76 
     77 	
     78 	var $supportAX;
     79 	var $supportSREG;
     80 	var $supportOpenId1_1;
     81 	var $supportOpenId2_0;
     82 	
     83 	
     84 	/**
     85 	 * Neue Open-Id Anfrage.
     86 	 *
     87 	 * @param String $user
     88 	 * @return OpenId
     89 	 */
     90 	function OpenId( $provider='',$user='' )
     91 	{
     92 		$this->provider = $provider;
     93 		$this->user     = $user;
     94 	}
     95 
     96 	
     97 	/**
     98 	 * Stellt fest, ob der Server vertrauenswuerdig ist.
     99 	 *
    100 	 * @return true, wenn vertrauenswuerdig.
    101 	 */
    102 	function serverOk()
    103 	{
    104 		global $conf;
    105 		$servers = $conf['security']['openid']['trusted_server'];
    106 		
    107 		if	( empty($servers) )
    108 		{
    109 			return true;
    110 		}
    111 		else
    112 		{
    113 			$serverList = explode(',',$servers);
    114 			
    115 			$http = new Http($this->server);
    116 			if	( !in_array($http->url['host'],$serverList) )
    117 			{
    118 				$this->error = 'Server '.$this->server.' is not trusted';
    119 				return false;
    120 			}
    121 			else
    122 				return true;
    123 		}
    124 		
    125 	}
    126 	
    127 	
    128 	
    129 	/**
    130 	 * Authentisierung Schritt 1.<br>
    131 	 * Ermitteln der Identity.
    132 	 *
    133 	 * @return boolean TRUE, wenn Identity ermittelt wurde.
    134 	 */
    135 	function login2()
    136 	{
    137 		if	( $this->provider != 'identity' ) 
    138 		{
    139 			$this->user     = config('security','openid','provider.'.$this->provider.'.xrds_uri'); 
    140 			$this->identity = 'http://specs.openid.net/auth/2.0/identifier_select'; 
    141 		}
    142 		$this->supportSREG = config('security','openid','provider.'.$this->provider.'.sreg_1_0');
    143 		$this->supportAX   = config('security','openid','provider.'.$this->provider.'.ax_1_0'  );
    144 		
    145 		// Schritt 1: Identity aus Yadis-Dokument laden.
    146 		$this->getIdentityFromYadis();
    147 
    148 		// Schritt 2: Fallback auf HTML-Dokument.
    149 		if	( empty($this->server) )
    150 		{
    151 			$this->getIdentityFromHtmlMetaData();
    152 		}
    153 		
    154 		// Falls immer noch kein Servername gefunden wurde, dann Abbruch.
    155 		if	( empty($this->server) )
    156 		{
    157 			if	( empty($this->error) )
    158 				$this->error = 'Unable to locate OpenId-Server in URL';
    159 			return false;
    160 		}
    161 		
    162 		if	( !$this->serverOk() )
    163 			return false; // Server nicht vertrauenswuerdig.
    164 		
    165 		if	( empty($this->identity) )
    166 			// Falls die Identity bis hierher nicht deligiert wurde...
    167 			// Lt. Spezifikation mit Prefix "http://".
    168 			$this->identity = 'http://'.$this->user;
    169 		
    170 		return true;
    171 	}
    172 	
    173 	
    174 	
    175 	/**
    176 	 * Erzeugt einen HTTP-Redirect auf den OpenId-Provider.
    177 	 */
    178 	public function getRedirectUrl()
    179 	{
    180 		global $conf;
    181 		
    182 		$this->handle = md5(microtime().session_id());
    183 
    184 		$redirHttp = new Http($this->server);
    185 		
    186 		if	( $this->supportOpenId2_0 )
    187 			$redirHttp->requestParameter['openid.ns'       ] = 'http://specs.openid.net/auth/2.0';
    188 			
    189 		$redirHttp->requestParameter['openid.mode'         ] = 'checkid_setup';
    190 		$redirHttp->requestParameter['openid.identity'     ] = $this->identity;
    191 		
    192 		if	( $this->supportOpenId2_0 )
    193 			$redirHttp->requestParameter['openid.claimed_id'] = $this->identity;
    194 		
    195 
    196 		// Profilangaben anfordern. E-Mail wird ben�tigt, Name und Sprache sind optional.
    197 		
    198 		if	( $this->supportAX )
    199 		{
    200 			Logger::info("OpenId-Server is using OpenID Attribute Exchange 1.0");
    201 			$redirHttp->requestParameter['openid.ns.ax'            ] = 'http://openid.net/srv/ax/1.0';
    202 			$redirHttp->requestParameter['openid.ax.mode'          ] = 'fetch_request';
    203 			$redirHttp->requestParameter['openid.ax.type.email'    ] = 'http://axschema.org/contact/email';
    204 			$redirHttp->requestParameter['openid.ax.type.username' ] = 'http://axschema.org/namePerson/friendly';
    205 		    $redirHttp->requestParameter['openid.ax.type.fullname' ] = 'http://axschema.org/namePerson';
    206 		    $redirHttp->requestParameter['openid.ax.type.language' ] = 'http://axschema.org/pref/language';
    207 		    $redirHttp->requestParameter['openid.ax.required'      ] = 'username,email';
    208 		    $redirHttp->requestParameter['openid.ax.if_available'  ] = 'language,fullname';
    209 		}
    210 		
    211 		if	( $this->supportSREG )
    212 		{
    213 			Logger::info("OpenId-Server is using OpenID Simple Registration Extension 1.0");
    214 			$redirHttp->requestParameter['openid.ns.sreg'      ] = 'http://openid.net/sreg/1.0';
    215 			$redirHttp->requestParameter['openid.sreg.required'] = 'email,nickname';
    216 			$redirHttp->requestParameter['openid.sreg.optional'] = 'fullname,language';
    217 		}
    218 		
    219 		$trustRoot = @$conf['security']['openid']['trust_root'];
    220 		$server = Http::getServer();
    221 		if	( empty($trustRoot) )
    222 			$trustRoot = $server;
    223 			
    224 		$redirHttp->requestParameter['openid.trust_root'   ] = slashify($trustRoot);
    225 		$redirHttp->requestParameter['openid.return_to'    ] = slashify($server).'openid.'.PHP_EXT;
    226 		//$redirHttp->requestParameter['openid.realm'        ] = slashify($server).'openid.'.PHP_EXT;
    227 		$redirHttp->requestParameter['openid.assoc_handle' ] = $this->handle;
    228 
    229 		return $redirHttp->getUrl();
    230 	}
    231 	
    232 	
    233 	
    234 	/**
    235 	 * Ermittelt OpenId-Server und OpenId-Identity aus Yadis-Dokument.<br>
    236 	 *
    237 	 * @return unknown
    238 	 */
    239 	private function getIdentityFromYadis()
    240 	{
    241 		$http = new Http($this->user);
    242 //		$http->url['host'] = $this->user;
    243 
    244 		$http->header[] = 'Accept: application/xrds+xml';
    245 		if	( ! $http->request() )
    246 		{
    247 			$this->error = 'Unable to get XML delegate information';
    248 			return false;
    249 		}
    250 		
    251 		Logger::debug("OpenId: Found YADIS-document for ".$http->getUrl());
    252 		//die();
    253 		$p = xml_parser_create();
    254 		$ok = xml_parse_into_struct($p, $http->body, $vals, $index);
    255 		xml_parser_free($p);
    256 
    257 		foreach( $vals as $tag )
    258 		{
    259 			if	( strtolower($tag['tag']) == 'type' )
    260 			{
    261 				if	( $tag['value'] == 'http://openid.net/srv/ax/1.0' )
    262 					$this->supportAX = true;
    263 					
    264 				if	( $tag['value'] == 'http://openid.net/sreg/1.0' )
    265 					$this->supportSREG = true;
    266 					
    267 				if	( $tag['value'] == 'http://openid.net/signon/1.1' )
    268 					$this->supportOpenId1_1 = true;
    269 					
    270 				if	( $tag['value'] == 'http://specs.openid.net/auth/2.0/server' )
    271 					$this->supportOpenId2_0 = true;
    272 			}
    273 			
    274 			if	( strtolower($tag['tag']) == 'uri' )
    275 			{
    276 				$this->server = $tag['value'];
    277 			}
    278 
    279 			if	( strtolower($tag['tag']) == 'openid:delegate' )
    280 			{
    281 				$this->identity = $tag['value'];
    282 			}
    283 		}
    284 		
    285 		if	( !$this->supportOpenId1_1 && !$this->supportOpenId2_0 )
    286 		{
    287 			$this->error = 'Only OpenId 1.1 and 2.0 is supported but this identity-provider does not seem to support any of these.';
    288 			return false;
    289 		}
    290 		if	( !$this->supportAX && !$this->supportSREG )
    291 		{
    292 			$this->error = 'The identity-provider must support either Attribute-Exchange (AX) oder Simple-Registration (SREG), but it does not seem to support any of these.';
    293 			return false;
    294 		}
    295 	}
    296 
    297 	
    298 	/**
    299 	 * Ermittelt OpenId-Server und OpenId-Identity aus HTML Meta-Tags.<br>
    300 	 */
    301 	private function getIdentityFromHtmlMetaData()
    302 	{
    303 		$http = new Http($this->user);
    304 //		$http = new Http();
    305 //		$http->url['host'] = $this->user;
    306 		$http->header[] = 'Accept: text/html';
    307 
    308 		if	( ! $http->request() )
    309 		{
    310 			$this->error = 'Unable to get HTML delegate information';
    311 			return false;
    312 		}
    313 		
    314 		$seite = $http->body;
    315 		
    316 		// Die Meta-Tags mit regulaerem Ausdruck auslesen.
    317 		$treffer = array();
    318 		preg_match('/rel="openid.server"\s+href="(\S+)"/',$seite,$treffer);
    319 		if	( count($treffer) >= 1 )
    320 		{
    321 			$this->server = $treffer[1];
    322 			$this->supportOpenId1_1 = true;
    323 		}
    324 
    325 		$treffer = array();
    326 		preg_match('/rel="openid2.provider"\s+href="(\S+)"/',$seite,$treffer);
    327 		if	( count($treffer) >= 1 )
    328 		{
    329 			$this->supportOpenId2_0 = true;
    330 			$this->server = $treffer[1];
    331 		}
    332 
    333 		$treffer = array();
    334 		preg_match('/rel="openid.delegate"\s+href="(\S+)"/',$seite,$treffer);
    335 		if	( count($treffer) >= 1 )
    336 			$this->identity = $treffer[1];
    337 	}
    338 	
    339 
    340 	/**
    341 	 * Ermittelt den Hostnamen aus der Identity.
    342 	 *
    343 	 * @return String
    344 	 */
    345 	public function getUserFromIdentiy()
    346 	{
    347 		if	( $this->provider == 'identity' )
    348 		{
    349 			$http = new Http($this->identity);
    350 			return $http->url['host'];
    351 		}
    352 		else
    353 		{
    354 			$attribute_name = config('security','openid','provider.'.$this->provider.'.map_attribute');
    355 			return $this->info[$attribute_name];
    356 		}
    357 	}
    358 		
    359 	
    360 	/**
    361 	 * Open-Id Login, �berpr�fen der Anmeldung.<br>
    362 	 * Spezifikation: http://openid.net/specs/openid-authentication-1_1.html<br>
    363 	 * Kapitel "4.4. check_authentication"<br>
    364 	 * <br>
    365 	 * Im 2. Schritt (Mode "id_res") erfolgte ein Redirect vom Open-Id Provider an OpenRat zur�ck.<br>
    366 	 * Wir befinden uns nun im darauf folgenden Request des Browsers.<br>
    367 	 * <br>
    368 	 * Es muss noch beim OpenId-Provider die Best�tigung eingeholt werden, danach ist der
    369 	 * Benutzer angemeldet.<br>
    370 	 */
    371 	public function checkAuthentication()
    372 	{
    373 		$queryVars = $this->getQueryParamList(); 
    374 		       
    375 		if	( $queryVars['openid.invalidate_handle'] != $this->handle )
    376 		{
    377 			throw new \SecurityException('Association-Handle mismatch.');
    378 		}
    379 
    380 		if	( $queryVars['openid.mode'] != 'id_res' )
    381 		{
    382 			throw new \SecurityException('Open-Id: Unknown mode:'.$queryVars['openid.mode']);
    383 		}
    384 		
    385 		if	( $this->provider=='identity' && $queryVars['openid.identity'] != $this->identity )
    386 		{
    387 			throw new \SecurityException('Open-Id: Identity mismatch. Wrong identity:'.$queryVars['openid.identity']);
    388 		}
    389 		
    390 
    391 		$params = array();
    392 		
    393 		if	( $this->supportAX )
    394 			// Den Namespace-Prefix für AX (attribute exchange) herausfinden.
    395 			// Leider kann das ein anderer Prefix sein, als wir im Request verwendet haben.
    396 			foreach( $queryVars as $request_key=>$request_value )
    397 				if	(  substr($request_key,0,10)=='openid.ns.' && $request_value == 'http://openid.net/srv/ax/1.0' )
    398 					$axPrefix = substr($request_key,10);
    399 		
    400 		foreach( $queryVars as $request_key=>$request_value )
    401 		{
    402 			// Benutzer-Attribute ermitteln.
    403 			// Benutzer-Attribute über SREG ermitteln.
    404 			if	( $this->supportSREG && substr($request_key,0,12)=='openid.sreg.' )
    405 				$this->info[ substr($request_key,12) ] = $request_value;
    406 			// Benutzer-Attribute über AX ermitteln.
    407 			elseif	( $this->supportAX && substr($request_key,0,14+strlen($axPrefix))=='openid.'.$axPrefix.'.value.' )
    408 				$this->info[ substr($request_key,14+strlen($axPrefix)) ] = $request_value;
    409 
    410 			// Alle OpenId-Parameter in den Check-Authentication-Request übertragen.
    411 			if	( substr($request_key,0,7)=='openid.' )
    412 					$params['openid.'.substr($request_key,7) ] = $request_value;			
    413 		}
    414 		$params['openid.mode'] = 'check_authentication';
    415 		
    416 		$checkRequest = new Http($this->server);
    417 		
    418 		$checkRequest->method = 'POST'; // Spezifikation verlangt POST.
    419 		$checkRequest->header['Accept'] = 'text/plain';
    420 		$checkRequest->requestParameter = $params;
    421 		
    422 		if	( ! $checkRequest->request() )
    423 		{
    424 			// Der HTTP-Request ging in die Hose.
    425 			$this->error = $checkRequest->error;
    426 			return false;
    427 		}
    428 		//Html::debug($checkRequest);
    429 		
    430 		// Analyse der HTTP-Antwort, Parsen des BODYs.
    431 		// Die Anmeldung ist best�tigt, wenn im BODY die Zeile "is_valid:true" vorhanden ist.
    432 		// Siehe Spezifikation Kapitel 4.4.2
    433 		$result = array();
    434 		foreach( explode("\n",$checkRequest->body) as $line )
    435 		{
    436 			$pair = explode(':',trim($line));
    437 			if	(count($pair)==2)
    438 				$result[strtolower($pair[0])] = strtolower($pair[1]);
    439 		}
    440 		
    441 		if	( !array_key_exists('is_valid',$result) )
    442 		{
    443 			// Zeile nicht gefunden.
    444 			throw new \SecurityException('Undefined Open-Id response: "is_valid" expected, but not found');
    445 		}
    446 		elseif	( $result['is_valid'] == 'true' )
    447 		{
    448 			// Anmeldung wurde mit "is_valid:true" best�tigt.
    449 			return true;
    450 		}
    451 		else
    452 		{
    453 			// Bestaetigung wurde durch den OpenId-Provider abgelehnt.
    454 			throw new \SecurityException('Server refused login.');
    455 		}
    456 	}
    457 	
    458 	
    459 	/**
    460 	 * Liefert die Query-Parameter aus der aktuellen URL.<br>
    461 	 * <br>
    462 	 * PHP hat leider die sehr bescheuerte Angewohnheit, Punkte und Leerzeichen in Request-Variablen
    463 	 * durch Unterstriche zu ersetzen. Diese Funktion liefert die GET-Parameter ohne diese Ersetzung.
    464 	 * 
    465 	 * @return Parameter der aktuellen URL
    466 	 */
    467 	private function getQueryParamList()
    468 	{
    469 		// Quelle: php.net
    470 		$str = $_SERVER['QUERY_STRING'];
    471 		$op = array();
    472 		$pairs = explode("&", $str);
    473 		foreach ($pairs as $pair)
    474 		{
    475 			list($k, $v) = array_map("urldecode", explode("=", $pair));
    476 			$op[$k] = $v;
    477 		}
    478 		
    479 		return $op;
    480 	}
    481 
    482 	
    483 }
    484 
    485 ?>