diff --git a/lib/SimpleSAML/XML/SAML20/AuthnRequest.php b/lib/SimpleSAML/XML/SAML20/AuthnRequest.php index 4e4412f147f6959eb05dd11cc2a16261c1c7e6f9..bf591eeb4dbfeaf6142e8481a5f22dac728504ce 100644 --- a/lib/SimpleSAML/XML/SAML20/AuthnRequest.php +++ b/lib/SimpleSAML/XML/SAML20/AuthnRequest.php @@ -20,6 +20,7 @@ class SimpleSAML_XML_SAML20_AuthnRequest { private $message = null; private $dom; private $relayState = null; + private $isPassive = 'false'; const PROTOCOL = 'saml2'; @@ -107,6 +108,43 @@ class SimpleSAML_XML_SAML20_AuthnRequest { } + /** + * This function sets the IsPassive flag + * + */ + public function setIsPassive($isPassive) { + $this->isPassive = $isPassive ? 'true' : 'false'; + } + + /** + * This function retrieves the IsPassive flag from this authentication request. + * + * @return The IsPassive flag from this authentication request. + */ + public function getIsPassive() { + $dom = $this->getDOM(); + if (empty($dom)) { + throw new Exception("Could not get message DOM in AuthnRequest object"); + } + + $root = $dom->documentElement; + + if(!$root->hasAttribute('IsPassive')) { + /* ForceAuthn defaults to false. */ + return FALSE; + } + + $fa = $root->getAttribute('IsPassive'); + if($fa === 'true') { + return TRUE; + } elseif($fa === 'false') { + return FALSE; + } else { + throw new Exception('Invalid value of IsPassive attribute in SAML2 AuthnRequest.'); + } + } + + /** * This function retrieves the ForceAuthn flag from this authentication request. * @@ -204,7 +242,7 @@ class SimpleSAML_XML_SAML20_AuthnRequest { $authnRequest = '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="' . $id . '" Version="2.0" - IssueInstant="' . $issueInstant . '" ForceAuthn="' . $forceauthn . '" + IssueInstant="' . $issueInstant . '" ForceAuthn="' . $forceauthn . '" IsPassive="' . $this->isPassive . '" Destination="' . htmlspecialchars($destination) . '" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="' . htmlspecialchars($assertionConsumerServiceURL) . '"> diff --git a/lib/SimpleSAML/XML/SAML20/AuthnResponse.php b/lib/SimpleSAML/XML/SAML20/AuthnResponse.php index c17ded36f41e90af500ce62464b7b5dfcd5acbf8..a9bdddeb1def7844f1a6a6427cce418bcf396d24 100644 --- a/lib/SimpleSAML/XML/SAML20/AuthnResponse.php +++ b/lib/SimpleSAML/XML/SAML20/AuthnResponse.php @@ -141,11 +141,23 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { } + /** + * This function finds the status of this response. + */ + public function findstatus() { + + $status = $this->doXPathQuery('/samlp:Response/samlp:Status/samlp:StatusCode')->item(0); + if($status != NULL) { + return $status->getAttribute('Value'); + } + throw new Exception('Unable to determine the status of this SAML2 AuthnResponse message.: ' . $this->getXML()); + } + /** * This function finds the issuer of this response. It will first search the Response element, * and if it isn't found there, it will search all Assertion elements. */ - private function findIssuer() { + public function findIssuer() { /* First check the Response element. */ $issuer = $this->doXPathQuery('/samlp:Response/saml:Issuer')->item(0); @@ -244,7 +256,7 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { if (isset($md['certificate'])) { $publickey = @file_get_contents($this->configuration->getPathValue('certdir') . $md['certificate']); if (!$publickey) { - throw new Exception("Optional saml20-idp-remote metadata 'certificate' set, but no certificate found"); + throw new Exception("Saml20-idp-remote id: " . $this-issuer . " 'certificate' set to ': " . $md['certificate'] . "', but no certificate found"); } } /* Validate the signature. */ @@ -476,32 +488,43 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { * current session if it is valid. It throws an exception if it is invalid. */ public function process() { - /* Find the issuer of this response. */ - $this->issuer = $this->findIssuer(); - - $this->decryptAssertion(); - - /* Validate the signature element. */ - $this->validateSignature(); - - /* Process all assertions. */ - $assertions = $this->doXPathQuery('/samlp:Response/saml:Assertion'); - foreach($assertions as $assertion) { - $this->processAssertion($assertion); - } - - if($this->nameid === NULL) { - throw new Exception('No nameID found in AuthnResponse.'); + $status = $this->findstatus(); + if ($status == 'urn:oasis:names:tc:SAML:2.0:status:Success' ) { + /* Find the issuer of this response. */ + $this->issuer = $this->findIssuer(); + + $this->decryptAssertion(); + + /* Validate the signature element. */ + $this->validateSignature(); + + /* Process all assertions. */ + $assertions = $this->doXPathQuery('/samlp:Response/saml:Assertion'); + foreach($assertions as $assertion) { + $this->processAssertion($assertion); + } + + if($this->nameid === NULL) { + throw new Exception('No nameID found in AuthnResponse.'); + } + + /* Update the session information */ + SimpleSAML_Session::init(true, 'saml2'); + $session = SimpleSAML_Session::getInstance(); + + $session->setAttributes($this->attributes); + $session->setNameID($this->nameid); + $session->setSessionIndex($this->sessionIndex); + $session->setIdP($this->issuer); + } elseif ($status == 'urn:oasis:names:tc:SAML:2.0:status:NoPassive') { + /* Do not process the authResponse when NoPassive is sent - we continue with an empty set of attributes. + Some day we will be able to tell the application what happened */ + SimpleSAML_Session::init(true, 'saml2'); + $session = SimpleSAML_Session::getInstance(); + $session->setAttributes(array()); + } else { + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'RESPONSESTATUSNOSUCCESS', new Exception("Status = " . $status)); } - - /* Update the session information */ - SimpleSAML_Session::init(true, 'saml2'); - $session = SimpleSAML_Session::getInstance(); - - $session->setAttributes($this->attributes); - $session->setNameID($this->nameid); - $session->setSessionIndex($this->sessionIndex); - $session->setIdP($this->issuer); } @@ -546,7 +569,7 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { * * @return AuthenticationResponse as string */ - public function generate($idpentityid, $spentityid, $inresponseto, $nameid, $attributes) { + public function generate($idpentityid, $spentityid, $inresponseto, $nameid, $attributes, $status = 'Success') { /** * Retrieving metadata for the two specific entity IDs. @@ -597,25 +620,10 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { } else { $nameid = $this->generateNameID($nameidformat, self::generateID(), $spnamequalifier); } - - /** - * Generating the response. - */ - $authnResponse = '<samlp:Response - xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" - xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" - xmlns:xs="http://www.w3.org/2001/XMLSchema" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - ID="' . $id . '" - InResponseTo="' . htmlspecialchars($inresponseto) . '" Version="2.0" - IssueInstant="' . $issueInstant . '" - Destination="' . htmlspecialchars($destination) . '"> - <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . htmlspecialchars($issuer) . '</saml:Issuer> - <samlp:Status xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"> - <samlp:StatusCode xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" - Value="urn:oasis:names:tc:SAML:2.0:status:Success" /> - </samlp:Status> - <saml:Assertion Version="2.0" + + $assertion = ""; + if ($status === 'Success') { + $assertion = '<saml:Assertion Version="2.0" ID="' . $assertionid . '" IssueInstant="' . $issueInstant . '"> <saml:Issuer>' . htmlspecialchars($issuer) . '</saml:Issuer> <saml:Subject> @@ -638,9 +646,29 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { </saml:AuthnContext> </saml:AuthnStatement> ' . $attributestatement. ' - </saml:Assertion> -</samlp:Response> -'; + </saml:Assertion>'; + } + + + /** + * Generating the response. + */ + $authnResponse = '<samlp:Response + xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" + xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + ID="' . $id . '" + InResponseTo="' . htmlspecialchars($inresponseto) . '" Version="2.0" + IssueInstant="' . $issueInstant . '" + Destination="' . htmlspecialchars($destination) . '"> + <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . htmlspecialchars($issuer) . '</saml:Issuer> + <samlp:Status xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"> + <samlp:StatusCode xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" + Value="urn:oasis:names:tc:SAML:2.0:status:' . $status . '" /> + </samlp:Status>' + . $assertion . + '</samlp:Response>'; return $authnResponse; } diff --git a/www/saml2/idp/SSOService.php b/www/saml2/idp/SSOService.php index aea1ad10c038009af1cae1ca73ed6086503dad31..dfbc8d477821194281033d4e7e9ef1b5d4783c0b 100644 --- a/www/saml2/idp/SSOService.php +++ b/www/saml2/idp/SSOService.php @@ -104,9 +104,17 @@ if (isset($_GET['SAMLRequest'])) { $forceAuthn = TRUE; } - if($forceAuthn) { + $isPassive = $authnrequest->getIsPassive(); + /* + * The ForceAuthn flag was set to true in the authentication request + * and IsPassive was not - IsPassive overrides ForceAuthn thus the check + * + */ + + if($forceAuthn && !$isPassive) { /* ForceAuthn is enabled. Mark the request as needing authentication. This flag * will be cleared by a call to setAuthenticated(TRUE, ...) to the current session. + * */ $requestcache['NeedAuthentication'] = TRUE; } @@ -175,7 +183,7 @@ if (!isset($session) || !$session->isValid($authority) ) { $needAuth = FALSE; } -if($needAuth) { +if($needAuth && !$isPassive) { SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: Will go to authentication module ' . $idpmetadata['auth']); @@ -204,9 +212,21 @@ if($needAuth) { SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: Sending back AuthnResponse to ' . $spentityid); - + if ($isPassive) { + /* Generate an SAML 2.0 AuthNResponse message + With statusCode: urn:oasis:names:tc:SAML:2.0:status:NoPassive + */ + $ar = new SimpleSAML_XML_SAML20_AuthnResponse($config, $metadata); + $authnResponseXML = $ar->generate($idpentityid, $spentityid, $requestid, null, array(), 'NoPassive'); + + // Sending the AuthNResponse using HTTP-Post SAML 2.0 binding + $httppost = new SimpleSAML_Bindings_SAML20_HTTPPost($config, $metadata); + $httppost->sendResponse($authnResponseXML, $idpentityid, $spentityid, + isset($requestcache['RelayState']) ? $requestcache['RelayState'] : null + ); + exit; + } - /* * Attribute handling */ diff --git a/www/saml2/sp/initSSO.php b/www/saml2/sp/initSSO.php index e2825be0698c9a6a8eb582ffa9c609473ba2fec6..3d50b4dcc000c51859e0cc5c9c249db7248d54b4 100644 --- a/www/saml2/sp/initSSO.php +++ b/www/saml2/sp/initSSO.php @@ -64,6 +64,9 @@ try { $sr = new SimpleSAML_XML_SAML20_AuthnRequest($config, $metadata); + if (isset($_GET['IsPassive'])) { + $sr->setIsPassive($_GET['IsPassive']); + }; $md = $metadata->getMetaData($idpentityid, 'saml20-idp-remote'); $req = $sr->generate($spentityid, $md['SingleSignOnService']);