tagName = $tagName; $this->id = SimpleSAML_Utilities::generateID(); $this->issueInstant = time(); $this->certificates = array(); $this->validators = array(); if ($xml === NULL) { return; } if (!$xml->hasAttribute('ID')) { throw new Exception('Missing ID attribute on SAML message.'); } $this->id = $xml->getAttribute('ID'); if ($xml->getAttribute('Version') !== '2.0') { /* Currently a very strict check. */ throw new Exception('Unsupported version: ' . $xml->getAttribute('Version')); } $this->issueInstant = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('IssueInstant')); if ($xml->hasAttribute('Destination')) { $this->destination = $xml->getAttribute('Destination'); } $issuer = SAML2_Utils::xpQuery($xml, './saml_assertion:Issuer'); if (!empty($issuer)) { $this->issuer = trim($issuer[0]->textContent); } /* Validate the signature element of the message. */ try { $sig = SAML2_Utils::validateElement($xml); if ($sig !== FALSE) { $this->certificates = $sig['Certificates']; $this->validators[] = array( 'Function' => array('SAML2_Utils', 'validateSignature'), 'Data' => $sig, ); } } catch (Exception $e) { /* Ignore signature validation errors. */ } } /** * Add a method for validating this message. * * This function is used by the HTTP-Redirect binding, to make it possible to * check the signature against the one included in the query string. * * @param callback $function The function which should be called. * @param mixed $data The data that should be included as the first parameter to the function. */ public function addValidator($function, $data) { assert('is_callable($function)'); $this->validators[] = array( 'Function' => $function, 'Data' => $data, ); } /** * Validate this message against a public key. * * TRUE is returned on success, FALSE is returned if we don't have any * signature we can validate. An exception is thrown if the signature * validation fails. * * @param XMLSecurityKey $key The key we should check against. * @return boolean TRUE on success, FALSE when we don't have a signature. */ public function validate(XMLSecurityKey $key) { if (count($this->validators) === 0) { return FALSE; } $exceptions = array(); foreach ($this->validators as $validator) { $function = $validator['Function']; $data = $validator['Data']; try { call_user_func($function, $data, $key); /* We were able to validate the message with this validator. */ return TRUE; } catch (Exception $e) { $exceptions[] = $e; } } /* No validators were able to validate the message. */ throw $exceptions[0]; } /** * Retrieve the identifier of this message. * * @return string The identifier of this message. */ public function getId() { return $this->id; } /** * Set the identifier of this message. * * @param string $id The new identifier of this message. */ public function setId($id) { assert('is_string($id)'); $this->id = $id; } /** * Retrieve the issue timestamp of this message. * * @return int The issue timestamp of this message, as an UNIX timestamp. */ public function getIssueInstant() { return $this->issueInstant; } /** * Set the issue timestamp of this message. * * @param int $issueInstant The new issue timestamp of this message, as an UNIX timestamp. */ public function setIssueInstant($issueInstant) { assert('is_int($issueInstant)'); $this->issueInstant = $issueInstant; } /** * Retrieve the destination of this message. * * @return string|NULL The destination of this message, or NULL if no destination is given. */ public function getDestination() { return $this->destination; } /** * Set the destination of this message. * * @param string|NULL $destination The new destination of this message. */ public function setDestination($destination) { assert('is_string($destination) || is_null($destination)'); $this->destination = $destination; } /** * Retrieve the issuer if this message. * * @return string|NULL The issuer of this message, or NULL if no issuer is given. */ public function getIssuer() { return $this->issuer; } /** * Set the issuer of this message. * * @param string|NULL $issuer The new issuer of this message. */ public function setIssuer($issuer) { assert('is_string($issuer) || is_null($issuer)'); $this->issuer = $issuer; } /** * Retrieve the RelayState associated with this message. * * @return string|NULL The RelayState, or NULL if no RelayState is given. */ public function getRelayState() { return $this->relayState; } /** * Set the RelayState associated with this message. * * @param string|NULL $relayState The new RelayState. */ public function setRelayState($relayState) { assert('is_string($relayState) || is_null($relayState)'); $this->relayState = $relayState; } /** * Convert this message to an unsigned XML document. * * This method does not sign the resulting XML document. * * @return DOMElement The root element of the DOM tree. */ public function toUnsignedXML() { $this->document = new DOMDocument(); $root = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'samlp:' . $this->tagName); $this->document->appendChild($root); /* Ugly hack to add another namespace declaration to the root element. */ $root->setAttributeNS(SAML2_Const::NS_SAML, 'saml:tmp', 'tmp'); $root->removeAttributeNS(SAML2_Const::NS_SAML, 'tmp'); $root->setAttribute('ID', $this->id); $root->setAttribute('Version', '2.0'); $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant)); if ($this->destination !== NULL) { $root->setAttribute('Destination', $this->destination); } if ($this->issuer !== NULL) { SAML2_Utils::addString($root, SAML2_Const::NS_SAML, 'saml:Issuer', $this->issuer); } return $root; } /** * Convert this message to a signed XML document. * * This method sign the resulting XML document if the private key for * the signature is set. * * @return DOMElement The root element of the DOM tree. */ public function toSignedXML() { $root = $this->toUnsignedXML(); if ($this->signatureKey === NULL) { /* We don't have a key to sign it with. */ return $root; } /* Find the position we should insert the signature node at. */ if ($this->issuer !== NULL) { /* * We have an issuer node. The signature node should come * after the issuer node. */ $issuerNode = $root->firstChild; $insertBefore = $issuerNode->nextSibling; } else { /* No issuer node - the signature element should be the first element. */ $insertBefore = $root->firstChild; } SAML2_Utils::insertSignature($this->signatureKey, $this->certificates, $root, $insertBefore); return $root; } /** * Retrieve the private key we should use to sign the message. * * @return XMLSecurityKey|NULL The key, or NULL if no key is specified. */ public function getSignatureKey() { return $this->signatureKey; } /** * Set the private key we should use to sign the message. * * If the key is NULL, the message will be sent unsigned. * * @param XMLSecurityKey|NULL $key */ public function setSignatureKey(XMLsecurityKey $signatureKey = NULL) { $this->signatureKey = $signatureKey; } /** * Set the certificates that should be included in the message. * * The certificates should be strings with the PEM encoded data. * * @param array $certificates An array of certificates. */ public function setCertificates(array $certificates) { $this->certificates = $certificates; } /** * Retrieve the certificates that are included in the message. * * @return array An array of certificates. */ public function getCertificates() { return $this->certificates; } /** * Convert an XML element into a message. * * @param DOMElement $xml The root XML element. * @return SAML2_Message The message. */ public static function fromXML(DOMElement $xml) { if ($xml->namespaceURI !== SAML2_Const::NS_SAMLP) { throw new Exception('Unknown namespace of SAML message: ' . var_export($xml->namespaceURI, TRUE)); } switch ($xml->localName) { case 'AttributeQuery': return new SAML2_AttributeQuery($xml); case 'AuthnRequest': return new SAML2_AuthnRequest($xml); case 'LogoutResponse': return new SAML2_LogoutResponse($xml); case 'LogoutRequest': return new SAML2_LogoutRequest($xml); case 'Response': return new SAML2_Response($xml); case 'ArtifactResponse': return new SAML2_ArtifactResponse($xml); case 'ArtifactResolve': return new SAML2_ArtifactResolve($xml); default: throw new Exception('Unknown SAML message: ' . var_export($xml->localName, TRUE)); } } } ?>