diff --git a/README.md b/README.md index 1f4970e..8f4fe7a 100644 --- a/README.md +++ b/README.md @@ -33,17 +33,13 @@ Installation The recommended way to install goetas-webservices / soap-client is using [Composer](https://getcomposer.org/): -Add this packages to your `composer.json` file. ``` -{ - "require": { - "goetas-webservices/soap-client": "^0.2", - }, - "require-dev": { - "goetas-webservices/wsdl2php": "^0.3", - }, -} +composer require goetas-webservices/soap-client +composer require goetas-webservices/wsdl2php --dev + +# to use WS Security +composer require ass/xmlsecurity ``` More dependencies might be needed depending on your PSR-7 and HTTP client preferred implementation. @@ -65,7 +61,7 @@ Here is an example: # config.yml soap_client: - alternative_endpoints: + alternative_endpoints: # optional MyServiceName: MySoapPortName: http://localhost:8080/service @@ -75,7 +71,7 @@ soap_client: 'TestNs/MyApp': soap/src destinations_jms: 'TestNs/MyApp': soap/metadata - aliases: + aliases: # optional 'http://www.example.org/test/': MyCustomXSDType: 'MyCustomMappedPHPType' diff --git a/composer.json b/composer.json index 42934b7..5e47dbb 100644 --- a/composer.json +++ b/composer.json @@ -23,12 +23,20 @@ "require-dev": { "phpunit/phpunit": "^4.8|^5.0", - "goetas-webservices/wsdl2php": "^0.3", + "goetas-webservices/wsdl2php": "^0.4", + "goetas-webservices/xsd2php": "^0.2.3", + + "jms/serializer": "dev-xml-namespaces-improvements as 1.5.0", + "goetas-webservices/wsdl-reader": "^v0.3.1", + "ass/xmlsecurity" : "^1.0", "php-http/guzzle6-adapter": "^1.0", "php-http/message": "^1.0" }, + "suggest": { + "ass/xmlsecurity" : "Required for WS Security features" + }, "autoload": { "psr-4": { "GoetasWebservices\\SoapServices\\SoapClient\\": "src" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ab62156..44dfb05 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -22,12 +22,14 @@ - - - src - - - + + + src + + src/WssWsSecurity/XmlSign + + + diff --git a/src/Arguments/Headers/Handler/HeaderHandler.php b/src/Arguments/Headers/Handler/HeaderHandler.php index 72d3efa..ed66589 100644 --- a/src/Arguments/Headers/Handler/HeaderHandler.php +++ b/src/Arguments/Headers/Handler/HeaderHandler.php @@ -9,13 +9,9 @@ use JMS\Serializer\SerializationContext; use JMS\Serializer\XmlDeserializationVisitor; use JMS\Serializer\XmlSerializationVisitor; -use Symfony\Component\DependencyInjection\SimpleXMLElement; class HeaderHandler implements SubscribingHandlerInterface { - const SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'; - const SOAP_12 = 'http://www.w3.org/2003/05/soap-envelope'; - protected $headerData = []; public static function getSubscribingMethods() @@ -67,7 +63,7 @@ public function serializeHeader(XmlSerializationVisitor $visitor, HeaderPlacehol $metadata = new StaticPropertyMetadata($classMetadata->name, $classMetadata->xmlRootName, $header->getData()); $metadata->xmlNamespace = $classMetadata->xmlRootNamespace; - $metadata->serializedName = $classMetadata->xmlRootName; + $metadata->serializedName = $classMetadata->xmlRootName ?: 'header'; $visitor->visitProperty($metadata, $header->getData(), $context); @@ -88,12 +84,7 @@ private function handleOptions(XmlSerializationVisitor $visitor, $header) foreach ($options as $option => $value) { if (in_array($option, ['mustUnderstand', 'required', 'role', 'actor'])) { - if ($currentNode->ownerDocument->documentElement->namespaceURI === self::SOAP_12) { - $envelopeNS = self::SOAP_12; - } else { - $envelopeNS = self::SOAP; - } - $this->setAttributeOnNode($currentNode->lastChild, $option, $value, $envelopeNS); + $this->setAttributeOnNode($currentNode->lastChild, $option, $value, $currentNode->ownerDocument->documentElement->namespaceURI); } } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 1c7483b..1969d6a 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -83,7 +83,12 @@ public function getConfigTreeBuilder() ->prototype('scalar')->end() ->end() - + ->scalarNode('any_element') + ->defaultValue(false) + ->end() + ->scalarNode('any_attribute') + ->defaultValue(false) + ->end() diff --git a/src/DependencyInjection/SoapClientExtension.php b/src/DependencyInjection/SoapClientExtension.php index 3b01419..74afc9c 100644 --- a/src/DependencyInjection/SoapClientExtension.php +++ b/src/DependencyInjection/SoapClientExtension.php @@ -42,7 +42,11 @@ public function load(array $configs, ContainerBuilder $container) foreach (['php', 'jms'] as $type) { + $converter = $container->getDefinition('goetas_webservices.xsd2php.converter.' . $type); + + $converterWsdl = $container->getDefinition('goetas_webservices.wsdl2php.converter.' . $type); + foreach ($config['namespaces'] as $xml => $php) { $converter->addMethodCall('addNamespace', [$xml, self::sanitizePhp($php)]); } @@ -53,6 +57,11 @@ public function load(array $configs, ContainerBuilder $container) } } + $schemaReader = $container->getDefinition('goetas_webservices.xsd2php.schema_reader'); + foreach ($config['known_locations'] as $namespace => $location) { + $schemaReader->addMethodCall('addKnownSchemaLocation', [$namespace, $location]); + } + $definition = $container->getDefinition('goetas_webservices.xsd2php.naming_convention.' . $config['naming_strategy']); $container->setDefinition('goetas_webservices.xsd2php.naming_convention', $definition); diff --git a/src/WssWsSecurity/Exception/ClientException.php b/src/WssWsSecurity/Exception/ClientException.php new file mode 100644 index 0000000..747653f --- /dev/null +++ b/src/WssWsSecurity/Exception/ClientException.php @@ -0,0 +1,9 @@ +password; + } + + /** + * @param string $password + * @param int $passwordType + */ + public function setPassword($password, $passwordType = self::PASSWORD_TYPE_DIGEST) + { + $this->password = $password; + $this->passwordType = $passwordType; + } + + public function isPasswordDigest() + { + return $this->passwordType === self::PASSWORD_TYPE_DIGEST; + } + + public function isPasswordPlain() + { + return $this->passwordType === self::PASSWORD_TYPE_TEXT; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } +} diff --git a/src/WssWsSecurity/SecurityKeyPair.php b/src/WssWsSecurity/SecurityKeyPair.php new file mode 100644 index 0000000..31e43ea --- /dev/null +++ b/src/WssWsSecurity/SecurityKeyPair.php @@ -0,0 +1,101 @@ +privateKey = XmlSecurityKey::factory($encryptionType, $key, $keyIsFile, XmlSecurityKey::TYPE_PRIVATE, $passphrase); + } + + /** + * Add public key. + * + * @param string $encryptionType Encryption type + * @param string $key Public key + * @param boolean $keyIsFile Given key parameter is path to key file + * + * @return void + */ + public function setPublicKey($encryptionType, $key = null, $keyIsFile = true) + { + $this->publicKey = XmlSecurityKey::factory($encryptionType, $key, $keyIsFile, XmlSecurityKey::TYPE_PUBLIC); + } + + /** + * Get private key. + * + * @return \ass\XmlSecurity\Key + */ + public function getPrivateKey() + { + return $this->privateKey; + } + + /** + * Get public key. + * + * @return \ass\XmlSecurity\Key + */ + public function getPublicKey() + { + return $this->publicKey; + } + + /** + * Has private and public key? + * + * @return boolean + */ + public function hasKeys() + { + return null !== $this->privateKey && null !== $this->publicKey; + } + + /** + * Has private key? + * + * @return boolean + */ + public function hasPrivateKey() + { + return null !== $this->privateKey; + } + + /** + * Has public key? + * + * @return boolean + */ + public function hasPublicKey() + { + return null !== $this->publicKey; + } +} diff --git a/src/WssWsSecurity/Serializer/AbstractWsSecurityFilter.php b/src/WssWsSecurity/Serializer/AbstractWsSecurityFilter.php new file mode 100644 index 0000000..09b1e02 --- /dev/null +++ b/src/WssWsSecurity/Serializer/AbstractWsSecurityFilter.php @@ -0,0 +1,55 @@ +serviceSecurityKey = $serviceSecurityKey; + } + + /** + * Set user security key. + * + * @param SecurityKeyPair $userSecurityKey User security key + * + * @return void + */ + public function setUserSecurityKeyObject(SecurityKeyPair $userSecurityKey = null) + { + $this->userSecurityKey = $userSecurityKey; + } +} diff --git a/src/WssWsSecurity/Serializer/WsSecurityFilterRequest.php b/src/WssWsSecurity/Serializer/WsSecurityFilterRequest.php new file mode 100644 index 0000000..00f3952 --- /dev/null +++ b/src/WssWsSecurity/Serializer/WsSecurityFilterRequest.php @@ -0,0 +1,459 @@ +addTimestamp = $addTimestamp; + $this->expires = $expires; + } + + /** + * @param \DateTime $initialTimestamp + */ + public function __construct(\DateTime $initialTimestamp = null) + { + $this->initialTimestamp = $initialTimestamp; + } + + /** + * Set security options. + * + * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 + * @param boolean $encryptSignature Encrypt signature + * + * @return void + */ + public function setSecurityOptionsEncryption($tokenReference, $encryptSignature = false) + { + $this->tokenReferenceEncryption = $tokenReference; + $this->encryptSignature = $encryptSignature; + } + + /** + * Set security options. + * + * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 + * @param boolean $signAllHeaders Sign all headers? + * + * @return void + */ + public function setSecurityOptionsSignature($tokenReference, $signAllHeaders = false) + { + $this->tokenReferenceSignature = $tokenReference; + $this->signAllHeaders = $signAllHeaders; + } + + /** + * Adds the configured KeyInfo to the parentNode. + * + * @param \DOMDocument $dom + * @param int $tokenReference Token reference type + * @param string $guid Unique ID + * @param XmlSecurityKey $xmlSecurityKey XML security key + * + * @return \DOMElement + */ + private function createKeyInfo(\DOMDocument $dom, $tokenReference, $guid, XmlSecurityKey $xmlSecurityKey = null) + { + $keyInfo = $dom->createElementNS(XmlSecurityDSig::NS_XMLDSIG, 'KeyInfo'); + $securityTokenReference = $dom->createElementNS(self::NS_WSS, 'SecurityTokenReference'); + $keyInfo->appendChild($securityTokenReference); + // security token + if (self::TOKEN_REFERENCE_SECURITY_TOKEN === $tokenReference) { + $reference = $dom->createElementNS(self::NS_WSS, 'Reference'); + $reference->setAttribute('URI', '#' . $guid); + if (null !== $xmlSecurityKey) { + $reference->setAttribute('ValueType', self::NAME_WSS_X509 . '#X509v3'); + } + $securityTokenReference->appendChild($reference); + // subject key identifier + } elseif (self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER === $tokenReference && null !== $xmlSecurityKey) { + $keyIdentifier = $dom->createElementNS(self::NS_WSS, 'KeyIdentifier'); + $keyIdentifier->setAttribute('EncodingType', self::NAME_WSS_SMS . '#Base64Binary'); + $keyIdentifier->setAttribute('ValueType', self::NAME_WSS_X509 . '#509SubjectKeyIdentifier'); + $securityTokenReference->appendChild($keyIdentifier); + $certificate = $xmlSecurityKey->getX509SubjectKeyIdentifier(); + $dataNode = new \DOMText($certificate); + $keyIdentifier->appendChild($dataNode); + // thumbprint sha1 + } elseif (self::TOKEN_REFERENCE_THUMBPRINT_SHA1 === $tokenReference && null !== $xmlSecurityKey) { + $keyIdentifier = $dom->createElementNS(self::NS_WSS, 'KeyIdentifier'); + $keyIdentifier->setAttribute('EncodingType', self::NAME_WSS_SMS . '#Base64Binary'); + $keyIdentifier->setAttribute('ValueType', self::NAME_WSS_SMS_1_1 . '#ThumbprintSHA1'); + $securityTokenReference->appendChild($keyIdentifier); + $thumbprintSha1 = base64_encode(sha1(base64_decode($xmlSecurityKey->getX509Certificate(true)), true)); + $dataNode = new \DOMText($thumbprintSha1); + $keyIdentifier->appendChild($dataNode); + } + + return $keyInfo; + } + + /** + * Create a list of \DOMNodes that should be encrypted. + * + * @param \DOMDocument $dom DOMDocument to query + * + * @return \DOMNodeList + */ + private function createNodeListForEncryption(\DOMDocument $dom) + { + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('SOAP-ENV', $dom->documentElement->namespaceURI); + $xpath->registerNamespace('ds', XmlSecurityDSig::NS_XMLDSIG); + if ($this->encryptSignature === true) { + $query = '//ds:Signature | //SOAP-ENV:Body'; + } else { + $query = '//SOAP-ENV:Body'; + } + + return $xpath->query($query); + } + + /** + * Create a list of \DOMNodes that should be signed. + * + * @param \DOMDocument $dom DOMDocument to query + * @param \DOMElement $security Security element + * + * @return array(\DOMNode) + */ + private function createNodeListForSigning(\DOMDocument $dom, \DOMElement $security) + { + $nodes = array(); + $body = $dom->getElementsByTagNameNS($dom->documentElement->namespaceURI, 'Body')->item(0); + if (null !== $body) { + $nodes[] = $body; + } + foreach ($security->childNodes as $node) { + if (XML_ELEMENT_NODE === $node->nodeType) { + $nodes[] = $node; + } + } + if ($this->signAllHeaders) { + foreach ($security->parentNode->childNodes as $node) { + if (XML_ELEMENT_NODE === $node->nodeType && + self::NS_WSS !== $node->namespaceURI + ) { + $nodes[] = $node; + } + } + } + return $nodes; + } + + + /** + * Modify the given request XML. + * + * @param \DOMElement $currentNode, + * @param Security $securityData + * + * @return \DOMElement + */ + public function filterDom(\DOMElement $currentNode, Security $securityData) + { + $dom = $currentNode->ownerDocument; + $root = $dom->documentElement; + + $namespaces = array( + 'ws' => self::NS_WSS, + 'wsu' => self::NS_WSU, + XmlSecurityDSig::PFX_XMLDSIG => XmlSecurityDSig::NS_XMLDSIG, + XmlSecurityEnc::PFX_XMLENC => XmlSecurityEnc::NS_XMLENC, + ); + + foreach ($namespaces as $prefix => $ns) { + $root->setAttributeNS( + 'http://www.w3.org/2000/xmlns/', // xmlns namespace URI + 'xmlns:'.$prefix, + $ns + ); + } + + $security = $dom->createElementNS(self::NS_WSS, $root->lookupPrefix(self::NS_WSS).':Security'); + $currentNode->parentNode->replaceChild($security, $currentNode); + + // init timestamp + $dt = $this->initialTimestamp ?: new \DateTime('now', new \DateTimeZone('UTC')); + + if (true === $this->addTimestamp || null !== $this->expires) { + $this->handleTimestamp($security, $dt); + } + + if (null !== $securityData->getUsername()) { + $this->handleUsername($security, $dt, $securityData); + } + + if (null !== $this->userSecurityKey && $this->userSecurityKey->hasKeys()) { + $signature = $this->handleSignature($security); + + // encrypt soap document + if (null !== $this->serviceSecurityKey && $this->serviceSecurityKey->hasKeys()) { + $this->handleEncryption($security, $signature); + } + } + return $security; + } + + /** + * Generate a pseudo-random version 4 UUID. + * + * @see http://de.php.net/manual/en/function.uniqid.php#94959 + * + * @return string + */ + private static function generateUUID() + { + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * @param \DOMElement $security + * @param \DateTime $dt + */ + private function handleTimestamp(\DOMElement $security, \DateTime $dt) + { + $dom = $security->ownerDocument; + $timestamp = $dom->createElementNS(self::NS_WSU, 'Timestamp'); + $created = $dom->createElementNS(self::NS_WSU, 'Created', $dt->format(self::DATETIME_FORMAT)); + $timestamp->appendChild($created); + if (null !== $this->expires) { + $dt = clone $dt; + $dt->modify('+' . $this->expires . ' seconds'); + $expiresTimestamp = $dt->format(self::DATETIME_FORMAT); + $expires = $dom->createElementNS(self::NS_WSU, 'Expires', $expiresTimestamp); + $timestamp->appendChild($expires); + } + $security->appendChild($timestamp); + } + + /** + * @param \DOMElement $security + * @param $dt + * @param Security $securityData + */ + private function handleUsername(\DOMElement $security, $dt, Security $securityData) + { + $dom = $security->ownerDocument; + + $usernameToken = $dom->createElementNS(self::NS_WSS, 'UsernameToken'); + $security->appendChild($usernameToken); + + $username = $dom->createElementNS(self::NS_WSS, 'Username', $securityData->getUsername()); + $usernameToken->appendChild($username); + + if (null !== $securityData->getPassword() + && (null === $this->userSecurityKey + || (null !== $this->userSecurityKey && !$this->userSecurityKey->hasPrivateKey())) + ) { + + if ($securityData->isPasswordDigest()) { + $nonce = mt_rand(); + $password = base64_encode(sha1($nonce . $dt->format(self::DATETIME_FORMAT) . $securityData->getPassword(), true)); + $passwordType = self::NAME_WSS_UTP . '#PasswordDigest'; + } else { + $password = $securityData->getPassword(); + $passwordType = self::NAME_WSS_UTP . '#PasswordText'; + } + + $password = $dom->createElementNS(self::NS_WSS, 'Password', $password); + $password->setAttribute('Type', $passwordType); + $usernameToken->appendChild($password); + if ($securityData->isPasswordDigest()) { + $nonce = $dom->createElementNS(self::NS_WSS, 'Nonce', base64_encode($nonce)); + $usernameToken->appendChild($nonce); + + $created = $dom->createElementNS(self::NS_WSU, 'Created', $dt->format(self::DATETIME_FORMAT)); + $usernameToken->appendChild($created); + } + } + } + + /** + * @param \DOMElement $security + * @return \DOMElement + */ + private function handleSignature(\DOMElement $security) + { + $dom = $security->ownerDocument; + + // this is fundamental for the signature + // formatting the dom adds nodes that are not signed + $dom->formatOutput = false; + $dom->preserveWhiteSpace = true; + + $guid = 'CertId-' . self::generateUUID(); + // add token references + $keyInfo = null; + if (null !== $this->tokenReferenceSignature) { + $keyInfo = $this->createKeyInfo($dom, $this->tokenReferenceSignature, $guid, $this->userSecurityKey->getPublicKey()); + } + $nodes = $this->createNodeListForSigning($dom, $security); + + + $signature = XmlSecurityDSig::createSignature($this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N, $security, null, $keyInfo); + + if ((!$prefix = $security->lookupPrefix(self::NS_WSU)) && (!$prefix = $security->ownerDocument->lookupPrefix(self::NS_WSU))) { + $prefix = 'ns-'. substr(sha1(self::NS_WSU), 0, 8); + } + + $options = array( + 'id_ns_prefix' => $prefix, + 'id_prefix_ns' => self::NS_WSU, + ); + foreach ($nodes as $node) { + XmlSecurityDSig::addNodeToSignature($signature, $node, XmlSecurityDSig::SHA1, XmlSecurityDSig::EXC_C14N, $options); + } + XmlSecurityDSig::signDocument($signature, $this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N); + + $publicCertificate = $this->userSecurityKey->getPublicKey()->getX509Certificate(true); + $binarySecurityToken = $dom->createElementNS(self::NS_WSS, 'BinarySecurityToken', $publicCertificate); + $binarySecurityToken->setAttribute('EncodingType', self::NAME_WSS_SMS . '#Base64Binary'); + $binarySecurityToken->setAttribute('ValueType', self::NAME_WSS_X509 . '#X509v3'); + + $security->insertBefore($binarySecurityToken, $signature); + + $binarySecurityToken->setAttributeNs(self::NS_WSU, $prefix.':Id', $guid); + + return $signature; + } + + /** + * @param \DOMElement $security + * @param \DOMElement $signature + */ + private function handleEncryption(\DOMElement $security, \DOMElement $signature) + { + $dom = $security->ownerDocument; + $guid = 'EncKey-' . self::generateUUID(); + // add token references + $keyInfo = null; + if (null !== $this->tokenReferenceEncryption) { + $keyInfo = $this->createKeyInfo($dom, $this->tokenReferenceEncryption, $guid, $this->serviceSecurityKey->getPublicKey()); + } + $encryptedKey = XmlSecurityEnc::createEncryptedKey($guid, $this->serviceSecurityKey->getPrivateKey(), $this->serviceSecurityKey->getPublicKey(), $security, $signature, $keyInfo); + $referenceList = XmlSecurityEnc::createReferenceList($encryptedKey); + // token reference to encrypted key + $keyInfo = $this->createKeyInfo($dom, self::TOKEN_REFERENCE_SECURITY_TOKEN, $guid); + $nodes = $this->createNodeListForEncryption($dom); + foreach ($nodes as $node) { + $type = XmlSecurityEnc::ELEMENT; + if ($node->localName == 'Body') { + $type = XmlSecurityEnc::CONTENT; + } + XmlSecurityEnc::encryptNode($node, $type, $this->serviceSecurityKey->getPrivateKey(), $referenceList, $keyInfo); + } + } +} diff --git a/src/WssWsSecurity/Serializer/WsSecurityFilterResponse.php b/src/WssWsSecurity/Serializer/WsSecurityFilterResponse.php new file mode 100644 index 0000000..61992bc --- /dev/null +++ b/src/WssWsSecurity/Serializer/WsSecurityFilterResponse.php @@ -0,0 +1,121 @@ +ownerDocument); + $xpath->registerNamespace('wsu', self::NS_WSU); + + return $xpath->query($query)->item(0); + } + + /** + * Tries to resolve a key from the given \DOMElement. + * + * @param \DOMElement $node Node where to resolve the key + * @param string $algorithm XML security key algorithm + * + * @return \ass\XmlSecurity\Key|null + */ + public function keyInfoSecurityTokenReferenceResolver(\DOMElement $node, $algorithm) + { + foreach ($node->childNodes as $key) { + if (self::NS_WSS === $key->namespaceURI) { + switch ($key->localName) { + case 'KeyIdentifier': + + return $this->serviceSecurityKey->getPublicKey(); + case 'Reference': + $uri = $key->getAttribute('URI'); + $referencedNode = $this->getReferenceNodeForUri($node, $uri); + + if (XmlSecurityEnc::NS_XMLENC === $referencedNode->namespaceURI + && 'EncryptedKey' == $referencedNode->localName + ) { + $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->userSecurityKey->getPrivateKey()); + + return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); + } elseif (self::NS_WSS === $referencedNode->namespaceURI + && 'BinarySecurityToken' == $referencedNode->localName + ) { + + $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); + + return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); + } + } + } + } + + return null; + } + + + /** + * Modify the given request XML. + * + * @param \DOMDocument $dom + * + * @return void + */ + public function filterDom(\DOMDocument $dom) + { + // locate security header + $security = $dom->getElementsByTagNameNS(self::NS_WSS, 'Security')->item(0); + if (null !== $security) { + // add SecurityTokenReference resolver for KeyInfo + $keyResolver = array($this, 'keyInfoSecurityTokenReferenceResolver'); + XmlSecurityDSig::addKeyInfoResolver(self::NS_WSS, 'SecurityTokenReference', $keyResolver); + // do we have a reference list in header + $referenceList = XmlSecurityEnc::locateReferenceList($security); + // get a list of encrypted nodes + + $encryptedNodes = XmlSecurityEnc::locateEncryptedData($dom, $referenceList); + + // decrypt them + if (null !== $encryptedNodes) { + + foreach ($encryptedNodes as $encryptedNode) { + XmlSecurityEnc::decryptNode($encryptedNode); + } + } + // locate signature node + $signature = XmlSecurityDSig::locateSignature($security); + if (null !== $signature) { + // verify references + $options = array( + 'id_ns_prefix' => 'wsu', // used only for the xpath prefix + 'id_prefix_ns' => self::NS_WSU + ); + if (XmlSecurityDSig::verifyReferences($signature, $options) !== true) { + throw new ClientException('The node signature or decryption was invalid'); + } + // verify signature + if (XmlSecurityDSig::verifyDocumentSignature($signature) !== true) { + throw new ClientException('The document signature or decryption was invalid'); + } + } + + $security->parentNode->removeChild($security); + } + } +} diff --git a/src/WssWsSecurity/Serializer/WssSecurityHeaderEventListener.php b/src/WssWsSecurity/Serializer/WssSecurityHeaderEventListener.php new file mode 100644 index 0000000..0eceadc --- /dev/null +++ b/src/WssWsSecurity/Serializer/WssSecurityHeaderEventListener.php @@ -0,0 +1,48 @@ + 'serializer.pre_deserialize', + 'method' => 'onPreDeserializeEvent', + 'class' => HeaderPlaceholder::class, + 'format' => 'xml' + ), + array( + 'event' => 'serializer.pre_deserialize', + 'method' => 'onPreDeserializeEvent', + 'class' => 'Ex\SoapEnvelope12\Messages\RequestHeaderInput', + 'format' => 'xml' + ), + ); + } + + public function __construct(WsSecurityFilterResponse $filter) + { + $this->filter = $filter; + } + + public function onPreDeserializeEvent(PreDeserializeEvent $event) + { + $data = $event->getData(); + + $envelope = dom_import_simplexml($data); + $this->filter->filterDom($envelope->ownerDocument); +echo $envelope->ownerDocument->saveXML(); + $newData = simplexml_import_dom($envelope); + $event->setData($newData); + } +} diff --git a/src/WssWsSecurity/Serializer/WssSecurityHeaderHandler.php b/src/WssWsSecurity/Serializer/WssSecurityHeaderHandler.php new file mode 100644 index 0000000..a52c60f --- /dev/null +++ b/src/WssWsSecurity/Serializer/WssSecurityHeaderHandler.php @@ -0,0 +1,42 @@ + GraphNavigator::DIRECTION_SERIALIZATION, + 'format' => 'xml', + 'type' => Security::class, + 'method' => 'serializeHeader' + ), + ); + } + + public function __construct(WsSecurityFilterRequest $filter) + { + $this->filter = $filter; + } + + public function serializeHeader(XmlSerializationVisitor $visitor, Security $data, array $type, SerializationContext $context) + { + $currentNode = $visitor->getCurrentNode(); + $securityHeader = $this->filter->filterDom($currentNode, $data); + $visitor->revertCurrentNode(); + $visitor->setCurrentNode($securityHeader); + } +} diff --git a/tests/Client/BuildClientTest.php b/tests/Client/BuildClientTest.php index 3216764..aab1d47 100644 --- a/tests/Client/BuildClientTest.php +++ b/tests/Client/BuildClientTest.php @@ -18,16 +18,20 @@ class BuildClientTest extends \PHPUnit_Framework_TestCase */ protected $factory; + protected static $namespaces = [ + 'http://www.example.org/test/' => "Ex", + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Secext', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Utility', + 'http://www.w3.org/2000/09/xmldsig#' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\XmlSign', + ]; + public function setUp() { - $namespaces = [ - 'http://www.example.org/test/' => "Ex" - ]; - $generator = new Generator($namespaces); + $generator = new Generator(self::$namespaces); $serializer = $generator->buildSerializer(); $naming = new ShortNamingStrategy(); - $metadataGenerator = new MetadataGenerator($naming, $namespaces); + $metadataGenerator = new MetadataGenerator($naming, self::$namespaces); $dispatcher = new EventDispatcher(); $wsdlReader = new DefinitionsReader(null, $dispatcher); diff --git a/tests/Client/Client12RequestResponsesTest.php b/tests/Client/Client12RequestResponsesTest.php index 4c84a5f..0f93d94 100644 --- a/tests/Client/Client12RequestResponsesTest.php +++ b/tests/Client/Client12RequestResponsesTest.php @@ -28,7 +28,10 @@ class Client12RequestResponsesTest extends \PHPUnit_Framework_TestCase { protected static $namespaces = [ - 'http://www.example.org/test/' => "Ex" + 'http://www.example.org/test/' => "Ex", + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Secext', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Utility', + 'http://www.w3.org/2000/09/xmldsig#' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\XmlSign', ]; /** * @var Generator @@ -102,7 +105,7 @@ public function testGetSimple() { $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' - + @@ -124,7 +127,7 @@ public function testGetSimpleUnwrapped() { $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' - + @@ -155,7 +158,7 @@ public function testHeaders() { $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' - + @@ -174,12 +177,12 @@ public function testHeaders() $client->getSimple("foo", new MustUnderstandHeader($mp)); $this->assertXmlStringEqualsXmlString( ' - + - + @@ -189,12 +192,12 @@ public function testHeaders() $this->assertXmlStringEqualsXmlString( ' - + - + @@ -250,7 +253,7 @@ public function testNoInput() $this->responseMock->append( new Response(200, ['Content-Type' => 'application/soap+xml'], ' - + @@ -292,7 +295,7 @@ public function testReturnMultiParam() 200, ['Content-Type' => 'application/soap+xml'], ' - + @@ -313,7 +316,7 @@ public function testReturnMultiParam() $this->assertXmlStringEqualsXmlString( ' - + @@ -329,7 +332,7 @@ public function testMultiParamRequest() 200, ['Content-Type' => 'application/soap+xml'], ' - + @@ -345,7 +348,7 @@ public function testMultiParamRequest() $this->assertXmlStringEqualsXmlString( ' - + diff --git a/tests/Client/ClientRequestResponsesTest.php b/tests/Client/ClientRequestResponsesTest.php index 87576a9..3490b28 100644 --- a/tests/Client/ClientRequestResponsesTest.php +++ b/tests/Client/ClientRequestResponsesTest.php @@ -28,7 +28,10 @@ class ClientRequestResponsesTest extends \PHPUnit_Framework_TestCase { protected static $namespaces = [ - 'http://www.example.org/test/' => "Ex" + 'http://www.example.org/test/' => "Ex", + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Secext', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Utility', + 'http://www.w3.org/2000/09/xmldsig#' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\XmlSign', ]; /** * @var Generator @@ -102,7 +105,7 @@ public function testGetSimple() { $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' - + @@ -124,7 +127,7 @@ public function testGetSimpleUnwrapped() { $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' - + @@ -155,7 +158,7 @@ public function testHeaders() { $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' - + @@ -174,12 +177,12 @@ public function testHeaders() $client->getSimple("foo", new MustUnderstandHeader($mp)); $this->assertXmlStringEqualsXmlString( ' - + - + @@ -189,12 +192,12 @@ public function testHeaders() $this->assertXmlStringEqualsXmlString( ' - + - + @@ -222,7 +225,7 @@ public function testNoInput() $this->responseMock->append( new Response(200, ['Content-Type' => 'text/xml'], ' - + @@ -264,7 +267,7 @@ public function testReturnMultiParam() 200, ['Content-Type' => 'text/xml'], ' - + @@ -285,7 +288,7 @@ public function testReturnMultiParam() $this->assertXmlStringEqualsXmlString( ' - + @@ -301,7 +304,7 @@ public function testMultiParamRequest() 200, ['Content-Type' => 'text/xml'], ' - + @@ -317,7 +320,7 @@ public function testMultiParamRequest() $this->assertXmlStringEqualsXmlString( ' - + diff --git a/tests/Fixtures/client_private_key.pem b/tests/Fixtures/client_private_key.pem new file mode 100644 index 0000000..ac19d1f --- /dev/null +++ b/tests/Fixtures/client_private_key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMsT5SynGYPCrpIP +1LkVZvEZwI9YoUQOHmnj4AN0SklWsZTOwBsJGJoi9MKm+QNAuLMFeYezn7a7nyEK +7Awls7yvig7xRn2yj5KskroAdyTvwB6s8yx+1BfkwOCZCOpOiYBNxpsRezjafYD8 +2SgwPywL3OYjgRUBraAbUPXt1KfrAgMBAAECgYABCb3/J2+C8+jOiFQvCgP3sYkB +cpOIdIYFRovrmJmUnGPV/eSPftFoYEtd/1qAgMEw8RM49VsYpQbgNV0Vhs/PYcQB +Jlpo/Lhv58/BiOVYei2bi3lQsIM2Y+LppF06JqcbpGn7neWILfYQiMKF4Zjxrj2F +v3JWJfTE2RzRUqQx+QJBAPU6MXbf+4eUl4VJkFAWG2qleDZsGNfutBxzKMLv2HWn +ciTLo+VXMfSxVNVECH8sjbwWYEAY0MiJZk1ZpVtk900CQQDT/7Dh5qFmQxmH1L40 +rGfgvaHQzsjk3pYkia84A0dudthExvpSPGwoGDY5HWwEgHKczWjfhZ1olXlSbKpk +9DAXAkEApVrDBdRMSATDEuYiwE3X2NaQs6m6KshTfKeOQbv2qobpKbSC5F8iWUvF +1zRTwmUpgT1ZU38oMUCs0dVz8aeoNQJBAKISrXDWt/eNPtx4SX3NfJD1iNsw66cF +gHWoiTtiTl7mHsrd8Aukw+8XK4UYuDbs2DKGWzHfXYrSE3FvQAl0IbsCQQDPj/Ua +KvpKA+HNpaHUyPrVG2d/tBJgcvh7Q3EF+2LuBxS1gW/4VulhNwhbbm8Wi1xz3mCC +rmejXgOSRBlBAjDW +-----END PRIVATE KEY----- diff --git a/tests/Fixtures/client_public_key.pem b/tests/Fixtures/client_public_key.pem new file mode 100644 index 0000000..bdc043c --- /dev/null +++ b/tests/Fixtures/client_public_key.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLE+UspxmDwq6SD9S5FWbxGcCP +WKFEDh5p4+ADdEpJVrGUzsAbCRiaIvTCpvkDQLizBXmHs5+2u58hCuwMJbO8r4oO +8UZ9so+SrJK6AHck78AerPMsftQX5MDgmQjqTomATcabEXs42n2A/NkoMD8sC9zm +I4EVAa2gG1D17dSn6wIDAQAB +-----END PUBLIC KEY----- diff --git a/tests/Fixtures/config.yml b/tests/Fixtures/config.yml index 3d06155..8377ae0 100644 --- a/tests/Fixtures/config.yml +++ b/tests/Fixtures/config.yml @@ -8,11 +8,13 @@ soap_client: 'http://www.example.org/test/': 'TestNs' destinations_php: 'TestNs': soap/src + destinations_jms: 'TestNs\SoapEnvelope12': soap/metadata/soap-env-12 'TestNs\SoapEnvelope': soap/metadata/soap-env-11 'TestNs\SoapParts': soap/metadata/soap-parts 'TestNs': soap/metadata + aliases: 'http://www.example.org/test/': responseHeaderMessagesResponse: 'HeaderResponse' diff --git a/tests/Fixtures/server_private_key.pem b/tests/Fixtures/server_private_key.pem new file mode 100644 index 0000000..ee0af3c --- /dev/null +++ b/tests/Fixtures/server_private_key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMOlvDM/vmkXG4m7 +TdXdY4f6yS4FsXEDRPfxuX2eM3wl1pNd6ZYVmCvby3xf1SXyV+b+RaY5IYHmT9rn +zXkJMQHTH5Kk2kxmUic9oDvYJak3FygW4bejnVsZ+U3z8S1LEyMuBEOvqgUJ11aA +3GPXVZI66so37mOEhW0/I50uHnddAgMBAAECgYEAhozhjGFHQyDIKIWu9ujFfYvk +dYkmyfEUqmwMRC7be4LOOhT5AuWg/HCxVbzWar1q5Ip0PefGen545rRKI/ZFQ6U+ +Fp0dH/4KIjVJP3YWSXB4GcJ/BtBj5op1o0T6rthIkvBLZULtu991+dbmnsOgsJ/x +Cgm68Eu8NngQ+mucqsECQQD6PrIRoVy6T5mYfID9V/aE4GEbDKPEKx7eaWa6prUj +BLXiATTR1txlV7VjiBzIQ+o/abtn+MQ9culIEbXwM5zxAkEAyCWZeP0Bt1LcQjFR +Vjfo4Nwyu/Z32Tpz/wNZakwuFbDWZ5vbN5LKDbs+gYyFqV97Kgqr10Z8tiEJs/4+ +Bx3xLQJACK3d/TCMh8W0/Q3sZ10Cps8lbwu8LlSUiIA9WOHpTGKgcEs8ar65/CXT +m7Uf0m5QlIx1PIDrRXpTzvUWS1Nu8QJBAKxC4EKnz9BO+s/lzpGccU0HeIsaaLCI +hMmZwl2gz5FPsFlgZV8BcfI7lGK/5VKPoVvf72LLgg7nhIhsbEqH1MkCQFcRuk/k +VHZQYJ2lxcqT4Q8BJiXppoplJjxM1xWnjYHGwDh6+T/yGgWIgbtWg3lv9q8z3sHH +qnNxabaU4Dz9wt4= +-----END PRIVATE KEY----- diff --git a/tests/Fixtures/server_public_key.pem b/tests/Fixtures/server_public_key.pem new file mode 100644 index 0000000..c87dbb0 --- /dev/null +++ b/tests/Fixtures/server_public_key.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDpbwzP75pFxuJu03V3WOH+sku +BbFxA0T38bl9njN8JdaTXemWFZgr28t8X9Ul8lfm/kWmOSGB5k/a5815CTEB0x+S +pNpMZlInPaA72CWpNxcoFuG3o51bGflN8/EtSxMjLgRDr6oFCddWgNxj11WSOurK +N+5jhIVtPyOdLh53XQIDAQAB +-----END PUBLIC KEY----- diff --git a/tests/Fixtures/test.wsdl b/tests/Fixtures/test.wsdl index 84cf392..933a8eb 100644 --- a/tests/Fixtures/test.wsdl +++ b/tests/Fixtures/test.wsdl @@ -2,7 +2,8 @@ @@ -199,7 +200,7 @@ - + @@ -345,123 +346,141 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -582,6 +601,7 @@ + diff --git a/tests/Serializer/WssSecurityTest.php b/tests/Serializer/WssSecurityTest.php new file mode 100644 index 0000000..7f224b2 --- /dev/null +++ b/tests/Serializer/WssSecurityTest.php @@ -0,0 +1,437 @@ + "Ex", + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Secext', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\Utility', + 'http://www.w3.org/2000/09/xmldsig#' => 'GoetasWebservices\SoapServices\SoapClient\WssWsSecurity\XmlSign', + ]; + + + public static function setUpBeforeClass() + { + self::$generator = new Generator(self::$namespaces); + self::$generator->generate([__DIR__ . '/../Fixtures/test.wsdl']); + self::$generator->registerAutoloader(); + } + + public static function tearDownAfterClass() + { + self::$generator->unRegisterAutoloader(); + //self::$generator->cleanDirectories(); + } + + public function setUp() + { + + $generator = self::$generator; + $ref = new \ReflectionClass(Fault12::class); + + $this->requestFilter = new WsSecurityFilterRequest(); + $this->responseFilter = new WsSecurityFilterResponse(); + + $keypair1 = new SecurityKeyPair(); + $keypair1->setPrivateKey(\ass\XmlSecurity\Key::RSA_SHA1, __DIR__.'/../Fixtures/client_private_key.pem'); + $keypair1->setPublicKey(\ass\XmlSecurity\Key::RSA_SHA1, __DIR__.'/../Fixtures/client_public_key.pem'); + + $this->requestFilter->setUserSecurityKeyObject($keypair1); + + $keypair2 = new SecurityKeyPair(); + $keypair2->setPrivateKey(\ass\XmlSecurity\Key::TRIPLEDES_CBC); + $keypair2->setPublicKey(\ass\XmlSecurity\Key::RSA_1_5, __DIR__.'/../Fixtures/server_public_key.pem'); + + $this->requestFilter->setServiceSecurityKeyObject($keypair2); + + $keypair3 = new SecurityKeyPair(); + $keypair3->setPrivateKey(\ass\XmlSecurity\Key::RSA_SHA1, __DIR__.'/../Fixtures/server_private_key.pem'); + $keypair3->setPublicKey(\ass\XmlSecurity\Key::RSA_SHA1, __DIR__.'/../Fixtures/client_public_key.pem'); + + + $this->responseFilter->setUserSecurityKeyObject($keypair3); + + $this->headerHandler = new HeaderHandler(); + $this->serializer = $generator->buildSerializer(function (HandlerRegistryInterface $h) { + $h->registerSubscribingHandler($this->headerHandler); + + $sechandler = new WssSecurityHeaderHandler($this->requestFilter); + $h->registerSubscribingHandler($sechandler); + }, [ + 'GoetasWebservices\SoapServices\SoapClient\Envelope\SoapEnvelope12' => dirname($ref->getFileName()) . '/../../../Resources/metadata/jms12', + ], function(EventDispatcherInterface $d) { + $d->addSubscriber(new WssSecurityHeaderEventListener($this->responseFilter)); + }); + + } + + public function testSerializeSecurity() + { + $headerPlaceholder = new HeaderPlaceholder(); + + $this->requestFilter->setSecurityOptionsEncryption(WsSecurityFilterRequest::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER); + + $security = new Security(); + $security->setUsername('foo'); + $security->setPassword('pass'); + + $auth = new \Ex\AuthHeader(); + $auth->setUser('bar'); + + $this->headerHandler->addHeaderData($headerPlaceholder, new MustUnderstandHeader($security)); + $this->headerHandler->addHeaderData($headerPlaceholder, new MustUnderstandHeader($auth)); + + $env = new RequestHeaderInput(); + $env->setHeader($headerPlaceholder); + + $body = new \Ex\SoapParts\RequestHeaderInput(); + $p = new \Ex\RequestHeader(); + $p->setIn("sss"); + $body->setParameters($p); + $env->setBody($body); + + $xml = $this->serializer->serialize($env, 'xml'); + + $expectedString = ' + + + + + + + + + + + aPfDzwzxuVaqRXvKfnBIKxqyPgdyZvBLm0JyNXRyZDn7HNHoX7cuAtT5tHne9lQP4/9DRXyhA/1HdxY755dv8IS9PMcNpGGEdhwy2UgTko0zDynpwUcvrslxukFehskvhhyFAxIShmbFJzRZjbq/OYeggiXWhcg+/tezDuyNbHSdnWcSq8w9+oO5UTog4Uj47DWY+2r6iP1/Ln7boMi8wn2xABIxkBFFU32z51tngtMIzvWyoFFDf9xc3jDgz5Vd9t4jUbm9akH68m2TAngGahGOEzIa3JudVLEkDLJXT8UCZkuHmP85r2QGEMz/8cMJOE8cjDKtAlbMy1lL7kbzSFA2yYZBEkyXM+F1y1E/A7YS+/mrQ2VqdDldEQNtfSiaV/VPEonrGJkFrSWlYeEUWF2Cg8rEerDrmYZX3vLyVxrtjNHSA5JVaQOpwWO0BGajIuEiCgoHRbA1zPmtpoZh9u39Hd/F8bg9ZBDCOky1L83f0IW5LHUjYOv0FxqFXftdKvnF7/aDbiZnhX3330p/Iw2guOWpwJTHyOilfMXo0sC3jY/hB36jIznYb+TtAMKB3lki47jVj2DREYQDh/nsH1HhbpgFe+JhtFvV3ewZGjc= + + + + + + + 2016-12-21T14:04:53.000Z + 2016-12-21T14:09:53.000Z + + + foo + + + + + + + + + + + og8QzG1KkeF3W302te9V5leP4Mnvrb+qJEld055MFCYUFN6znwR0dbp/P3YRqV0efBIkF6MA5Gj4TX6TkyIvHjELTJNyQDpfrLWXZBhzP+6AdrCQzpWZ0p3sYUJXDVoYXEKhfPYmdpReMoK/H4cmcfdZj2B/ZcMmL/ZCQNxOAv4= + + + + + + + + + + + + + + + ji4+Hoi++5jtopiElK3EFL/AcTM= + + + + + + + H4BPAIlGlXwRxZcXxgJTgBMxQ5Y= + + + + + + + Unz40wju5ExcAkTC5PMMJaRxZV8= + + + kRjlhN3nNNHciRBaD89V89NDZyiKrHV1WcgmQ7mdKyBgXeRMhitnYIt0YsPqwA0DA86doFNiMqfkI4OqcCltUL4VQt3MiMn+6st5R/tQgGoExZ/VE5QqzWjZiJEk8nLcDmqVmOFS8HSsp3cct2rj4PhxBo1IxSo2ZJo0psBg3G4= + + + + + + + +'; + + $this->assertXmlStringEqualsXmlString($this->cleanXML($expectedString), $this->cleanXML($xml)); + } + + public function testDeSerializeSecurity() + { + + $headerPlaceholder = new HeaderPlaceholder(); + + $this->requestFilter->setSecurityOptionsEncryption(WsSecurityFilterRequest::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER); + $this->requestFilter->setSecurityOptionsSignature(WsSecurityFilterRequest::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER); + + $keypair1 = new SecurityKeyPair(); + $keypair1->setPrivateKey(\ass\XmlSecurity\Key::RSA_1_5, __DIR__.'/../Fixtures/client_private_key.pem'); + $keypair1->setPublicKey(\ass\XmlSecurity\Key::RSA_1_5, __DIR__.'/../Fixtures/client_public_key.pem'); + + $keypair2 = new SecurityKeyPair(); + $keypair2->setPrivateKey(\ass\XmlSecurity\Key::TRIPLEDES_CBC, str_repeat("1", 24)); + $keypair2->setPublicKey(\ass\XmlSecurity\Key::RSA_1_5, __DIR__.'/../Fixtures/server_public_key.pem'); + + $keypair3 = new SecurityKeyPair(); + $keypair3->setPrivateKey(\ass\XmlSecurity\Key::RSA_1_5, __DIR__.'/../Fixtures/server_private_key.pem'); + + $keypair4 = new SecurityKeyPair(); + $keypair4->setPublicKey(\ass\XmlSecurity\Key::RSA_1_5, __DIR__.'/../Fixtures/client_public_key.pem', true); + + $this->requestFilter->setUserSecurityKeyObject($keypair1); + $this->requestFilter->setServiceSecurityKeyObject($keypair2); + + $this->responseFilter->setServiceSecurityKeyObject($keypair4); + $this->responseFilter->setUserSecurityKeyObject($keypair3); + + $security = new Security(); + $security->setUsername('foo'); + + $this->headerHandler->addHeaderData($headerPlaceholder, new MustUnderstandHeader($security)); + + + $env = new RequestHeaderInput(); + $env->setHeader($headerPlaceholder); + + $body = new \Ex\SoapParts\RequestHeaderInput(); + $p = new \Ex\RequestHeader(); + $p->setIn("sss"); + $body->setParameters($p); + $env->setBody($body); + + $xmlString = $this->serializer->serialize($env, 'xml'); + + /** + * @var $object RequestHeaderInput + */ + $object = $this->serializer->deserialize($xmlString, RequestHeaderInput::class, 'xml'); + + $this->assertEquals("sss", $object->getBody()->getParameters()->getIn()); + $this->assertInstanceOf('Ex\RequestHeader', $object->getBody()->getParameters()); + } + + public function testSerializeSecurityWithTokenTypes() + { + $headerPlaceholder = new HeaderPlaceholder(); + + $this->requestFilter->setSecurityOptionsEncryption(WsSecurityFilterRequest::TOKEN_REFERENCE_SECURITY_TOKEN); + $this->requestFilter->setSecurityOptionsSignature(WsSecurityFilterRequest::TOKEN_REFERENCE_SECURITY_TOKEN); + + $security = new Security(); + $security->setUsername('foo'); + $security->setPassword('pass'); + + $this->headerHandler->addHeaderData($headerPlaceholder, new MustUnderstandHeader($security)); + + $env = new RequestHeaderInput(); + $env->setHeader($headerPlaceholder); + + $body = new \Ex\SoapParts\RequestHeaderInput(); + $p = new \Ex\RequestHeader(); + $p->setIn("sss"); + $body->setParameters($p); + $env->setBody($body); + + $xml = $this->serializer->serialize($env, 'xml'); + + $expectedString = ' + + + + + + + + + + + RuqNIV9tlKbV/XBoB0vxN5DjvCuVtJArIgSKcOQ8LoS7CEuD3mbmHsbDwAf3nEWQ7zRrQTw2C3XC+J5xNrMpyaAmiJuSea4TdhhSJ0uMFgtKY9OpSVEAEvFklx7kaOJTxTg+M1DMopAljldbRFVlghmTR0g2PIPaXhJfawO6HbjIeAJ8BfYblDnzoHv6CzpbaRJCKcbi1PjlLSaub1kQtjdONWv5VfLh1jB7M+EgWKGVbjLG6Xq1cColAo24NaD1TI+deaNKT4/Rw3w3zJKnxbEIQ01xgCtiB6AfR6G9851FXqR0OdcqHhVe0vfnjT+NGDEwazgLX6XcnwFDkge7zJ/vqKHWC1Jyjl2ym/P3JTSjje/ozFPNnQjlMVSAK0c5wA57+HsA3OCo9fHWtQoaRw7cboMOg0Qg6IoUi2+g0OhWgC0EKVnUqQzFGqDEXD9S3gIsv7Q+jCpwFEcKvkYe287VgeKmePOqbBLuA5LcMRYDMoOGtY0CUpe0A1klC2cGIYn2EMpGcTK4+ckUp5Hz02JgK6VnaI54f8EaVIV2hhrnnJSes+3/94v3erytbY6pROvM6TzFUDc5dOD+ZU07MfIPZAhV0h0dXmpObiyctpw= + + + + + + + 2016-12-21T14:00:49.000Z + 2016-12-21T14:05:49.000Z + + + foo + + + + + + + + + + + lWbYQaXg8hhPxjyMlBMCt0dsxt3NWFZWIlNL3lq6s1qsvQ+UN8Gn1jJYivPkLTtiGKatTtwQijS8iBcOmyp73/27EkGwLqovbhnkz+ZmWdyIcFtdD2qpcb2HD10cppo0a8QPmwEtXIYPM5GlH8kLXm4u9kn9OJA5FreCFASCwT0= + + + + + + + + + + + + + + + RHWAhMuWzw2uUF5cBc6ObCBpC9I= + + + + + + + CaIRD27prYA1YCyY4fUV5L9rj7Y= + + + + + + + Mbbxwz6mcQhxMbiw8RIKHqyXeHs= + + + F8aOS40ypNHMHnsWDbkWb27NAEezKV7RmodKFuXjmkRB70YKoGUMIZSyvIlaj/E6GQ7a27gQyv6Kjzb8XTemQw+I8kgMGXiECVplbhuH3OayOKvMXPo8g4y0+7P5uf7h0OhFoq9U2Pbya3HDqsxVm5AUdl5AAm9SZkNLoC43tuc= + + + + + + + + + +'; + + $this->assertXmlStringEqualsXmlString($this->cleanXML($expectedString), $this->cleanXML($xml)); + } + + public function testSerializeSecurityWithNoCerts() + { + $headerPlaceholder = new HeaderPlaceholder(); + + $this->requestFilter->setServiceSecurityKeyObject(null); + $this->requestFilter->setUserSecurityKeyObject(null); + + $security = new Security(); + $security->setUsername('foo'); + $security->setPassword('pass'); + + $this->headerHandler->addHeaderData($headerPlaceholder, new MustUnderstandHeader($security)); + + $env = new RequestHeaderInput(); + $env->setHeader($headerPlaceholder); + + $body = new \Ex\SoapParts\RequestHeaderInput(); + $p = new \Ex\RequestHeader(); + $p->setIn("sss"); + $body->setParameters($p); + $env->setBody($body); + + $xml = $this->serializer->serialize($env, 'xml'); + + $expectedString = ' + + + + + + + + + + 2016-12-21T09:51:41.000Z + 2016-12-21T09:56:41.000Z + + + foo + E+rwHP8u/HO0gP/9gHweD4hm/Lk= + MTg3NzQxNjUzMw== + 2016-12-21T09:51:41.000Z + + + + '; + + $this->assertXmlStringEqualsXmlString($this->cleanXML($expectedString), $this->cleanXML($xml)); + } + + private function cleanXML($xml) + { + $xml = preg_replace('~(Id|EncKey|Cert|\#)[a-f0-9\-]+~', 'X', $xml); + $xml = preg_replace('~\d{4}\-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*?Z~', 'X', $xml); + + $tags = ['ds:SignatureValue', 'ds:DigestValue', 'xenc:CipherValue', 'ws:BinarySecurityToken', 'ws:Nonce', 'ws:Password']; + foreach ($tags as $tag) { + $xml = preg_replace("~(<$tag.*?>)(.+?)(<\\/$tag>)~", '\1abc\3', $xml); + } + return $xml; + } +} diff --git a/tests/example.php b/tests/example.php index d7809b9..51f78da 100644 --- a/tests/example.php +++ b/tests/example.php @@ -26,7 +26,7 @@ $responseMock = new MockHandler(); $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' - +