public static getDOMChildren ( DOMElement $element, $localName, $namespaceURI ) | ||
$element | DOMElement |
/** * 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)'); $doc = new DOMDocument(); if (!$doc->loadXML($soapResponse)) { throw new SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.'); } $soapEnvelope = $doc->firstChild; if (!SimpleSAML_Utilities::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_Utilities::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_Utilities::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 = new DOMDocument(); $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 array $idpmd The metadata of the IdP which is sending the response. * @param array $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, $idpmd, $spmd, $relayState, $shire) { SimpleSAML_Utilities::validateXMLDocument($response, 'saml11'); $privatekey = SimpleSAML_Utilities::loadPrivateKey($idpmd, TRUE); $publickey = SimpleSAML_Utilities::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. */ $signResponse = FALSE; if (array_key_exists('signresponse', $spmd) && $spmd['signresponse'] !== NULL) { $signResponse = $spmd['signresponse']; if (!is_bool($signResponse)) { throw new Exception('Expected the \'signresponse\' option in the metadata of the' . ' SP \'' . $spmd['entityid'] . '\' to be a boolean value.'); } } 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 (array_key_exists('certificatechain', $idpmd)) { $signer->addCertificate($idpmd['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_Utilities::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(); if ($this->configuration->getBoolean('debug', FALSE)) { $p = new SimpleSAML_XHTML_Template($this->configuration, 'post-debug.php'); $p->data['header'] = 'SAML (Shibboleth 1.3) Response Debug-mode'; $p->data['RelayStateName'] = 'TARGET'; $p->data['RelayState'] = $relayState; $p->data['destination'] = $shire; $p->data['response'] = str_replace("\n", "", base64_encode($response)); $p->data['responseHTML'] = htmlspecialchars(SimpleSAML_Utilities::formatXMLString($response)); $p->show(); } else { SimpleSAML_Utilities::postRedirect($shire, array('TARGET' => $relayState, 'SAMLResponse' => base64_encode($response))); } }
/** * 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_Utilities::validateXMLDocument($response, 'saml11'); $privatekey = SimpleSAML_Utilities::loadPrivateKey($idpmd, TRUE); $publickey = SimpleSAML_Utilities::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. */ $signResponse = FALSE; 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_Utilities::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_Utilities::debugMessage($response, 'out'); SimpleSAML_Utilities::postRedirect($shire, array('TARGET' => $relayState, 'SAMLResponse' => base64_encode($response))); }
/** * Parse an Extensions element. * * @param mixed $element The element which contains the Extensions element. */ private static function processExtensions($element) { $ret = array('scope' => array(), 'tags' => array()); foreach ($element->Extensions as $e) { if ($e instanceof SAML2_XML_shibmd_Scope) { $ret['scope'][] = $e->scope; continue; } 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_Utilities', 'getDOMText'), SimpleSAML_Utilities::getDOMChildren($attribute, 'AttributeValue', '@saml2')); if ($name === 'tags') { foreach ($values as $tagname) { if (!empty($tagname)) { $ret['tags'][] = $tagname; } } } } } return $ret; }
/** * Parse an Extensions element. * * @param mixed $element The element which contains the Extensions element. */ private static function processExtensions($element) { $ret = array('scope' => array(), 'tags' => array(), 'entityAttributes' => 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) { 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; } } } } 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_Utilities', 'getDOMText'), SimpleSAML_Utilities::getDOMChildren($attribute, 'AttributeValue', '@saml2')); if ($name === 'tags') { foreach ($values as $tagname) { if (!empty($tagname)) { $ret['tags'][] = $tagname; } } } } } return $ret; }
/** * Parse an Extensions element. * * @param mixed $element The element which contains the Extensions element. */ 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_Utilities', 'getDOMText'), SimpleSAML_Utilities::getDOMChildren($attribute, 'AttributeValue', '@saml2')); if ($name === 'tags') { foreach ($values as $tagname) { if (!empty($tagname)) { $ret['tags'][] = $tagname; } } } } } return $ret; }
/** * This function parses a KeyDescriptor element. It currently only supports keys with a single * X509 certificate. * * The associative array for a key can contain: * - 'encryption': Indicates wheter this key can be used for encryption. * - 'signing': Indicates wheter this key can be used for signing. * - 'type: The type of the key. 'X509Certificate' is the only key type we support. * - 'X509Certificate': The contents of the first X509Certificate element (if the type is 'X509Certificate '). * * @param $kd The KeyDescriptor element. * @return Associative array describing the key, or NULL if this is an unsupported key. */ private static function parseKeyDescriptor($kd) { assert('$kd instanceof DOMElement'); $r = array(); if ($kd->hasAttribute('use')) { $use = $kd->getAttribute('use'); if ($use === 'encryption') { $r['encryption'] = TRUE; $r['signing'] = FALSE; } elseif ($use === 'signing') { $r['encryption'] = FALSE; $r['signing'] = TRUE; } else { throw new Exception('Invalid use-value for KeyDescriptor: ' . $use); } } else { $r['encryption'] = TRUE; $r['signing'] = TRUE; } $keyInfo = SimpleSAML_Utilities::getDOMChildren($kd, 'KeyInfo', '@ds'); if (count($keyInfo) === 0) { throw new Exception('Missing required KeyInfo field for KeyDescriptor.'); } $keyInfo = $keyInfo[0]; $X509Data = SimpleSAML_Utilities::getDOMChildren($keyInfo, 'X509Data', '@ds'); if (count($X509Data) === 0) { return NULL; } $X509Data = $X509Data[0]; $X509Certificate = SimpleSAML_Utilities::getDOMChildren($X509Data, 'X509Certificate', '@ds'); if (count($X509Certificate) === 0) { return NULL; } $X509Certificate = $X509Certificate[0]; $r['type'] = 'X509Certificate'; $r['X509Certificate'] = SimpleSAML_Utilities::getDOMText($X509Certificate); return $r; }