From cf130ccfc9743d3c6d05846647ae2c8e06631927 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 27 Jul 2020 11:41:15 +0200 Subject: [PATCH 1/2] Update composer.json. --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index 5e62b2c..44ecc7e 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,12 @@ "license": "BSD-3-Clause", "require": { "php": ">= 7.1.3", + "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-simplexml": "*", "league/uri-query-parser": "^1.0", + "openlss/lib-array2xml": "^1.0", "psr/cache": "^1.0.1", "psr/http-client": "^1.0.0", "psr/http-factory": "^1.0.1", From 8c27a72deb2fe37ef5f9fb79a959e2ad0864bd82 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 27 Jul 2020 14:33:08 +0200 Subject: [PATCH 2/2] Parse XML with a contrib library and get rid of custom class. --- spec/EcPhp/CasLib/CasSpec.php | 53 ++++++++-------- .../Introspection/ServiceValidateSpec.php | 26 ++++---- spec/EcPhp/CasLib/Utils/SimpleXmlSpec.php | 60 ------------------- src/Introspection/Introspector.php | 47 +++++++++++++-- src/Utils/SimpleXml.php | 59 ------------------ 5 files changed, 82 insertions(+), 163 deletions(-) delete mode 100644 spec/EcPhp/CasLib/Utils/SimpleXmlSpec.php delete mode 100644 src/Utils/SimpleXml.php diff --git a/spec/EcPhp/CasLib/CasSpec.php b/spec/EcPhp/CasLib/CasSpec.php index e110596..d4147c1 100644 --- a/spec/EcPhp/CasLib/CasSpec.php +++ b/spec/EcPhp/CasLib/CasSpec.php @@ -8,11 +8,8 @@ use EcPhp\CasLib\Configuration\Properties as CasProperties; use EcPhp\CasLib\Introspection\Introspector; use EcPhp\CasLib\Introspection\ServiceValidate; -use EcPhp\CasLib\Utils\SimpleXml; use Exception; use InvalidArgumentException; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Response; use Nyholm\Psr7\ServerRequest; @@ -1358,19 +1355,19 @@ public function it_can_validate_a_service_ticket() public function it_can_validate_any_type_of_ticket() { - $body = <<< 'EOF' - - - username - - -EOF; + $body = [ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + ], + ], + ]; $request = new ServerRequest('GET', 'http://from?ticket=ST-TICKET'); $response = new Response( 200, ['Content-Type' => 'application/json'], - json_encode(SimpleXml::toArray(SimpleXml::fromString($body))) + json_encode($body) ); $this @@ -1378,20 +1375,20 @@ public function it_can_validate_any_type_of_ticket() ->requestTicketValidation([], $response) ->shouldBeAnInstanceOf(ResponseInterface::class); - $body = <<< 'EOF' - - - username - pgtIou - - -EOF; + $body = [ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + 'proxyGrantingTicket' => 'pgtIou', + ], + ], + ]; $request = new ServerRequest('GET', 'http://from?ticket=PT-TICKET'); $response = new Response( 200, ['Content-Type' => 'application/json'], - json_encode(SimpleXml::toArray(SimpleXml::fromString($body))) + json_encode($body) ); $this @@ -1399,19 +1396,19 @@ public function it_can_validate_any_type_of_ticket() ->requestTicketValidation([], $response) ->shouldBeNull(); - $body = <<< 'EOF' - - - username - - -EOF; + $body = [ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + ], + ], + ]; $request = new ServerRequest('GET', 'http://from'); $response = new Response( 500, ['Content-Type' => 'application/json'], - json_encode(SimpleXml::toArray(SimpleXml::fromString($body))) + json_encode($body) ); $this diff --git a/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php b/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php index a4c00dd..2eb8e70 100644 --- a/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php +++ b/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php @@ -16,16 +16,7 @@ public function it_can_detect_a_proxy_service_validate_response() $psr17Factory = new Psr17Factory(); $body = <<< 'EOF' - - - user - proxyGrantingTicket - - http://proxy1 - http://proxy2 - - - +Useless stuff here. EOF; $response = (new Response(200)) @@ -41,6 +32,17 @@ public function it_can_detect_a_proxy_service_validate_response() 'http://proxy2', ], ], + 'extendedAttributes' => [ + 'extendedAttribute' => [ + 'attributeValue' => [ + 0 => 'rex', + 1 => 'snoopy', + ], + '@attributes' => [ + 'name' => 'http://stork.eu/motherInLawDogName', + ], + ], + ], ]; $parsed = [ @@ -74,6 +76,10 @@ public function it_can_detect_a_proxy_service_validate_response() $this ->getResponse() ->shouldReturn($response); + + $this + ->getParsedResponse() + ->shouldReturn($parsed); } public function it_can_detect_a_service_validate_response() diff --git a/spec/EcPhp/CasLib/Utils/SimpleXmlSpec.php b/spec/EcPhp/CasLib/Utils/SimpleXmlSpec.php deleted file mode 100644 index a3670fd..0000000 --- a/spec/EcPhp/CasLib/Utils/SimpleXmlSpec.php +++ /dev/null @@ -1,60 +0,0 @@ - - - - PT-214-A3OoEPNr4Q9kNNuYzmfN8azU31aDUsuW8nk380k7wDExT5PFJpxR1TrNI3q3VGzyDdi0DpZ1LKb8IhPKZKQvavW-8hnfexYjmLCx7qWNsLib1W-DCzzoLVTosAUFzP3XDn5dNzoNtxIXV9KSztF9fYhwHvU0 - - -EOF; - - $this::fromString($data) - ->shouldBeAnInstanceOf(SimpleXMLElement::class); - } - - public function it_can_convert_xml_into_an_array() - { - $data = <<< 'EOF' - - - - PT-214-A3OoEPNr4Q9kNNuYzmfN8azU31aDUsuW8nk380k7wDExT5PFJpxR1TrNI3q3VGzyDdi0DpZ1LKb8IhPKZKQvavW-8hnfexYjmLCx7qWNsLib1W-DCzzoLVTosAUFzP3XDn5dNzoNtxIXV9KSztF9fYhwHvU0 - - -EOF; - $xml = $this->getWrappedObject()::fromString($data); - - $this::toArray($xml) - ->shouldReturn( - [ - 'serviceResponse' => [ - 'proxySuccess' => [ - 'proxyTicket' => 'PT-214-A3OoEPNr4Q9kNNuYzmfN8azU31aDUsuW8nk380k7wDExT5PFJpxR1TrNI3q3VGzyDdi0DpZ1LKb8IhPKZKQvavW-8hnfexYjmLCx7qWNsLib1W-DCzzoLVTosAUFzP3XDn5dNzoNtxIXV9KSztF9fYhwHvU0', - ], - ], - ] - ); - } - - public function it_is_initializable() - { - $this->shouldHaveType(SimpleXml::class); - } -} diff --git a/src/Introspection/Introspector.php b/src/Introspection/Introspector.php index 1147799..ba1814c 100644 --- a/src/Introspection/Introspector.php +++ b/src/Introspection/Introspector.php @@ -4,13 +4,20 @@ namespace EcPhp\CasLib\Introspection; +use DOMDocument; +use DOMXPath; use EcPhp\CasLib\Introspection\Contract\IntrospectionInterface; use EcPhp\CasLib\Introspection\Contract\IntrospectorInterface; -use EcPhp\CasLib\Utils\SimpleXml; +use Exception; use InvalidArgumentException; +use LSS\XML2Array; use Psr\Http\Message\ResponseInterface; use const JSON_ERROR_NONE; +use const LIBXML_NOBLANKS; +use const LIBXML_NOCDATA; +use const LIBXML_NONET; +use const LIBXML_NSCLEAN; final class Introspector implements IntrospectorInterface { @@ -73,18 +80,37 @@ public function detect(ResponseInterface $response): IntrospectionInterface */ public function parse(ResponseInterface $response, string $format = 'XML'): array { + $body = (string) $response->getBody(); + + if ('' === $body) { + throw new InvalidArgumentException('Empty response body'); + } + if ('XML' === $format) { - $xml = SimpleXml::fromString((string) $response->getBody()); + libxml_disable_entity_loader(true); + libxml_use_internal_errors(true); + + try { + $dom = new DOMDocument(); + + $dom + ->loadXML( + $body, + LIBXML_NSCLEAN | LIBXML_NOCDATA | LIBXML_NOBLANKS | LIBXML_NONET + ); + + $this->removeDomNamespace($dom, 'cas'); - if (null === $xml) { - throw new InvalidArgumentException('Unable to parse the response using XML format.'); + $data = XML2Array::createArray($dom); + } catch (Exception $e) { + throw new InvalidArgumentException('Unable to parse the response using XML format.', 0, $e); } - return SimpleXml::toArray($xml); + return $data; } if ('JSON' === $format) { - $json = json_decode((string) $response->getBody(), true); + $json = json_decode($body, true); if (null === $json || JSON_ERROR_NONE !== json_last_error()) { throw new InvalidArgumentException('Unable to parse the response using JSON format.'); @@ -95,4 +121,13 @@ public function parse(ResponseInterface $response, string $format = 'XML'): arra throw new InvalidArgumentException('Unsupported format.'); } + + private function removeDomNamespace(DOMDocument $doc, string $namespace): void + { + $query = sprintf('//*[namespace::%s and not(../namespace::%s)]', $namespace, $namespace); + + foreach ((new DOMXPath($doc))->query($query) as $node) { + $node->removeAttributeNS($node->lookupNamespaceURI($namespace), $namespace); + } + } } diff --git a/src/Utils/SimpleXml.php b/src/Utils/SimpleXml.php deleted file mode 100644 index 90af902..0000000 --- a/src/Utils/SimpleXml.php +++ /dev/null @@ -1,59 +0,0 @@ -getName() => self::toArrayRecursive($xml)]; - } - - /** - * @return array[] - */ - private static function toArrayRecursive(SimpleXMLElement $element): ?array - { - return array_map( - static function ($node) { - return $node instanceof SimpleXMLElement ? - self::toArrayRecursive($node) : - $node; - }, - (array) $element - ); - } -}