openrat-cms

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

OpenIdAuth.class.php (12734B)


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