/** * Extract the response element from the SOAP response. * * @param string $soapResponse The SOAP response. * @return string The <saml1p:Response> element, as a string. */ private static function extractResponse($soapResponse) { assert('is_string($soapResponse)'); try { $doc = SAML2_DOMDocumentFactory::fromString($soapResponse); } catch (\Exception $e) { throw new SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.'); } $soapEnvelope = $doc->firstChild; if (!SimpleSAML\Utils\XML::isDOMElementOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) { throw new SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.'); } $soapBody = SimpleSAML\Utils\XML::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/'); if (count($soapBody) === 0) { throw new SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.'); } $soapBody = $soapBody[0]; $responseElement = SimpleSAML\Utils\XML::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol'); if (count($responseElement) === 0) { throw new SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.'); } $responseElement = $responseElement[0]; /* * Save the <saml1p:Response> element. Note that we need to import it * into a new document, in order to preserve namespace declarations. */ $newDoc = SAML2_DOMDocumentFactory::create(); $newDoc->appendChild($newDoc->importNode($responseElement, TRUE)); $responseXML = $newDoc->saveXML(); return $responseXML; }
/** * Send an authenticationResponse using HTTP-POST. * * @param string $response The response which should be sent. * @param SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response. * @param SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response. * @param string|null $relayState The relaystate for the SP. * @param string $shire The shire which should receive the response. */ public function sendResponse($response, SimpleSAML_Configuration $idpmd, SimpleSAML_Configuration $spmd, $relayState, $shire) { \SimpleSAML\Utils\XML::checkSAMLMessage($response, 'saml11'); $privatekey = SimpleSAML\Utils\Crypto::loadPrivateKey($idpmd, true); $publickey = SimpleSAML\Utils\Crypto::loadPublicKey($idpmd, true); $responsedom = new DOMDocument(); $responsedom->loadXML(str_replace("\r", "", $response)); $responseroot = $responsedom->getElementsByTagName('Response')->item(0); $firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0); /* Determine what we should sign - either the Response element or the Assertion. The default is to sign the * Assertion, but that can be overridden by the 'signresponse' option in the SP metadata or * 'saml20.signresponse' in the global configuration. * * TODO: neither 'signresponse' nor 'shib13.signresponse' are valid options any longer. Remove! */ if ($spmd->hasValue('signresponse')) { $signResponse = $spmd->getBoolean('signresponse'); } else { $signResponse = $this->configuration->getBoolean('shib13.signresponse', true); } // check if we have an assertion to sign. Force to sign the response if not if ($firstassertionroot === null) { $signResponse = true; } $signer = new SimpleSAML_XML_Signer(array('privatekey_array' => $privatekey, 'publickey_array' => $publickey, 'id' => $signResponse ? 'ResponseID' : 'AssertionID')); if ($idpmd->hasValue('certificatechain')) { $signer->addCertificate($idpmd->getString('certificatechain')); } if ($signResponse) { // sign the response - this must be done after encrypting the assertion // we insert the signature before the saml2p:Status element $statusElements = SimpleSAML\Utils\XML::getDOMChildren($responseroot, 'Status', '@saml1p'); assert('count($statusElements) === 1'); $signer->sign($responseroot, $responseroot, $statusElements[0]); } else { /* Sign the assertion */ $signer->sign($firstassertionroot, $firstassertionroot); } $response = $responsedom->saveXML(); \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'out'); \SimpleSAML\Utils\HTTP::submitPOSTData($shire, array('TARGET' => $relayState, 'SAMLResponse' => base64_encode($response))); }
/** * Parse an Extensions element. * * @param mixed $element The element which contains the Extensions element. * * @return array An associative array with the extensions parsed. */ private static function processExtensions($element) { $ret = array('scope' => array(), 'tags' => array(), 'EntityAttributes' => array(), 'UIInfo' => array(), 'DiscoHints' => array()); foreach ($element->Extensions as $e) { if ($e instanceof SAML2_XML_shibmd_Scope) { $ret['scope'][] = $e->scope; continue; } // Entity Attributes are only allowed at entity level extensions and not at RoleDescriptor level if ($element instanceof SAML2_XML_md_EntityDescriptor) { if ($e instanceof SAML2_XML_mdattr_EntityAttributes && !empty($e->children)) { foreach ($e->children as $attr) { // only saml:Attribute are currently supported here. The specifications also allows // saml:Assertions, which more complex processing. if ($attr instanceof SAML2_XML_saml_Attribute) { if (empty($attr->Name) || empty($attr->AttributeValue)) { continue; } // attribute names that is not URI is prefixed as this: '{nameformat}name' $name = $attr->Name; if (empty($attr->NameFormat)) { $name = '{' . SAML2_Const::NAMEFORMAT_UNSPECIFIED . '}' . $attr->Name; } elseif ($attr->NameFormat !== 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri') { $name = '{' . $attr->NameFormat . '}' . $attr->Name; } $values = array(); foreach ($attr->AttributeValue as $attrvalue) { $values[] = $attrvalue->getString(); } $ret['EntityAttributes'][$name] = $values; } } } } // UIInfo elements are only allowed at RoleDescriptor level extensions if ($element instanceof SAML2_XML_md_RoleDescriptor) { if ($e instanceof SAML2_XML_mdui_UIInfo) { $ret['UIInfo']['DisplayName'] = $e->DisplayName; $ret['UIInfo']['Description'] = $e->Description; $ret['UIInfo']['InformationURL'] = $e->InformationURL; $ret['UIInfo']['PrivacyStatementURL'] = $e->PrivacyStatementURL; foreach ($e->Keywords as $uiItem) { if (!$uiItem instanceof SAML2_XML_mdui_Keywords || empty($uiItem->Keywords) || empty($uiItem->lang)) { continue; } $ret['UIInfo']['Keywords'][$uiItem->lang] = $uiItem->Keywords; } foreach ($e->Logo as $uiItem) { if (!$uiItem instanceof SAML2_XML_mdui_Logo || empty($uiItem->url) || empty($uiItem->height) || empty($uiItem->width)) { continue; } $logo = array('url' => $uiItem->url, 'height' => $uiItem->height, 'width' => $uiItem->width); if (!empty($uiItem->lang)) { $logo['lang'] = $uiItem->lang; } $ret['UIInfo']['Logo'][] = $logo; } } } // DiscoHints elements are only allowed at IDPSSODescriptor level extensions if ($element instanceof SAML2_XML_md_IDPSSODescriptor) { if ($e instanceof SAML2_XML_mdui_DiscoHints) { $ret['DiscoHints']['IPHint'] = $e->IPHint; $ret['DiscoHints']['DomainHint'] = $e->DomainHint; $ret['DiscoHints']['GeolocationHint'] = $e->GeolocationHint; } } if (!$e instanceof SAML2_XML_Chunk) { continue; } if ($e->localName === 'Attribute' && $e->namespaceURI === SAML2_Const::NS_SAML) { $attribute = $e->getXML(); $name = $attribute->getAttribute('Name'); $values = array_map(array('SimpleSAML\\Utils\\XML', 'getDOMText'), SimpleSAML\Utils\XML::getDOMChildren($attribute, 'AttributeValue', '@saml2')); if ($name === 'tags') { foreach ($values as $tagname) { if (!empty($tagname)) { $ret['tags'][] = $tagname; } } } } } return $ret; }
/** * Parse an Extensions element. Extensions may appear in multiple elements and certain extension may get inherited * from a parent element. * * @param mixed $element The element which contains the Extensions element. * @param array $parentExtensions An optional array of extensions from the parent element. * * @return array An associative array with the extensions parsed. */ private static function processExtensions($element, $parentExtensions = array()) { $ret = array('scope' => array(), 'tags' => array(), 'EntityAttributes' => array(), 'RegistrationInfo' => array(), 'UIInfo' => array(), 'DiscoHints' => array()); // Some extensions may get inherited from a parent element if (($element instanceof \SAML2\XML\md\EntityDescriptor || $element instanceof \SAML2\XML\md\EntitiesDescriptor) && !empty($parentExtensions['RegistrationInfo'])) { $ret['RegistrationInfo'] = $parentExtensions['RegistrationInfo']; } foreach ($element->Extensions as $e) { if ($e instanceof \SAML2\XML\shibmd\Scope) { $ret['scope'][] = $e->scope; continue; } // Entity Attributes are only allowed at entity level extensions and not at RoleDescriptor level if ($element instanceof \SAML2\XML\md\EntityDescriptor || $element instanceof \SAML2\XML\md\EntitiesDescriptor) { if ($e instanceof \SAML2\XML\mdrpi\RegistrationInfo) { // Registration Authority cannot be overridden (warn only if override attempts to change the value) if (isset($ret['RegistrationInfo']['registrationAuthority']) && $ret['RegistrationInfo']['registrationAuthority'] !== $e->registrationAuthority) { SimpleSAML\Logger::warning('Invalid attempt to override registrationAuthority \'' . $ret['RegistrationInfo']['registrationAuthority'] . "' with '{$e->registrationAuthority}'"); } else { $ret['RegistrationInfo']['registrationAuthority'] = $e->registrationAuthority; } } if ($e instanceof \SAML2\XML\mdattr\EntityAttributes && !empty($e->children)) { foreach ($e->children as $attr) { // only saml:Attribute are currently supported here. The specifications also allows // saml:Assertions, which more complex processing if ($attr instanceof \SAML2\XML\saml\Attribute) { if (empty($attr->Name) || empty($attr->AttributeValue)) { continue; } // attribute names that is not URI is prefixed as this: '{nameformat}name' $name = $attr->Name; if (empty($attr->NameFormat)) { $name = '{' . \SAML2\Constants::NAMEFORMAT_UNSPECIFIED . '}' . $attr->Name; } elseif ($attr->NameFormat !== 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri') { $name = '{' . $attr->NameFormat . '}' . $attr->Name; } $values = array(); foreach ($attr->AttributeValue as $attrvalue) { $values[] = $attrvalue->getString(); } $ret['EntityAttributes'][$name] = $values; } } } } // UIInfo elements are only allowed at RoleDescriptor level extensions if ($element instanceof \SAML2\XML\md\RoleDescriptor) { if ($e instanceof \SAML2\XML\mdui\UIInfo) { $ret['UIInfo']['DisplayName'] = $e->DisplayName; $ret['UIInfo']['Description'] = $e->Description; $ret['UIInfo']['InformationURL'] = $e->InformationURL; $ret['UIInfo']['PrivacyStatementURL'] = $e->PrivacyStatementURL; foreach ($e->Keywords as $uiItem) { if (!$uiItem instanceof \SAML2\XML\mdui\Keywords || empty($uiItem->Keywords) || empty($uiItem->lang)) { continue; } $ret['UIInfo']['Keywords'][$uiItem->lang] = $uiItem->Keywords; } foreach ($e->Logo as $uiItem) { if (!$uiItem instanceof \SAML2\XML\mdui\Logo || empty($uiItem->url) || empty($uiItem->height) || empty($uiItem->width)) { continue; } $logo = array('url' => $uiItem->url, 'height' => $uiItem->height, 'width' => $uiItem->width); if (!empty($uiItem->lang)) { $logo['lang'] = $uiItem->lang; } $ret['UIInfo']['Logo'][] = $logo; } } } // DiscoHints elements are only allowed at IDPSSODescriptor level extensions if ($element instanceof \SAML2\XML\md\IDPSSODescriptor) { if ($e instanceof \SAML2\XML\mdui\DiscoHints) { $ret['DiscoHints']['IPHint'] = $e->IPHint; $ret['DiscoHints']['DomainHint'] = $e->DomainHint; $ret['DiscoHints']['GeolocationHint'] = $e->GeolocationHint; } } if (!$e instanceof \SAML2\XML\Chunk) { continue; } if ($e->localName === 'Attribute' && $e->namespaceURI === \SAML2\Constants::NS_SAML) { $attribute = $e->getXML(); $name = $attribute->getAttribute('Name'); $values = array_map(array('SimpleSAML\\Utils\\XML', 'getDOMText'), SimpleSAML\Utils\XML::getDOMChildren($attribute, 'AttributeValue', '@saml2')); if ($name === 'tags') { foreach ($values as $tagname) { if (!empty($tagname)) { $ret['tags'][] = $tagname; } } } } } return $ret; }
/** * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::getDOMChildren() instead. */ public static function getDOMChildren(DOMElement $element, $localName, $namespaceURI) { return SimpleSAML\Utils\XML::getDOMChildren($element, $localName, $namespaceURI); }