/** * @param SAML2_AuthnRequest|EngineBlock_Saml2_AuthnRequestAnnotationDecorator $request * @param SAML2_Response|EngineBlock_Saml2_ResponseAnnotationDecorator $sourceResponse */ public function createEnhancedResponse(EngineBlock_Saml2_AuthnRequestAnnotationDecorator $request, EngineBlock_Saml2_ResponseAnnotationDecorator $sourceResponse) { $newResponse = $this->_createBaseResponse($request); // We don't support multiple assertions, only use the first one. $sourceAssertions = $sourceResponse->getAssertions(); $sourceAssertion = $sourceAssertions[0]; // Store the Origin response and issuer (from the IdP) $newResponse->setOriginalResponse($sourceResponse->getOriginalResponse() ? $sourceResponse->getOriginalResponse() : $sourceResponse); $newResponse->setOriginalIssuer($sourceResponse->getOriginalIssuer() ? $sourceResponse->getOriginalIssuer() : $newResponse->getOriginalResponse()->getIssuer()); // Copy over the Status (which should be success) $newResponse->setStatus($sourceResponse->getStatus()); // Create a new assertion by us. $newAssertion = new SAML2_Assertion(); $newResponse->setAssertions(array($newAssertion)); $newAssertion->setId($this->getNewId(IdFrame::ID_USAGE_SAML2_ASSERTION)); $newAssertion->setIssueInstant(time()); $newAssertion->setIssuer($newResponse->getIssuer()); // Unless of course we are in 'stealth' / transparent mode, in which case, // pretend to be the Identity Provider. $serviceProvider = $this->getRepository()->fetchServiceProviderByEntityId($request->getIssuer()); $mustProxyTransparently = $request->isTransparent() || $serviceProvider->isTransparentIssuer; if (!$this->isInProcessingMode() && $mustProxyTransparently) { $newResponse->setIssuer($newResponse->getOriginalIssuer()); $newAssertion->setIssuer($newResponse->getOriginalIssuer()); } // Copy over the NameID for now... // (further on in the filters we'll have more info and set this to something better) $sourceNameId = $sourceAssertion->getNameId(); if (!empty($sourceNameId) && !empty($sourceNameId['Value']) && !empty($sourceNameId['Format'])) { $newAssertion->setNameId(array('Value' => $sourceNameId['Value'], 'Format' => $sourceNameId['Format'])); } // Set up the Subject Confirmation element. $subjectConfirmation = new SAML2_XML_saml_SubjectConfirmation(); $subjectConfirmation->Method = SAML2_Const::CM_BEARER; $newAssertion->setSubjectConfirmation(array($subjectConfirmation)); $subjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); $subjectConfirmation->SubjectConfirmationData = $subjectConfirmationData; // Confirm where we are sending it. $acs = $this->getRequestAssertionConsumer($request); $subjectConfirmationData->Recipient = $acs->location; // Confirm that this is in response to their AuthnRequest (unless, you know, it isn't). if (!$request->isUnsolicited()) { /** @var SAML2_AuthnRequest $request */ $subjectConfirmationData->InResponseTo = $request->getId(); } // Note that it is valid for some 5 minutes. $notOnOrAfter = time() + $this->getConfig('NotOnOrAfter', 300); $newAssertion->setNotBefore(time() - 1); if ($sourceAssertion->getSessionNotOnOrAfter()) { $newAssertion->setSessionNotOnOrAfter($sourceAssertion->getSessionNotOnOrAfter()); } $newAssertion->setNotOnOrAfter($notOnOrAfter); $subjectConfirmationData->NotOnOrAfter = $notOnOrAfter; // And only valid for the SP that requested it. $newAssertion->setValidAudiences(array($request->getIssuer())); // Copy over the Authentication information because the IdP did the authentication, not us. $newAssertion->setAuthnInstant($sourceAssertion->getAuthnInstant()); $newAssertion->setSessionIndex($sourceAssertion->getSessionIndex()); $newAssertion->setAuthnContextClassRef($sourceAssertion->getAuthnContextClassRef()); $newAssertion->setAuthnContextDeclRef($sourceAssertion->getAuthnContextDeclRef()); if ($sourceAssertion->getAuthnContextDecl()) { $newAssertion->setAuthnContextDecl($sourceAssertion->getAuthnContextDecl()); } // Copy over the Authenticating Authorities and add the EntityId of the Source Response Issuer. // Note that because EB generates multiple responses, this will likely result in: // "https://engine/../idp/metadata" !== "https://original-idp/../idpmetadata" => true, gets added // "https://engine/../idp/metadata" !== "https://engine/../idp/metadata" => false, does not get added // "https://engine/../idp/metadata" !== "https://engine/../idp/metadata" => false, does not get added // UNLESS the Response is destined for an SP in VO mode, in which case the flow will be: // "https://engine/../idp/metadata" !== "https://original-idp/../idpmetadata" => true, gets added // "https://engine/../idp/metadata" !== "https://engine/../idp/metadata" => false, does not get added // "https://engine/../idp/metadata/vo:void" !== "https://engine/../idp/metadata" => TRUE, gets added! // This is a 'bug'/'feature' that we're keeping in for BWC reasons. $authenticatingAuthorities = $sourceAssertion->getAuthenticatingAuthority(); if ($this->getUrl('idpMetadataService') !== $sourceResponse->getIssuer()) { $authenticatingAuthorities[] = $sourceResponse->getIssuer(); } $newAssertion->setAuthenticatingAuthority($authenticatingAuthorities); // Copy over the attributes $newAssertion->setAttributes($sourceAssertion->getAttributes()); $newAssertion->setAttributeNameFormat(SAML2_Const::NAMEFORMAT_URI); return $newResponse; }