From 3504b644819bd119aae8fe32927fb3e04b6c6483 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 2 May 2022 13:41:54 +0200 Subject: [PATCH] Road to version 2 - Refactor and rewrite most of the library (#33) * chore: Bump dev dependencies. * refactor: Remove `request` property, pass it to each methods instead. * chore: Update `composer.json` - Remove uneeded dev dependency. * docs: Update documentation. * tests: Update tests accordingly. * refactor: Use `loophp/psr17` dependency. * refactor: Remove dependency to `monolog/monolog`. * refactor: Merge `Handler` and `Service`. * refactor: Get rid of `Introspector`. * refactor: Make it PSR15 compatible. * refactor: Add more exceptions and update minor things here and there. * tests: Restore tests and update them according to latest changes. * refactor: Merge `ProxyValidate` and `ServiceValidate`. * refactor: Simplify configuration and reorganize code. * Fix `ServiceValidate` handler, restore `ProxyValidation` and update tests accordingly. * refactor: Simplify and rewrite handlers. * Update `CasResponseBuilder` to allow more granular customizations. * Fix `ProxyCallback`. * fix: Update `proxyCallback` handler, add note and fix test. * tests: Add missing tests, improve coverage. * chore: Require Psr17 stuff. * refactor: Add response factories. * refactor: Update factories, add interfaces. * Make sure `text/xml` can be converted. This is the kind of Content-Type header that EU Login is sending. * refactor: Fix Proxy handling. * Update Exception handling and PHPDoc. * Update documentation and URI parameter handling. BREAKING CHANGE: yes * Sync `.editorconfig` with `ecphp/php-library-template`. * Add more handler exceptions and fix an issue in the Service Validate, make it throws when the PGT is not available. * Update documentation pages. --- .editorconfig | 5 +- composer.json | 16 +- docs/index.rst | 16 +- docs/pages/configuration.rst | 25 +- docs/pages/installation.rst | 25 +- docs/pages/requirements.rst | 13 +- docs/pages/tests.rst | 54 +- docs/pages/usage.rst | 18 +- spec/EcPhp/CasLib/Cas.php | 162 +- spec/EcPhp/CasLib/CasSpec.php | 1446 ++++++----------- spec/EcPhp/CasLib/Handler/LoginSpec.php | 153 ++ spec/EcPhp/CasLib/Handler/LogoutSpec.php | 59 + .../CasLib/Handler/ProxyCallbackSpec.php | 133 +- spec/EcPhp/CasLib/Handler/ProxySpec.php | 85 + .../CasLib/Handler/ServiceValidateSpec.php | 156 ++ .../Introspection/ServiceValidateSpec.php | 163 -- spec/EcPhp/CasLib/Redirect/LoginSpec.php | 138 -- spec/EcPhp/CasLib/Redirect/LogoutSpec.php | 54 - .../CasResponseBuilderSpec.php} | 140 +- .../Type}/ProxyFailureSpec.php | 22 +- .../Type}/ProxySpec.php | 22 +- .../Response/Type/ServiceValidateSpec.php | 195 +++ spec/EcPhp/CasLib/Service/ProxySpec.php | 68 - .../CasLib/Service/ProxyValidateSpec.php | 122 -- spec/EcPhp/CasLib/Utils/ResponseSpec.php | 113 ++ spec/EcPhp/CasLib/Utils/UriSpec.php | 2 +- spec/tests/EcPhp/CasLib/CasSpec.php | 108 +- .../CasLib/Service/ProxyValidateSpec.php | 200 --- src/Cas.php | 434 ++--- src/CasInterface.php | 165 -- src/Configuration/Properties.php | 1 + src/Contract/CasInterface.php | 131 ++ .../Configuration/PropertiesInterface.php | 2 +- src/Contract/Handler/HandlerInterface.php | 35 + .../ServiceValidateHandlerInterface.php} | 4 +- .../Response/CasResponseBuilderInterface.php | 23 + .../Response/CasResponseInterface.php | 25 + .../Factory/AuthenticationFailureFactory.php | 20 + .../Response/Factory/ProxyFactory.php} | 10 +- .../Response/Factory/ProxyFailureFactory.php} | 8 +- .../Factory/ServiceValidateFactory.php | 20 + .../Response/Type/AuthenticationFailure.php | 18 + .../Response/Type}/Proxy.php | 6 +- .../Response/Type}/ProxyFailure.php | 6 +- .../Response/Type}/ServiceValidate.php | 12 +- src/Exception/CasException.php | 89 + .../CasExceptionInterface.php} | 6 +- src/Exception/CasHandlerException.php | 129 ++ src/Exception/CasResponseBuilderException.php | 38 + src/Handler/Handler.php | 157 +- src/Handler/Login.php | 79 + src/Handler/Logout.php | 40 + src/Handler/Proxy.php | 61 + src/Handler/ProxyCallback.php | 63 +- src/Handler/ServiceValidate.php | 116 ++ .../Contract/IntrospectionInterface.php | 25 - .../Contract/IntrospectorInterface.php | 24 - src/Introspection/Introspection.php | 54 - src/Introspection/Introspector.php | 143 -- src/Introspection/ServiceValidate.php | 31 - src/Redirect/Login.php | 114 -- src/Redirect/Logout.php | 36 - src/Redirect/Redirect.php | 30 - src/Response/CasResponseBuilder.php | 80 + .../Factory/AuthenticationFailureFactory.php | 25 + src/Response/Factory/ProxyFactory.php | 25 + src/Response/Factory/ProxyFailureFactory.php | 25 + .../Factory/ServiceValidateFactory.php | 25 + .../Type}/AuthenticationFailure.php | 6 +- src/Response/Type/CasResponse.php | 120 ++ .../Type}/Proxy.php | 8 +- .../Type}/ProxyFailure.php | 8 +- src/Response/Type/ServiceValidate.php | 51 + src/Service/Proxy.php | 52 - src/Service/ProxyValidate.php | 39 - src/Service/Service.php | 272 ---- src/Service/ServiceValidate.php | 39 - src/Utils/Response.php | 100 ++ src/Utils/Uri.php | 88 +- tests/Cas.php | 88 +- tests/Service/ProxyValidate.php | 114 -- 81 files changed, 3207 insertions(+), 3826 deletions(-) create mode 100644 spec/EcPhp/CasLib/Handler/LoginSpec.php create mode 100644 spec/EcPhp/CasLib/Handler/LogoutSpec.php create mode 100644 spec/EcPhp/CasLib/Handler/ProxySpec.php create mode 100644 spec/EcPhp/CasLib/Handler/ServiceValidateSpec.php delete mode 100644 spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php delete mode 100644 spec/EcPhp/CasLib/Redirect/LoginSpec.php delete mode 100644 spec/EcPhp/CasLib/Redirect/LogoutSpec.php rename spec/EcPhp/CasLib/{Introspection/IntrospectorSpec.php => Response/CasResponseBuilderSpec.php} (63%) rename spec/EcPhp/CasLib/{Introspection => Response/Type}/ProxyFailureSpec.php (58%) rename spec/EcPhp/CasLib/{Introspection => Response/Type}/ProxySpec.php (50%) create mode 100644 spec/EcPhp/CasLib/Response/Type/ServiceValidateSpec.php delete mode 100644 spec/EcPhp/CasLib/Service/ProxySpec.php delete mode 100644 spec/EcPhp/CasLib/Service/ProxyValidateSpec.php create mode 100644 spec/EcPhp/CasLib/Utils/ResponseSpec.php delete mode 100644 spec/tests/EcPhp/CasLib/Service/ProxyValidateSpec.php delete mode 100644 src/CasInterface.php create mode 100644 src/Contract/CasInterface.php rename src/{ => Contract}/Configuration/PropertiesInterface.php (89%) create mode 100644 src/Contract/Handler/HandlerInterface.php rename src/{Introspection/Contract/AuthenticationFailure.php => Contract/Handler/ServiceValidateHandlerInterface.php} (65%) create mode 100644 src/Contract/Response/CasResponseBuilderInterface.php create mode 100644 src/Contract/Response/CasResponseInterface.php create mode 100644 src/Contract/Response/Factory/AuthenticationFailureFactory.php rename src/{Handler/HandlerInterface.php => Contract/Response/Factory/ProxyFactory.php} (57%) rename src/{Service/ServiceInterface.php => Contract/Response/Factory/ProxyFailureFactory.php} (54%) create mode 100644 src/Contract/Response/Factory/ServiceValidateFactory.php create mode 100644 src/Contract/Response/Type/AuthenticationFailure.php rename src/{Introspection/Contract => Contract/Response/Type}/Proxy.php (63%) rename src/{Introspection/Contract => Contract/Response/Type}/ProxyFailure.php (61%) rename src/{Introspection/Contract => Contract/Response/Type}/ServiceValidate.php (52%) create mode 100644 src/Exception/CasException.php rename src/{Redirect/RedirectInterface.php => Exception/CasExceptionInterface.php} (62%) create mode 100644 src/Exception/CasHandlerException.php create mode 100644 src/Exception/CasResponseBuilderException.php create mode 100644 src/Handler/Login.php create mode 100644 src/Handler/Logout.php create mode 100644 src/Handler/Proxy.php create mode 100644 src/Handler/ServiceValidate.php delete mode 100644 src/Introspection/Contract/IntrospectionInterface.php delete mode 100644 src/Introspection/Contract/IntrospectorInterface.php delete mode 100644 src/Introspection/Introspection.php delete mode 100644 src/Introspection/Introspector.php delete mode 100644 src/Introspection/ServiceValidate.php delete mode 100644 src/Redirect/Login.php delete mode 100644 src/Redirect/Logout.php delete mode 100644 src/Redirect/Redirect.php create mode 100644 src/Response/CasResponseBuilder.php create mode 100644 src/Response/Factory/AuthenticationFailureFactory.php create mode 100644 src/Response/Factory/ProxyFactory.php create mode 100644 src/Response/Factory/ProxyFailureFactory.php create mode 100644 src/Response/Factory/ServiceValidateFactory.php rename src/{Introspection => Response/Type}/AuthenticationFailure.php (54%) create mode 100644 src/Response/Type/CasResponse.php rename src/{Introspection => Response/Type}/Proxy.php (50%) rename src/{Introspection => Response/Type}/ProxyFailure.php (51%) create mode 100644 src/Response/Type/ServiceValidate.php delete mode 100644 src/Service/Proxy.php delete mode 100644 src/Service/ProxyValidate.php delete mode 100644 src/Service/Service.php delete mode 100644 src/Service/ServiceValidate.php create mode 100644 src/Utils/Response.php delete mode 100644 tests/Service/ProxyValidate.php diff --git a/.editorconfig b/.editorconfig index 654c18b..3aca22b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,13 +6,10 @@ root = true -[*.{php,inc,module}] +[*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 4 - -[*.{json,json.dist,yml,yml.dist}] -indent_size = 4 \ No newline at end of file diff --git a/composer.json b/composer.json index f519434..bbacc76 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,9 @@ "ext-json": "*", "ext-libxml": "*", "ext-simplexml": "*", + "ergebnis/http-method": "^2.2", "league/uri-components": "^2", + "loophp/psr17": "^1.0", "openlss/lib-array2xml": "^1.0", "psr/cache": "^1.0.1 || ^2 || ^3", "psr/http-client": "^1.0", @@ -24,7 +26,8 @@ "psr/http-factory-implementation": "^1", "psr/http-message": "^1.0", "psr/http-message-implementation": "^1", - "psr/log": "^1.1 || ^2 || ^3" + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" }, "require-dev": { "ext-xdebug": "*", @@ -32,14 +35,18 @@ "friends-of-phpspec/phpspec-code-coverage": "^6.0.0", "infection/infection": "^0.23 || ^0.24", "infection/phpspec-adapter": "^0.1.2", - "monolog/monolog": "^1.0 || ^2.0", "nyholm/psr7": "^1.4", - "nyholm/psr7-server": "^1.0.0", "phpspec/phpspec": "^7", - "phpstan/phpstan-strict-rules": "^0.12 || ^1.0", + "phpstan/phpstan-strict-rules": "^1", "symfony/cache": "^5.2", "symfony/http-client": "^5.2" }, + "provide": { + "psr/http-server-handler-implementation": "^1.0" + }, + "suggest": { + "nyholm/psr7": "A super lightweight PSR-7 implementation" + }, "autoload": { "psr-4": { "EcPhp\\CasLib\\": "./src/" @@ -64,7 +71,6 @@ "changelog-unreleased": "docker-compose run auto_changelog -c .auto-changelog -u", "changelog-version": "docker-compose run auto_changelog -c .auto-changelog -v", "grumphp": "./vendor/bin/grumphp run", - "phpinsights": "./vendor/bin/phpinsights analyse src/", "phpspec": "./vendor/bin/phpspec run" } } diff --git a/docs/index.rst b/docs/index.rst index df61783..a48ef4e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,10 +3,14 @@ CAS Lib CAS Lib, a standard PHP library for `CAS authentication`_. -The Central Authentication Service (CAS) is an Open-Source single sign-on protocol for the web. -Its purpose is to permit a user to access multiple applications while providing their credentials only once. -It also allows web applications to authenticate users without gaining access to a user's security credentials, -such as a password. The name CAS also refers to a software package that implements this protocol. +The Central Authentication Service (CAS) is an Open-Source single sign-on +protocol for the web. +Its purpose is to permit a user to access multiple applications while providing +their credentials only once. +It also allows web applications to authenticate users without gaining access to +a user's security credentials, +such as a password. The name CAS also refers to a software package that +implements this protocol. For improving the flexibility and in order to maximize it, it is able to authenticate users and leaves the session handling up to the developer. @@ -15,11 +19,11 @@ In order to foster a greater adoption of this library, it has been built with interoperability in mind. It only uses `PHP Standards Recommendations`_ interfaces. -- `PSR-3`_ for logging, - `PSR-4`_ for classes autoloading, - `PSR-6`_ for caching, - `PSR-7`_ for HTTP messages (requests, responses), - `PSR-12`_ for coding standards, +- `PSR-15`_ for HTTP Server Request Handlers, - `PSR-17`_ for HTTP messages factories, - `PSR-18`_ for HTTP client. @@ -28,11 +32,11 @@ any PHP project, with any framework. .. _CAS authentication: https://en.wikipedia.org/wiki/Central_Authentication_Service .. _PHP Standards Recommendations: https://www.php-fig.org/ -.. _PSR-3: https://www.php-fig.org/psr/psr-3/ .. _PSR-4: https://www.php-fig.org/psr/psr-4/ .. _PSR-6: https://www.php-fig.org/psr/psr-6/ .. _PSR-7: https://www.php-fig.org/psr/psr-7/ .. _PSR-12: https://www.php-fig.org/psr/psr-12/ +.. _PSR-15: https://www.php-fig.org/psr/psr-15/ .. _PSR-17: https://www.php-fig.org/psr/psr-17/ .. _PSR-18: https://www.php-fig.org/psr/psr-18/ diff --git a/docs/pages/configuration.rst b/docs/pages/configuration.rst index 59f837a..9ca9604 100644 --- a/docs/pages/configuration.rst +++ b/docs/pages/configuration.rst @@ -7,36 +7,21 @@ Configuration protocol: login: path: /login - allowed_parameters: - - service - - renew - - gateway + default_parameters: + foo: bar serviceValidate: path: /p3/serviceValidate - allowed_parameters: - - service - - ticket - - pgtUrl - - renew - - format default_parameters: pgtUrl: https://my-app/casProxyCallback logout: path: /logout - allowed_parameters: - - service default_parameters: service: https://my-app/homepage proxy: path: /proxy - allowed_parameters: - - targetService - - pgt + default_parameters: + foo: bar proxyValidate: path: /proxyValidate - allowed_parameters: - - service - - ticket - - pgtUrl default_parameters: - pgtUrl: https://my-app/casProxyCallback \ No newline at end of file + pgtUrl: https://my-app/casProxyCallback diff --git a/docs/pages/installation.rst b/docs/pages/installation.rst index ea8ca93..40afb26 100644 --- a/docs/pages/installation.rst +++ b/docs/pages/installation.rst @@ -7,4 +7,27 @@ The easiest way to install it is through Composer_ composer require ecphp/cas-lib -.. _Composer: https://getcomposer.org \ No newline at end of file +Based on the context this package is used, you might also need to install +a package which provides `PSR7 implementations`_. + +There are `many packages implementing PSR7`_, you can pick the one you prefer, +exemple: + +.. code-block:: bash + + composer require nyholm/psr7 + +Next, you'll need an implementation of PSR17_. PSR17 provides the required +factories for the HTTP protocol. In order to facilitate the customizations, +you can either implements your own PSR17 implementation or use `loophp/psr17`_ +which provides a default one: + +.. code-block:: bash + + composer require loophp/psr17 + +.. _Composer: https://getcomposer.org +.. _PSR7 implementations: https://www.php-fig.org/psr/psr-7/ +.. _many packages implementing PSR7: https://packagist.org/providers/psr/http-message-implementation +.. _PSR17: https://www.php-fig.org/psr/psr-17/ +.. _`loophp/psr17`: https://github.com/loophp/psr17 \ No newline at end of file diff --git a/docs/pages/requirements.rst b/docs/pages/requirements.rst index eb77ff8..5dfcfab 100644 --- a/docs/pages/requirements.rst +++ b/docs/pages/requirements.rst @@ -4,7 +4,7 @@ Requirements PHP --- -PHP greater than 7.1 is required for this library. +PHP >= 7.4 is required for this library. PHP Extensions -------------- @@ -24,13 +24,11 @@ class. +------------------+-----------+---------------------------------+------------------------+ | Dependency | PSR | Implementations | Example package | +==================+===========+=================================+========================+ -| Logger | `PSR-3`_ | `log-implementation`_ | `monolog/monolog`_ | -+------------------+-----------+---------------------------------+------------------------+ | Cache | `PSR-6`_ | `cache-implementation`_ | `symfony/cache`_ | +------------------+-----------+---------------------------------+------------------------+ -| Server request | `PSR-7`_ | `http-message-implementations`_ | `nyholm/psr7-server`_ | +| HTTP Message | `PSR-7`_ | `http-message-implementations`_ | `nyholm/psr7`_ | +------------------+-----------+---------------------------------+------------------------+ -| HTTP factories | `PSR-17`_ | `http-factory-implementations`_ | `nyholm/psr7`_ | +| HTTP Factory | `PSR-17`_ | `http-factory-implementations`_ | `loophp/psr17`_ | +------------------+-----------+---------------------------------+------------------------+ | HTTP Client | `PSR-18`_ | `http-client-implementations`_ | `symfony/http-client`_ | +------------------+-----------+---------------------------------+------------------------+ @@ -46,18 +44,15 @@ You may use custom code for that, but you can also use any of the following pack .. _zendframework/zend-httphandlerrunner: https://packagist.org/packages/zendframework/zend-httphandlerrunner .. _http-interop/response-sender: https://packagist.org/packages/http-interop/response-sender -.. _monolog/monolog: https://packagist.org/packages/monolog/monolog -.. _nyholm/psr7-server: https://packagist.org/packages/nyholm/psr7-server .. _nyholm/psr7: https://packagist.org/packages/nyholm/psr7 +.. _loophp/psr17: https://packagist.org/packages/loophp/psr17 .. _symfony/cache: https://packagist.org/packages/symfony/cache .. _symfony/http-client: https://packagist.org/packages/symfony/http-client .. _cache-implementation: https://packagist.org/providers/psr/cache-implementation .. _http-client-implementations: https://packagist.org/providers/psr/http-client-implementation .. _http-factory-implementations: https://packagist.org/providers/psr/http-factory-implementation .. _http-message-implementations: https://packagist.org/providers/psr/http-message-implementation -.. _log-implementation: https://packagist.org/providers/psr/log-implementation .. _PSR-17: https://www.php-fig.org/psr/psr-17/ .. _PSR-18: https://www.php-fig.org/psr/psr-18/ -.. _PSR-3: https://www.php-fig.org/psr/psr-3/ .. _PSR-6: https://www.php-fig.org/psr/psr-6/ .. _PSR-7: https://www.php-fig.org/psr/psr-7/ diff --git a/docs/pages/tests.rst b/docs/pages/tests.rst index 2866c13..7083409 100644 --- a/docs/pages/tests.rst +++ b/docs/pages/tests.rst @@ -1,57 +1,17 @@ Tests, code quality and code style ================================== -Every time changes are introduced into the library, `Travis CI`_ and `Github Actions`_ -run the tests written with `PHPSpec`_. +Every time changes are introduced into the library, the continuous integration +system run and validate the tests. -`PHPInfection`_ is also triggered used to ensure that your code is properly -tested. +A PHP quality tool, Grumphp_, is used to orchestrate all these tasks at each +commit on the local machine, but also on the continuous integration tool in use. -The code style is based on `PSR-12`_ plus a set of custom rules. -Find more about the code style in use in the package `drupol/php-conventions`_. - -A PHP quality tool, Grumphp_, is used to orchestrate all these tasks at each commit -on the local machine, but also on the continuous integration tools (Travis, Github actions) - -To run the whole tests tasks locally, do +To run the tests locally: .. code-block:: bash composer grumphp -or - -.. code-block:: bash - - ./vendor/bin/grumphp run - -Here's an example of output that shows all the tasks that are setup in Grumphp and that -will check your code - -.. code-block:: bash - - $ ./vendor/bin/grumphp run - GrumPHP is sniffing your code! - Running task 1/13: SecurityChecker... ✔ - Running task 2/13: Composer... ✔ - Running task 3/13: ComposerNormalize... ✔ - Running task 4/13: YamlLint... ✔ - Running task 5/13: JsonLint... ✔ - Running task 6/13: PhpLint... ✔ - Running task 7/13: TwigCs... ✔ - Running task 8/13: PhpCsAutoFixerV2... ✔ - Running task 9/13: PhpCsFixerV2... ✔ - Running task 10/13: Phpcs... ✔ - Running task 11/13: PhpStan... ✔ - Running task 12/13: Phpspec... ✔ - Running task 13/13: Infection... ✔ - $ - - -.. _PSR-12: https://www.php-fig.org/psr/psr-12/ -.. _drupol/php-conventions: https://github.com/drupol/php-conventions -.. _Travis CI: https://travis-ci.org/ecphp/cas-lib/builds -.. _Github Actions: https://github.com/ecphp/cas-lib/actions -.. _PHPSpec: http://www.phpspec.net/ -.. _PHPInfection: https://github.com/infection/infection -.. _Grumphp: https://github.com/phpro/grumphp \ No newline at end of file +.. _ecphp/php-conventions: https://github.com/ecphp/php-conventions +.. _Grumphp: https://github.com/phpro/grumphp diff --git a/docs/pages/usage.rst b/docs/pages/usage.rst index e0fe877..1875fe3 100644 --- a/docs/pages/usage.rst +++ b/docs/pages/usage.rst @@ -1,15 +1,19 @@ Usage ===== -Apereo_ already provides a demo CAS server without no proxy authentication mechanism enabled. +Apereo_ already provides a demo CAS server without no proxy authentication +mechanism enabled. -In order to test the libraries here, I've setup another `CAS server with Proxy authentication enabled`_ this time. +In order to test the libraries here, I've setup another +`CAS server with Proxy authentication enabled`_ this time. Feel free to use it for your tests. -.. warning:: If your client application is not hosted on a public server and in HTTPS, this won't work. +.. warning:: If your client application is not hosted on a public server and in + HTTPS, this won't work. -.. tip:: See more on the page :ref:`development`. if you want to have your own local CAS server. +.. tip:: See more on the page :ref:`development`. if you want to have your own + local CAS server. The test login is `casuser`, password is: `Mellon` @@ -24,16 +28,16 @@ Test `the bare PHP demo application`_ now. Symfony ------- -The CAS Lib library can be used in a Symfony (4 or 5) project through the package `ecphp/cas-bundle`_ +The CAS Lib library can be used in a Symfony project through the package `ecphp/cas-bundle`_ -Test `the Symfony bundle demo application`_ now. +Test `the Symfony demo application`_ now. See `the documentation of the ecphp/cas-bundle`_ for more information. .. _Apereo: https://www.apereo.org/ .. _ecphp/cas-bundle: https://github.com/ecphp/cas-bundle .. _the documentation of the ecphp/cas-bundle: http://github.com/ecphp/cas-bundle -.. _the Symfony bundle demo application: https://cas-bundle-demo.herokuapp.com/ +.. _the Symfony demo application: https://cas-bundle-demo.herokuapp.com/ .. _CAS server with Proxy authentication enabled: https://heroku-cas-server.herokuapp.com/cas/login .. _drupol/psrcas-client-poc: https://github.com/drupol/psrcas-client-poc/ .. _the bare PHP demo application: https://psrcas-php-demo.herokuapp.com/ diff --git a/spec/EcPhp/CasLib/Cas.php b/spec/EcPhp/CasLib/Cas.php index 78e4074..433b8f1 100644 --- a/spec/EcPhp/CasLib/Cas.php +++ b/spec/EcPhp/CasLib/Cas.php @@ -12,66 +12,56 @@ namespace spec\EcPhp\CasLib; use EcPhp\CasLib\Configuration\Properties as CasProperties; +use Exception; use PhpSpec\ObjectBehavior; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; -use tests\EcPhp\CasLib\Exception\TestClientException; +use Symfony\Contracts\HttpClient\ResponseInterface; class Cas extends ObjectBehavior { public static function getHttpClientMock() { - $callback = static function ($method, $url, $options) { + $callback = static function ($method, $url, $options): ResponseInterface { $body = ''; - $info = []; + $info = [ + 'response_headers' => [ + 'Content-Type' => 'application/xml', + ], + ]; switch ($url) { - case 'http://local/cas/serviceValidate?service=service&ticket=ticket': - case 'http://local/cas/serviceValidate?ticket=ST-ticket&service=http%3A%2F%2Ffrom': - case 'http://local/cas/serviceValidate?ticket=ST-ticket&service=http%3A%2F%2Flocal%2Fcas%2FserviceValidate%3Fservice%3Dservice': - case 'http://local/cas/serviceValidate?ticket=PT-ticket&service=http%3A%2F%2Flocal%2Fcas%2FproxyValidate%3Fservice%3Dservice': - case 'http://local/cas/serviceValidate?ticket=PT-ticket&service=http%3A%2F%2Ffrom': - $body = <<< 'EOF' - - - username - - - EOF; - - break; - - case 'http://local/cas/serviceValidate?service=service&ticket=ticket-failure': + case 'http://local/cas/proxyValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_get_credentials_with_pgtUrl%2Fmissing_pgt': + case 'http://from/it_can_detect_a_wrong_proxy_response': + case 'http://local/cas/serviceValidate?format=XML&service=http%3A%2F%2Ffrom&ticket=ST-TICKET-VALID': + case 'http://local/cas/serviceValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_test_the_proxy_mode_without_pgtUrl&ticket=ST-TICKET-VALID': + case 'http://local/cas/serviceValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_get_credentials_without_pgtUrl': + case 'http://local/cas/serviceValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_validate_a_service_ticket': $body = <<< 'EOF' - - - - + + + username + + EOF; break; - case 'http://local/cas/proxyValidate?service=service&ticket=ticket': - case 'http://local/cas/proxyValidate?ticket=PT-ticket&service=http%3A%2F%2Ffrom': - case 'http://local/cas/proxyValidate?ticket=ST-ticket&service=http%3A%2F%2Ffrom': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Flocal%2Fcas%2FproxyValidate%3Fservice%3Dservice&ticket=ticket': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Flocal%2Fcas%2FproxyValidate%3Fservice%3Dservice%26renew%3Dtrue&ticket=ticket&renew=true': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Flocal%2Fcas%2FserviceValidate%3Fservice%3Dservice&ticket=ticket': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Flocal%2Fcas%2FserviceValidate%3Fservice%3Dservice%26renew%3Dtrue&ticket=ticket&renew=true': + case 'http://local/cas/serviceValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_validate_a_bad_service_validate_request&ticket=ST-TICKET-INVALID': $body = <<< 'EOF' - - username - - http://app/proxyCallback.php - - + + service and ticket parameters are both required + EOF; break; - case 'http://local/cas/serviceValidate?ticket=ST-ticket-pgt&service=http%3A%2F%2Ffrom': + case 'http://local/cas/proxyValidate?format=XML&service=http%3A%2F%2Ffrom&ticket=ST-TICKET-VALID': + case 'http://local/cas/proxyValidate?format=XML&service=http%3A%2F%2Ffrom&ticket=PT-TICKET-VALID': + case 'http://local/cas/proxyValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_test_the_proxy_mode_with_pgtUrl&ticket=ST-TICKET-VALID': + case 'http://local/cas/proxyValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_get_credentials_with_pgtUrl': $body = <<< 'EOF' @@ -83,49 +73,13 @@ public static function getHttpClientMock() break; - case 'http://local/cas/serviceValidate?ticket=ST-ticket-pgt-pgtiou-not-found&service=http%3A%2F%2Ffrom': - $body = <<< 'EOF' - - - username - unknownPgtIou - - - EOF; - - break; - - case 'http://local/cas/proxyValidate?ticket=ST-ticket-pgt-pgtiou-pgtid-null&service=http%3A%2F%2Ffrom': - $body = <<< 'EOF' - - - username - pgtIouWithPgtIdNull - - - EOF; + case 'http://local/cas/serviceValidate?format=JSON&service=http%3A%2F%2Ffrom%2Fit_can_parse_json_in_a_response': + $info = [ + 'response_headers' => [ + 'Content-Type' => 'application/json', + ], + ]; - break; - - case 'http://local/cas/proxyValidate?service=service&ticket=ST-ticket-pgt': - case 'http://local/cas/proxyValidate?ticket=ST-ticket-pgt&service=http%3A%2F%2Ffrom': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Ffrom&ticket=PT-ticket': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Flocal%2Fcas%2FproxyValidate%3Fservice%3Dhttp%253A%252F%252Ffrom&ticket=PT-ticket-pgt': - $body = <<< 'EOF' - - - username - pgtIou - - http://app/proxyCallback.php - - - - EOF; - - break; - - case 'http://local/cas/serviceValidate?service=http%3A%2F%2Flocal%2Fcas%2FserviceValidate%3Fservice%3Dservice%26format%3DJSON&ticket=ticket&format=JSON': $body = <<< 'EOF' { "serviceResponse": { @@ -138,7 +92,10 @@ public static function getHttpClientMock() break; - case 'http://local/cas/proxy?targetService=targetService&pgt=pgt': + case 'http://local/cas/proxy?service=service-valid': + case 'http://local/cas/serviceValidate?format=XML&service=http%3A%2F%2Ffrom%2Fit_can_detect_when_response_type_is_invalid&ticket=ST-TICKET-VALID': + case 'http://from/it_can_request_a_proxy_ticket': + case 'http://from/it_can_parse_a_good_proxy_request_response': $body = <<< 'EOF' - - - TODO: Find something to put here. - - - EOF; - - break; - - case 'http://local/cas/serviceValidate?service=http%3A%2F%2Ffrom&ticket=BAD-http-query': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Ffrom&ticket=BAD-http-query': - case 'http://local/cas/proxyValidate?service=http%3A%2F%2Flocal%2Fcas%2FproxyValidate%3Fservice%3Dservice%26error%3DTestClientException&ticket=ticket&error=TestClientException': - case 'http://local/cas/proxy?targetService=targetService&pgt=pgt&error=TestClientException': - throw new TestClientException(); + default: + throw new Exception(sprintf('URL %s is not defined in the HTTP mock client.', $url)); break; } @@ -188,48 +128,24 @@ public static function getTestProperties(): CasProperties 'protocol' => [ 'login' => [ 'path' => '/login', - 'allowed_parameters' => [ - 'service', - 'custom', - 'renew', - 'gateway', - ], ], 'logout' => [ 'path' => '/logout', - 'allowed_parameters' => [ - 'service', - 'custom', - ], ], 'serviceValidate' => [ 'path' => '/serviceValidate', - 'allowed_parameters' => [ - 'ticket', - 'service', - 'custom', - ], 'default_parameters' => [ 'format' => 'XML', ], ], 'proxyValidate' => [ 'path' => '/proxyValidate', - 'allowed_parameters' => [ - 'ticket', - 'service', - 'custom', - ], 'default_parameters' => [ 'format' => 'XML', ], ], 'proxy' => [ 'path' => '/proxy', - 'allowed_parameters' => [ - 'targetService', - 'pgt', - ], ], ], ]); diff --git a/spec/EcPhp/CasLib/CasSpec.php b/spec/EcPhp/CasLib/CasSpec.php index ac2c29c..ebb4e02 100644 --- a/spec/EcPhp/CasLib/CasSpec.php +++ b/spec/EcPhp/CasLib/CasSpec.php @@ -13,26 +13,25 @@ use EcPhp\CasLib\Cas; use EcPhp\CasLib\Configuration\Properties as CasProperties; -use EcPhp\CasLib\Introspection\Introspector; -use EcPhp\CasLib\Introspection\ServiceValidate; +use EcPhp\CasLib\Exception\CasException; +use EcPhp\CasLib\Exception\CasExceptionInterface; +use EcPhp\CasLib\Exception\CasHandlerException; +use EcPhp\CasLib\Response\CasResponseBuilder; +use EcPhp\CasLib\Response\Factory\AuthenticationFailureFactory; +use EcPhp\CasLib\Response\Factory\ProxyFactory; +use EcPhp\CasLib\Response\Factory\ProxyFailureFactory; +use EcPhp\CasLib\Response\Factory\ServiceValidateFactory; +use EcPhp\CasLib\Utils\Uri as UtilsUri; +use Ergebnis\Http\Method; use Exception; -use InvalidArgumentException; +use loophp\psr17\Psr17; use Nyholm\Psr7\Factory\Psr17Factory; -use Nyholm\Psr7\Response; use Nyholm\Psr7\ServerRequest; use Nyholm\Psr7\Uri; -use Nyholm\Psr7Server\ServerRequestCreator; use PhpSpec\ObjectBehavior; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; -use Psr\Http\Client\ClientInterface; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\UriFactoryInterface; -use Psr\Log\LoggerInterface; use spec\EcPhp\CasLib\Cas as CasSpecUtils; use Symfony\Component\Cache\CacheItem; use Symfony\Component\HttpClient\Psr18Client; @@ -50,165 +49,102 @@ class CasSpec extends ObjectBehavior protected $cacheItem; /** - * @var \Psr\Log\LoggerInterface - */ - protected $logger; - - public function it_can_authenticate() - { - $request = new ServerRequest('GET', 'http://from?ticket=ST-TICKET'); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - - $request = new ServerRequest('GET', 'http://from?ticket=ST-ticket'); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeArray(); - - $request = new ServerRequest('GET', 'http://from?ticket=FOO-TICKET'); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - - $request = new ServerRequest('GET', 'http://from?ticket=ST-INVALID-JSON&format=JSON'); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - } - - /** - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Http\Message\ServerRequestInterface $serverRequest - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Http\Client\ClientInterface $client - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Http\Message\UriFactoryInterface $uriFactory - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Http\Message\RequestFactoryInterface $requestFactory - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Http\Message\ResponseFactoryInterface $responseFactory - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Http\Message\StreamFactoryInterface $streamFactory * @param \PhpSpec\Wrapper\Collaborator|\Psr\Cache\CacheItemPoolInterface $cache - * @param \PhpSpec\Wrapper\Collaborator|\Psr\Log\LoggerInterface $logger * * @throws \Psr\Cache\InvalidArgumentException */ - public function it_can_authenticate_a_request(ServerRequestInterface $serverRequest, ClientInterface $client, UriFactoryInterface $uriFactory, RequestFactoryInterface $requestFactory, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, CacheItemPoolInterface $cache, LoggerInterface $logger) + public function it_can_authenticate_a_request(CacheItemPoolInterface $cache) { $psr17Factory = new Psr17Factory(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $cacheItem = new CacheItem(); - $cacheItem->set('pgtId'); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - //$logger = new Logger('psrcas', [new StreamHandler('php://stderr')]); - - $uri = new Uri('http://from?ticket=ST-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket']); - - $this->beConstructedWith($request, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $this - ->authenticate() - ->shouldBeArray(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + CasSpecUtils::getTestProperties(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) + ); - $uri = new Uri('http://from?ticket=foo'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'foo']); + $request = new ServerRequest( + Method::GET, + 'http://from?ticket=ST-TICKET-INVALID' + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); + ->shouldThrow(CasException::class) + ->during('authenticate', [$request]); - $uri = new Uri('http://from?ticket=PT-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'PT-ticket']); + $request = new ServerRequest( + Method::GET, + 'http://from?ticket=ST-TICKET-VALID' + ); $this - ->withServerRequest($request) - ->authenticate() + ->authenticate($request) ->shouldBeArray(); - $uri = new Uri('http://from'); - $request = new ServerRequest('GET', $uri); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - - $uri = new Uri('http://from'); - $request = new ServerRequest('GET', $uri); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - - $uri = new Uri('http://from?ticket=ST-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket']); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-VALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() + ->authenticate($request) ->shouldBeArray(); - $uri = new Uri('http://from?ticket=ST-FOO'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-FOO']); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - - $uri = new Uri('http://from?ticket=ST-ticket-pgt'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket-pgt']); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-INVALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeArray(); + ->shouldThrow(Exception::class) + ->during('authenticate', [$request]); - $uri = new Uri('http://from?ticket=ST-ticket-pgt-pgtiou-not-found'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket-pgt-pgtiou-not-found']); + $request = new ServerRequest( + Method::GET, + new Uri('http://from') + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('authenticate', [$request]); - $uri = new Uri('http://from?ticket=ST-ticket-pgt-pgtiou-pgtid-null'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket-pgt-pgtiou-pgtid-null']); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-VALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); + ->authenticate($request) + ->shouldBeEqualTo([ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + ], + ], + ]); } - public function it_can_authenticate_a_request_in_proxy_mode(ServerRequestInterface $serverRequest, ClientInterface $client, UriFactoryInterface $uriFactory, RequestFactoryInterface $requestFactory, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, CacheItemPoolInterface $cache, LoggerInterface $logger) + public function it_can_authenticate_a_request_in_proxy_mode(CacheItemPoolInterface $cache) { $psr17Factory = new Psr17Factory(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); $cacheItem = new CacheItem(); $cacheItem->set('pgtId'); @@ -221,331 +157,156 @@ public function it_can_authenticate_a_request_in_proxy_mode(ServerRequestInterfa ->getItem('pgtIou') ->willReturn($cacheItem); - //$logger = new Logger('psrcas', [new StreamHandler('php://stderr')]); - - $uri = new Uri('http://from?ticket=ST-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket']); + $cache + ->hasItem('unknownPgtIou') + ->willReturn(false); - $this->beConstructedWith($request, CasSpecUtils::getTestPropertiesWithPgtUrl(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); + $cache + ->hasItem('pgtIouWithPgtIdNull') + ->willReturn(true); - $this - ->authenticate() - ->shouldBeArray(); + $cache + ->getItem('pgtIouWithPgtIdNull') + ->willReturn(new CacheItem()); + + $this->beConstructedWith( + CasSpecUtils::getTestPropertiesWithPgtUrl(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) + ); - $uri = new Uri('http://from?ticket=foo'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'foo']); + $request = new ServerRequest( + Method::GET, + 'http://from?ticket=ST-TICKET-INVALID' + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); + ->shouldThrow(CasException::class) + ->during('authenticate', [$request]); - $uri = new Uri('http://from?ticket=PT-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'PT-ticket']); + $request = new ServerRequest( + Method::GET, + 'http://from?ticket=ST-TICKET-VALID' + ); $this - ->withServerRequest($request) - ->authenticate() + ->authenticate($request) ->shouldBeArray(); - $uri = new Uri('http://from'); - $request = new ServerRequest('GET', $uri); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-VALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); + ->authenticate($request) + ->shouldBeArray(); - $uri = new Uri('http://from'); - $request = new ServerRequest('GET', $uri); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-INVALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('authenticate', [$request]); - $uri = new Uri('http://from?ticket=ST-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket']); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-VALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() + ->authenticate($request) ->shouldBeArray(); - $uri = new Uri('http://from?ticket=ST-FOO'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-FOO']); - - $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - - $uri = new Uri('http://from?ticket=ST-ticket-pgt'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket-pgt']); + $request = new ServerRequest( + Method::GET, + new Uri('http://from') + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeArray(); + ->shouldThrow(Exception::class) + ->during('authenticate', [$request]); - $uri = new Uri('http://from?ticket=ST-ticket-pgt-pgtiou-not-found'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket-pgt-pgtiou-not-found']); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-TICKET-VALID'] + ) + ); $this - ->withServerRequest($request) - ->authenticate() - ->shouldBeNull(); - } - - public function it_can_be_constructed_without_base_url(LoggerInterface $logger, CacheItemPoolInterface $cache) - { - $properties = new CasProperties([ - 'base_url' => '//////', - 'protocol' => [ - 'login' => [ - 'path' => '/login', - 'allowed_parameters' => [ - 'coin', + ->authenticate($request) + ->shouldBeEqualTo([ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + 'proxyGrantingTicket' => 'pgtId', ], ], - ], - ]); + ]); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-ticket-pgt-pgtiou-not-found'] + ) ); - $serverRequest = $creator->fromGlobals(); - $this->beConstructedWith($serverRequest, $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $request = new ServerRequest('GET', 'http://foo'); - - $this - ->withServerRequest($request) - ->login() - ->getHeaders() - ->shouldReturn(['Location' => ['/login']]); - } - - public function it_can_check_if_the_logger_works_during_a_failed_authentication_of_service_ticket(ServerRequestInterface $serverRequest, ClientInterface $client, UriFactoryInterface $uriFactory, RequestFactoryInterface $requestFactory, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $cacheItem = new CacheItem(); - $cacheItem->set('pgtId'); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $uri = new Uri('http://from?ticket=BAD-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'BAD-ticket']); - - $this->beConstructedWith($request, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); $this - ->authenticate() - ->shouldBeNull(); - - $logger - ->error('Unable to parse the response with the specified format {format}.', ['format' => 'XML', 'response' => '']) - ->shouldHaveBeenCalledOnce(); - - $logger - ->error('Unable to parse the response during the normalization process.', ['body' => '']) - ->shouldHaveBeenCalledOnce(); - - $logger - ->error('Unable to detect the response format.') - ->shouldHaveBeenCalledOnce(); - - $logger - ->error('Unable to authenticate the user.') - ->shouldHaveBeenCalledOnce(); + ->shouldThrow(Exception::class) + ->during('authenticate', [$request]); - $logger - ->error('Unable to authenticate the request.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_check_if_the_logger_works_during_a_failed_proxy_validate_request(ServerRequestInterface $serverRequest, ClientInterface $client, UriFactoryInterface $uriFactory, RequestFactoryInterface $requestFactory, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $cacheItem = new CacheItem(); - $cacheItem->set('pgtId'); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $uri = new Uri('http://from?ticket=BAD-http-query'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'BAD-ticket']); - - $this->beConstructedWith($request, CasSpecUtils::getTestPropertiesWithPgtUrl(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $this - ->requestProxyValidate() - ->shouldBeNull(); - - $logger - ->error('Error during the proxy validate request.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_check_if_the_logger_works_during_a_failed_service_validate_request(ServerRequestInterface $serverRequest, ClientInterface $client, UriFactoryInterface $uriFactory, RequestFactoryInterface $requestFactory, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $cacheItem = new CacheItem(); - $cacheItem->set('pgtId'); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $uri = new Uri('http://from?ticket=BAD-http-query'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'BAD-ticket']); - - $this->beConstructedWith($request, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from'), + ['ticket' => 'ST-ticket-pgt-pgtiou-pgtid-null'] + ) + ); $this - ->requestServiceValidate() - ->shouldBeNull(); - - $logger - ->error('Error during the service validate request.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_check_if_the_logger_works_during_a_successful_authentication_of_service_ticket(ServerRequestInterface $serverRequest, ClientInterface $client, UriFactoryInterface $uriFactory, RequestFactoryInterface $requestFactory, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $cacheItem = new CacheItem(); - $cacheItem->set('pgtId'); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $uri = new Uri('http://from?ticket=ST-ticket'); - $request = (new ServerRequest('GET', $uri)) - ->withQueryParams(['ticket' => 'ST-ticket']); - - $this->beConstructedWith($request, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $response = $this - ->getWrappedObject() - ->authenticate(); - - $logger - ->debug('Response normalization succeeded.', ['body' => json_encode($response)]) - ->shouldHaveBeenCalledOnce(); + ->shouldThrow(Exception::class) + ->during('authenticate', [$request]); } public function it_can_check_if_the_request_needs_authentication() { - $from = 'http://local/page'; - $request = new ServerRequest('GET', $from); + $request = new ServerRequest( + Method::GET, + 'http://from' + ); $this - ->withServerRequest($request) - ->supportAuthentication() + ->supportAuthentication($request) ->shouldReturn(false); - $from = 'http://local/page?ticket=ticket'; - $request = new ServerRequest('GET', $from); + $request = new ServerRequest(Method::GET, 'http://from?ticket=ticket'); $this - ->withServerRequest($request) - ->supportAuthentication() + ->supportAuthentication($request) ->shouldReturn(true); } - public function it_can_detect_the_type_of_a_response(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $body = <<< 'EOF' - - - username - - - EOF; - - $headers = [ - 'Content-Type' => 'application/xml', - ]; - - $response = new Response( - 200, - $headers, - $body - ); - - $this - ->detect( - $response - ) - ->shouldReturnAnInstanceOf(ServiceValidate::class); - - $body = <<< 'EOF' - - - username - - - EOF; - - $headers = [ - 'Content-Type' => 'application/foo', - ]; - - $response = new Response( - 200, - $headers, - $body - ); - - $this - ->shouldThrow(InvalidArgumentException::class) - ->during('detect', [$response]); - } - public function it_can_detect_when_gateway_and_renew_are_set_together() { $from = 'http://local/'; @@ -555,22 +316,30 @@ public function it_can_detect_when_gateway_and_renew_are_set_together() 'gateway' => true, ]; + $request = new ServerRequest( + Method::GET, + $from + ); + $this - ->withServerRequest(new ServerRequest('GET', $from)) - ->login($parameters) - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('login', [$request, $parameters]); $parameters = [ 'gateway' => true, ]; + $request = new ServerRequest( + Method::GET, + $from . '?gateway=false' + ); + $this - ->withServerRequest(new ServerRequest('GET', $from . '?gateway=false')) - ->login($parameters) - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('login', [$request, $parameters]); } - public function it_can_detect_wrong_url(LoggerInterface $logger, CacheItemPoolInterface $cache) + public function it_can_detect_wrong_url(CacheItemPoolInterface $cache) { $properties = new CasProperties([ 'base_url' => '', @@ -584,196 +353,149 @@ public function it_can_detect_wrong_url(LoggerInterface $logger, CacheItemPoolIn ], ]); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + $properties, + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) ); - $serverRequest = $creator->fromGlobals(); - - $this->beConstructedWith($serverRequest, $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); $parameters = [ 'service' => 'service', 'ticket' => 'ticket', ]; + $request = new ServerRequest(Method::GET, 'error'); + $this - ->withServerRequest(new ServerRequest('GET', 'error')) - ->requestServiceValidate($parameters) - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('requestServiceValidate', [$request, $parameters]); } public function it_can_do_a_request_to_validate_a_ticket() { - $from = 'http://local/cas/serviceValidate?service=service'; - $request = new ServerRequest('GET', $from); + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_do_a_request_to_validate_a_ticket/no-ticket') + ); $this - ->withServerRequest($request) - ->requestTicketValidation() - ->shouldBeNull(); - - $from = 'http://local/cas/serviceValidate?service=service&ticket=ST-ticket'; - $request = new ServerRequest('GET', $from); + ->shouldThrow(Exception::class) + ->during('requestTicketValidation', [$request]); - $this - ->withServerRequest($request) - ->requestTicketValidation() - ->shouldBeAnInstanceOf(ResponseInterface::class); + $request = new ServerRequest( + Method::GET, + new Uri('http://from?ticket=ST-TICKET-VALID') + ); - $from = 'http://local/cas/proxyValidate?service=service&ticket=PT-ticket'; - $request = new ServerRequest('GET', $from); + $response = $this + ->requestTicketValidation($request); - $this - ->withServerRequest($request) - ->requestTicketValidation() + $response ->shouldBeAnInstanceOf(ResponseInterface::class); - $from = 'http://local/cas/proxyValidate?service=service&ticket=bar'; - $request = new ServerRequest('GET', $from); + $response + ->shouldThrow(Exception::class) + ->during('getProxyGrantingTicket', [$request]); - $this - ->withServerRequest($request) - ->requestTicketValidation() - ->shouldBeNull(); - - $from = 'http://local/cas/proxyValidate?service=service'; - $request = new ServerRequest('GET', $from); - - $this - ->withServerRequest($request) - ->requestTicketValidation() - ->shouldBeNull(); - - $from = 'http://local/cas/serviceValidate?service=service&ticket=ST-ticket'; - $request = new ServerRequest('GET', $from); + $request = new ServerRequest( + Method::GET, + new Uri('http://from?ticket=EMPTY-BODY') + ); $this - ->withServerRequest($request) - ->requestTicketValidation(['service' => 'foo', 'ticket' => 'bar']) - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('requestTicketValidation', [$request]); } - public function it_can_handle_proxy_callback_request(LoggerInterface $logger, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) + public function it_can_handle_proxy_callback_request() { - $request = new ServerRequest('GET', 'http://local/proxycallback?pgtId=pgtId&pgtIou=false'); - - $this - ->withServerRequest($request) - ->handleProxyCallback() - ->shouldReturnAnInstanceOf(ResponseInterface::class); - - $this - ->withServerRequest($request) - ->handleProxyCallback() - ->getStatusCode() - ->shouldReturn(500); - - $request = new ServerRequest('GET', 'http://local/proxycallback?pgtIou=pgtIou&pgtId=pgtId'); + $request = new ServerRequest( + Method::GET, + 'http://local/proxycallback?pgtId=pgtId&pgtIou=pgtIou' + ); $this - ->withServerRequest($request) - ->handleProxyCallback() + ->handleProxyCallback($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); $this - ->withServerRequest($request) - ->handleProxyCallback() + ->handleProxyCallback($request) ->getStatusCode() ->shouldReturn(200); - $request = new ServerRequest('GET', 'http://local/proxycallback?pgtId=pgtId'); - - $this - ->withServerRequest($request) - ->handleProxyCallback() - ->shouldReturnAnInstanceOf(ResponseInterface::class); + $request = new ServerRequest( + Method::GET, + 'http://local/proxycallback?pgtId=pgtId&pgtIou=false' + ); $this - ->withServerRequest($request) - ->handleProxyCallback() - ->getStatusCode() - ->shouldReturn(500); - - $request = new ServerRequest('GET', 'http://local/proxycallback?pgtIou=pgtIou'); + ->shouldThrow(Exception::class) + ->during('handleProxyCallback', [$request]); - $this - ->withServerRequest($request) - ->handleProxyCallback() - ->shouldReturnAnInstanceOf(ResponseInterface::class); + $request = new ServerRequest( + Method::GET, + 'http://local/proxycallback?pgtId=pgtId' + ); $this - ->withServerRequest($request) - ->handleProxyCallback() - ->getStatusCode() - ->shouldReturn(500); + ->shouldThrow(Exception::class) + ->during('handleProxyCallback', [$request]); - $request = new ServerRequest('GET', 'http://local/proxycallback'); - - $this - ->withServerRequest($request) - ->handleProxyCallback() - ->shouldReturnAnInstanceOf(ResponseInterface::class); + $request = new ServerRequest( + Method::GET, + 'http://local/proxycallback?pgtIou=pgtIou' + ); $this - ->withServerRequest($request) - ->handleProxyCallback() - ->getStatusCode() - ->shouldReturn(200); - - $request = new ServerRequest('GET', 'http://local/proxycallback?pgtId=pgtId&pgtIou=pgtIou'); + ->shouldThrow(Exception::class) + ->during('handleProxyCallback', [$request]); - $this->cache - ->getItem('false') - ->willThrow(new InvalidArgumentException('foo')); + $request = new ServerRequest( + Method::GET, + 'http://local/proxycallback' + ); $this - ->withServerRequest($request) - ->handleProxyCallback() + ->handleProxyCallback($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); $this - ->withServerRequest($request) - ->handleProxyCallback() + ->handleProxyCallback($request) ->getStatusCode() ->shouldReturn(200); - - $response = new Response(200); - - $this - ->withServerRequest($request) - ->handleProxyCallback([], $response) - ->shouldReturn($response); } public function it_can_login() { - $request = new ServerRequest('GET', 'http://local/', ['referer' => 'http://google.com/']); + $request = new ServerRequest(Method::GET, 'http://local/', ['referer' => 'http://google.com/']); $this - ->withServerRequest($request) - ->login() + ->login($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); $this - ->withServerRequest($request) - ->login() + ->login($request) ->getStatusCode() ->shouldReturn(302); - $request = new ServerRequest('GET', 'http://local/'); + $request = new ServerRequest(Method::GET, 'http://local/'); $this - ->withServerRequest($request) - ->login() + ->login($request) ->getHeader('Location') ->shouldReturn(['http://local/cas/login?service=http%3A%2F%2Flocal%2F']); - $request = new ServerRequest('GET', 'http://local/'); + $request = new ServerRequest(Method::GET, 'http://local/'); $parameters = [ 'foo' => 'bar', @@ -781,22 +503,20 @@ public function it_can_login() ]; $this - ->withServerRequest($request) - ->login($parameters) + ->login($request, $parameters) ->getHeader('Location') - ->shouldReturn(['http://local/cas/login?service=http%3A%2F%2Ffoo.bar%2F']); + ->shouldReturn(['http://local/cas/login?foo=bar&service=http%3A%2F%2Ffoo.bar%2F']); $parameters = [ 'custom' => 'foo', ]; $this - ->withServerRequest($request) - ->login($parameters) + ->login($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/login?custom=foo&service=http%3A%2F%2Flocal%2F']); - $request = new ServerRequest('GET', 'http://local/', ['referer' => 'http://referer/']); + $request = new ServerRequest(Method::GET, 'http://local/', ['referer' => 'http://referer/']); $parameters = [ 'foo' => 'bar', @@ -804,18 +524,16 @@ public function it_can_login() ]; $this - ->withServerRequest($request) - ->login($parameters) + ->login($request, $parameters) ->getHeader('Location') - ->shouldReturn(['http://local/cas/login?service=http%3A%2F%2Ffoo.bar%2F']); + ->shouldReturn(['http://local/cas/login?foo=bar&service=http%3A%2F%2Ffoo.bar%2F']); $parameters = [ 'custom' => 'foo', ]; $this - ->withServerRequest($request) - ->login($parameters) + ->login($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/login?custom=foo&service=http%3A%2F%2Flocal%2F']); @@ -825,30 +543,26 @@ public function it_can_login() ]; $this - ->withServerRequest($request) - ->login($parameters) + ->login($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/login?custom=foo']); } public function it_can_logout() { - $request = new ServerRequest('GET', 'http://local/'); + $request = new ServerRequest(Method::GET, 'http://local/'); $this - ->withServerRequest($request) - ->logout() + ->logout($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); $this - ->withServerRequest($request) - ->logout() + ->logout($request) ->getStatusCode() ->shouldReturn(302); $this - ->withServerRequest($request) - ->logout() + ->logout($request) ->getHeader('Location') ->shouldReturn(['http://local/cas/logout']); @@ -857,8 +571,7 @@ public function it_can_logout() ]; $this - ->withServerRequest($request) - ->logout($parameters) + ->logout($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/logout?custom=bar']); @@ -868,20 +581,18 @@ public function it_can_logout() ]; $this - ->withServerRequest($request) - ->logout($parameters) + ->logout($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/logout?custom=bar&service=http%3A%2F%2Fcustom.local%2F']); - $request = new ServerRequest('GET', 'http://local/', ['referer' => 'http://referer/']); + $request = new ServerRequest(Method::GET, 'http://local/', ['referer' => 'http://referer/']); $parameters = [ 'custom' => 'bar', ]; $this - ->withServerRequest($request) - ->logout($parameters) + ->logout($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/logout?custom=bar']); @@ -891,8 +602,7 @@ public function it_can_logout() ]; $this - ->withServerRequest($request) - ->logout($parameters) + ->logout($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/logout?custom=bar&service=http%3A%2F%2Fcustom.local%2F']); @@ -901,72 +611,17 @@ public function it_can_logout() ]; $this - ->withServerRequest($request) - ->logout($parameters) + ->logout($request, $parameters) ->shouldReturnAnInstanceOf(ResponseInterface::class); } - public function it_can_parse_a_bad_proxy_request_response(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $serverRequest = $creator->fromGlobals(); - $this->beConstructedWith($serverRequest, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $url = 'http://from'; - - $this - ->withServerRequest(new ServerRequest('GET', $url)) - ->requestProxyTicket(['targetService' => 'targetService', 'pgt' => 'pgt-error-in-getCredentials']) - ->shouldBeNull(); - - $logger - ->error('Unable to authenticate the user.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_parse_a_good_proxy_request_response(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $serverRequest = $creator->fromGlobals(); - $this->beConstructedWith($serverRequest, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $url = 'http://from'; - - $this - ->withServerRequest(new ServerRequest('GET', $url)) - ->requestProxyTicket(['targetService' => 'targetService', 'pgt' => 'pgt']) - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $logger - ->error('Unable to authenticate the user.') - ->shouldNotBeCalled(); - } - - public function it_can_parse_json_in_a_response(LoggerInterface $logger, CacheItemPoolInterface $cache) + public function it_can_parse_json_in_a_response(CacheItemPoolInterface $cache) { $properties = new CasProperties([ 'base_url' => '', 'protocol' => [ 'serviceValidate' => [ 'path' => 'http://local/cas/serviceValidate', - 'allowed_parameters' => [ - 'service', - 'ticket', - ], 'default_parameters' => [ 'format' => 'JSON', ], @@ -974,102 +629,96 @@ public function it_can_parse_json_in_a_response(LoggerInterface $logger, CacheIt ], ]); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + $properties, + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) ); - $serverRequest = $creator->fromGlobals(); - $this->beConstructedWith($serverRequest, $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - $request = new ServerRequest('GET', 'http://local/cas/serviceValidate?service=service&ticket=ticket&format=JSON'); + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_parse_json_in_a_response') + ); $this - ->withServerRequest($request) - ->requestServiceValidate() + ->requestServiceValidate($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_can_renew_login() { - $from = 'http://local/'; - - $request = new ServerRequest('GET', $from); + $request = new ServerRequest(Method::GET, 'http://local/'); $parameters = [ 'renew' => true, ]; $this - ->withServerRequest($request) - ->login($parameters) + ->login($request, $parameters) ->getHeader('Location') ->shouldReturn(['http://local/cas/login?renew=true&service=http%3A%2F%2Flocal%2F']); - $request = new ServerRequest('GET', $from . '?renew=false'); - - $parameters = [ - 'renew' => true, - ]; + $request = new ServerRequest(Method::GET, 'http://local/?renew=false'); $this - ->withServerRequest($request) - ->login($parameters) - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('login', [$request, $parameters]); } - public function it_can_request_a_proxy_ticket(LoggerInterface $logger, CacheItemPoolInterface $cache) + public function it_can_request_a_proxy_ticket(CacheItemPoolInterface $cache) { - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + CasSpecUtils::getTestProperties(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) ); - $serverRequest = $creator->fromGlobals(); - //$logger = new Logger('psrcas', [new StreamHandler('php://stderr')]); - - $this->beConstructedWith($serverRequest, CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $url = 'http://from'; + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_request_a_proxy_ticket') + ); $this - ->withServerRequest(new ServerRequest('GET', $url)) - ->requestProxyTicket(['targetService' => 'targetService', 'pgt' => 'pgt']) + ->requestProxyTicket($request, ['service' => 'service-valid']) ->shouldBeAnInstanceOf(ResponseInterface::class); - $url = 'http://from?error=TestClientException'; + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_request_a_proxy_ticket') + ); $this - ->withServerRequest(new ServerRequest('GET', $url)) - ->requestProxyTicket(['targetService' => 'targetService', 'pgt' => 'pgt']) - ->shouldBeNull(); - - $logger - ->error('Error during the proxy ticket request.') - ->shouldHaveBeenCalledOnce(); + ->shouldThrow(Exception::class) + ->during('requestProxyTicket', [$request, ['service' => 'service-invalid']]); } - public function it_can_validate_a_bad_proxy_ticket(LoggerInterface $logger, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) + public function it_can_validate_a_bad_proxy_ticket(CacheItemPoolInterface $cache) { $properties = new CasProperties([ 'base_url' => '', 'protocol' => [ 'proxyValidate' => [ 'path' => 'http://local/cas/proxyValidate', - 'allowed_parameters' => [ - 'service', - 'ticket', - 'http_code', - 'invalid_xml', - 'unrelated_xml', - ], 'default_parameters' => [ 'format' => 'XML', ], @@ -1077,351 +726,165 @@ public function it_can_validate_a_bad_proxy_ticket(LoggerInterface $logger, Cach ], ]); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $psr17 = new Psr17( + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory ); - $serverRequest = $creator->fromGlobals(); - - $this->beConstructedWith($serverRequest, $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - $request = new ServerRequest('POST', 'foo'); - - $this - ->withServerRequest($request) - ->requestProxyValidate() - ->shouldBeNull(); + $this->beConstructedWith( + $properties, + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) + ); - $url = 'http://local/cas/proxyValidate?service=service&ticket=ticket&error=TestClientException'; + $request = new ServerRequest( + Method::POST, + 'http://from/it_can_validate_a_bad_proxy_ticket' + ); $this - ->withServerRequest(new ServerRequest('GET', $url)) - ->requestProxyValidate() - ->shouldBeNull(); - - $logger - ->error('Unable to authenticate the user.') - ->shouldHaveBeenCalledOnce(); + ->shouldThrow(Exception::class) + ->during('requestServiceValidate', [$request]); } - public function it_can_validate_a_bad_service_validate_request(LoggerInterface $logger, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) + public function it_can_validate_a_bad_service_validate_request(CacheItemPoolInterface $cache) { - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + CasSpecUtils::getTestProperties(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) ); - $this->beConstructedWith($creator->fromGlobals(), CasSpecUtils::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - $from = 'http://from/'; - - $parameters = [ - 'service' => 'service', - 'ticket' => 'ticket-failure', - ]; - - $request = new ServerRequest('GET', $from); + $request = new ServerRequest( + Method::GET, + 'http://from/it_can_validate_a_bad_service_validate_request' + ); $this - ->withServerRequest($request) - ->requestServiceValidate($parameters) - ->shouldBeNull(); - - $logger - ->error('Unable to authenticate the user.') - ->shouldHaveBeenCalledOnce(); + ->shouldThrow(CasHandlerException::class) + ->during( + 'requestServiceValidate', + [ + $request, + ['ticket' => 'ST-TICKET-INVALID'], + ] + ); } - public function it_can_validate_a_good_proxy_ticket(LoggerInterface $logger, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) + public function it_can_validate_a_service_ticket() { - $properties = new CasProperties([ - 'base_url' => '', - 'protocol' => [ - 'proxyValidate' => [ - 'path' => 'http://local/cas/proxyValidate', - 'allowed_parameters' => [ - 'service', - 'ticket', - 'http_code', - 'invalid_xml', - 'unrelated_xml', - ], - 'default_parameters' => [ - 'format' => 'XML', - ], - ], - ], - ]); - - $cacheItem - ->get() - ->willReturn('pgtIou'); - - $cacheItem - ->set('pgtId') - ->willReturn($cacheItem); - - $cacheItem - ->expiresAfter(300) - ->willReturn($cacheItem); - - $cache - ->save($cacheItem) - ->willReturn(true); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->hasItem('pgtIouInvalid') - ->willReturn(false); - - // See: https://github.com/phpspec/prophecy/pull/429 - $cache - ->hasItem('false') - ->willThrow(new InvalidArgumentException('foo')); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_validate_a_service_ticket') ); - $serverRequest = $creator->fromGlobals(); - - //$logger = new Logger('psrcas', [new StreamHandler('php://stderr')]); - - $this->beConstructedWith($serverRequest, $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $request = new ServerRequest('GET', 'http://local/cas/proxyValidate?service=service&ticket=ticket'); $this - ->withServerRequest($request) - ->requestProxyValidate() + ->requestServiceValidate($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); $this - ->withServerRequest($request) - ->requestProxyValidate() + ->requestServiceValidate($request) ->getStatusCode() ->shouldReturn(200); $this - ->withServerRequest($request) - ->requestProxyValidate() + ->requestServiceValidate($request) ->shouldReturnAnInstanceOf(ResponseInterface::class); - $request = new ServerRequest('GET', 'http://local/cas/proxyValidate?service=service&ticket=ticket&renew=true'); - - $this - ->withServerRequest($request) - ->requestProxyValidate() - ->shouldReturnAnInstanceOf(ResponseInterface::class); - - $url = 'http://local/cas/proxyValidate?ticket=PT-ticket-pgt&service=http%3A%2F%2Ffrom'; - - $this - ->withServerRequest(new ServerRequest('GET', $url)) - ->requestProxyValidate() - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $logger - ->error('Unable to authenticate the user.') - ->shouldNotHaveBeenCalled(); - } - - public function it_can_validate_a_good_service_validate_request(LoggerInterface $logger, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) - { - $properties = new CasProperties([ - 'base_url' => '', - 'protocol' => [ - 'serviceValidate' => [ - 'path' => 'http://local/cas/serviceValidate', - 'allowed_parameters' => [ - 'service', - 'ticket', - 'http_code', - 'invalid_xml', - 'with_pgt', - 'pgt_valid', - 'pgt_is_not_string', - ], - 'default_parameters' => [ - 'format' => 'XML', - ], - ], - ], - ]); - - $cacheItem - ->set('pgtId') - ->willReturn($cacheItem); - - $cacheItem - ->expiresAfter(300) - ->willReturn($cacheItem); - - $cache - ->save($cacheItem) - ->willReturn(true); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->hasItem('pgtIouInvalid') - ->willReturn(false); - - // See: https://github.com/phpspec/prophecy/pull/429 - $cache - ->hasItem('false') - ->willThrow(new InvalidArgumentException('foo')); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_validate_a_service_ticket/404') ); - $this->beConstructedWith($creator->fromGlobals(), $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $from = 'http://local/'; - - $request = new ServerRequest('GET', $from); - - $parameters = [ - 'service' => 'service', - 'ticket' => 'ticket', - ]; $this - ->withServerRequest($request) - ->requestServiceValidate($parameters) - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $logger - ->error('Unable to authenticate the user.') - ->shouldNotHaveBeenCalled(); + ->shouldThrow(Exception::class) + ->during('requestServiceValidate', [$request]); } - public function it_can_validate_a_service_ticket() + public function it_can_validate_any_type_of_ticket() { - $request = new ServerRequest('GET', 'http://local/cas/serviceValidate?service=service&ticket=ticket'); + $request = new ServerRequest(Method::GET, 'http://from?ticket=ST-TICKET-VALID'); $this - ->withServerRequest($request) - ->requestProxyValidate() - ->shouldReturnAnInstanceOf(ResponseInterface::class); + ->requestTicketValidation($request) + ->shouldBeAnInstanceOf(ResponseInterface::class); - $this - ->withServerRequest($request) - ->requestProxyValidate() - ->getStatusCode() - ->shouldReturn(200); + $request = new ServerRequest(Method::GET, 'http://from?ticket=ST-TICKET-INVALID'); $this - ->withServerRequest($request) - ->requestProxyValidate() - ->shouldReturnAnInstanceOf(ResponseInterface::class); + ->shouldThrow(Exception::class) + ->during('requestTicketValidation', [$request]); - $request = new ServerRequest('GET', 'http://local/cas/serviceValidate?service=service&ticket=ticket&http_code=404'); + $request = new ServerRequest(Method::GET, 'http://from/it_can_validate_any_type_of_ticket/ticket-is-available-but-invalid'); $this - ->withServerRequest($request) - ->requestProxyValidate() - ->shouldBeNull(); + ->shouldThrow(CasExceptionInterface::class) + ->during('requestTicketValidation', [$request, ['ticket' => 'ticket-invalid']]); - $request = new ServerRequest('GET', 'http://local/cas/serviceValidate?service=service&ticket=ticket&renew=true'); + $request = new ServerRequest(Method::GET, 'http://from/it_can_validate_any_type_of_ticket/ticket-is-unavailable'); $this - ->withServerRequest($request) - ->requestProxyValidate() - ->shouldReturnAnInstanceOf(ResponseInterface::class); + ->shouldThrow(CasExceptionInterface::class) + ->during('requestTicketValidation', [$request]); } - public function it_can_validate_any_type_of_ticket() + public function it_cannot_be_constructed_without_base_url(CacheItemPoolInterface $cache) { - $body = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => 'username', - ], - ], - ]; - - $request = new ServerRequest('GET', 'http://from?ticket=ST-TICKET'); - $response = new Response( - 200, - ['Content-Type' => 'application/json'], - json_encode($body) - ); - - $this - ->withServerRequest($request) - ->requestTicketValidation([], $response) - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $body = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => 'username', - 'proxyGrantingTicket' => 'pgtIou', + $properties = new CasProperties([ + 'base_url' => '//////', + 'protocol' => [ + 'login' => [ + 'path' => '/login', ], ], - ]; + ]); - $request = new ServerRequest('GET', 'http://from?ticket=PT-TICKET'); - $response = new Response( - 200, - ['Content-Type' => 'application/json'], - json_encode($body) + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + $properties, + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) ); - $this - ->withServerRequest($request) - ->requestTicketValidation([], $response) - ->shouldBeNull(); - - $body = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => 'username', - ], - ], - ]; - - $request = new ServerRequest('GET', 'http://from'); - $response = new Response( - 500, - ['Content-Type' => 'application/json'], - json_encode($body) + $request = new ServerRequest( + Method::GET, + 'http://foo' ); $this - ->withServerRequest($request) - ->requestTicketValidation([], $response) - ->shouldBeNull(); + ->shouldThrow(Exception::class) + ->during('login', [$request]); } public function it_is_initializable() @@ -1429,23 +892,13 @@ public function it_is_initializable() $this->shouldHaveType(Cas::class); } - public function let(LoggerInterface $logger, CacheItemPoolInterface $cache, CacheItemInterface $cacheItemPgtIou, CacheItemInterface $cacheItemPgtIdNull) + public function let(CacheItemPoolInterface $cache, CacheItemInterface $cacheItemPgtIou, CacheItemInterface $cacheItemPgtIdNull) { - $this->logger = $logger; $this->cache = $cache; $this->cacheItem = $cacheItemPgtIou; - $properties = CasSpecUtils::getTestProperties(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $serverRequest = $creator->fromGlobals(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); $cacheItemPgtIou ->set('pgtId') @@ -1499,6 +952,17 @@ public function let(LoggerInterface $logger, CacheItemPoolInterface $cache, Cach ->getItem('pgtIouWithPgtIdNull') ->willReturn($cacheItemPgtIdNull); - $this->beConstructedWith($serverRequest, $properties, $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); + $this->beConstructedWith( + CasSpecUtils::getTestProperties(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) + ); } } diff --git a/spec/EcPhp/CasLib/Handler/LoginSpec.php b/spec/EcPhp/CasLib/Handler/LoginSpec.php new file mode 100644 index 0000000..e58c799 --- /dev/null +++ b/spec/EcPhp/CasLib/Handler/LoginSpec.php @@ -0,0 +1,153 @@ + range(1, 5), + ]; + + $this->beConstructedWith( + $parameters, + $cache, + $casResponseBuilder, + new Psr18Client(Cas::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_deal_with_array_parameters') + ); + + $this + ->handle($request) + ->shouldBeAnInstanceOf(ResponseInterface::class); + + $this + ->handle($request) + ->getHeaderLine('Location') + ->shouldReturn('http://local/cas/login?custom%5B0%5D=1&custom%5B1%5D=2&custom%5B2%5D=3&custom%5B3%5D=4&custom%5B4%5D=5&service=http%3A%2F%2Ffrom%2Fit_can_deal_with_array_parameters'); + } + + public function it_can_deal_with_renew_and_gateway_parameters(CacheItemPoolInterface $cache, CasResponseBuilderInterface $casResponseBuilder) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $parameters = [ + 'renew' => true, + 'gateway' => true, + 'service' => 'service', + ]; + + $this->beConstructedWith( + $parameters, + $cache, + $casResponseBuilder, + new Psr18Client(Cas::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_deal_with_renew_and_gateway_parameters') + ); + + $this + ->shouldThrow(Exception::class) + ->during('handle', [$request]); + } + + public function it_can_deal_with_renew_parameter(CacheItemPoolInterface $cache, CasResponseBuilderInterface $casResponseBuilder) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $parameters = [ + 'renew' => 'coin', + 'gateway' => false, + ]; + + $this->beConstructedWith( + $parameters, + $cache, + $casResponseBuilder, + new Psr18Client(Cas::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_deal_with_renew_parameter') + ); + + $this + ->handle($request) + ->shouldBeAnInstanceOf(ResponseInterface::class); + } + + public function it_can_get_a_response() + { + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_deal_with_renew_parameter') + ); + + $this + ->handle($request) + ->shouldBeAnInstanceOf(ResponseInterface::class); + } + + public function it_is_initializable() + { + $this->shouldHaveType(Login::class); + } + + public function let(CacheItemPoolInterface $cache, CasResponseBuilderInterface $casResponseBuilder) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + [], + $cache, + $casResponseBuilder, + new Psr18Client(Cas::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + } +} diff --git a/spec/EcPhp/CasLib/Handler/LogoutSpec.php b/spec/EcPhp/CasLib/Handler/LogoutSpec.php new file mode 100644 index 0000000..ae775c4 --- /dev/null +++ b/spec/EcPhp/CasLib/Handler/LogoutSpec.php @@ -0,0 +1,59 @@ +handle($request) + ->shouldBeAnInstanceOf(ResponseInterface::class); + } + + public function it_is_initializable() + { + $this->shouldHaveType(Logout::class); + } + + public function let(CacheItemPoolInterface $cache, CasResponseBuilderInterface $casResponseBuilder) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + [], + $cache, + $casResponseBuilder, + new Psr18Client(Cas::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + } +} diff --git a/spec/EcPhp/CasLib/Handler/ProxyCallbackSpec.php b/spec/EcPhp/CasLib/Handler/ProxyCallbackSpec.php index f9b9005..a552bde 100644 --- a/spec/EcPhp/CasLib/Handler/ProxyCallbackSpec.php +++ b/spec/EcPhp/CasLib/Handler/ProxyCallbackSpec.php @@ -11,24 +11,38 @@ namespace spec\EcPhp\CasLib\Handler; +use EcPhp\CasLib\Contract\Response\CasResponseBuilderInterface; use EcPhp\CasLib\Handler\ProxyCallback; +use EcPhp\CasLib\Utils\Uri as UtilsUri; +use Ergebnis\Http\Method; use Exception; +use loophp\psr17\Psr17; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\ServerRequest; +use Nyholm\Psr7\Uri; use PhpSpec\ObjectBehavior; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Log\LoggerInterface; +use Psr\Http\Client\ClientInterface; use spec\EcPhp\CasLib\Cas; class ProxyCallbackSpec extends ObjectBehavior { - public function it_can_catch_issue_with_the_cache(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, LoggerInterface $logger) + public function it_can_catch_issue_with_the_cache(CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, ClientInterface $client, CasResponseBuilderInterface $casResponseBuilder) { $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from?pgtId=pgtId&pgtIou=pgtIou'); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from/it_can_catch_issue_with_the_cache'), + [ + 'pgtId' => 'pgtId', + 'pgtIou' => 'pgtIou', + ] + ) + ); $cacheItem ->set('pgtId') @@ -48,103 +62,31 @@ public function it_can_catch_issue_with_the_cache(ServerRequestInterface $server ->save($cacheItem) ->willReturn(true); - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle(); - - $logger - ->error($uniqid) - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_test_if_the_cache_is_working(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, LoggerInterface $logger) - { - $this - ->handle(); - - $cache - ->save($cacheItem) - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_test_the_logger_when_missing_pgtId(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from?pgtIou=pgtIou'); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); + $this->beConstructedWith([], $cache, $casResponseBuilder, $client, Cas::getTestProperties(), $psr17); $this - ->handle(); - - $logger - ->debug('Missing proxy callback parameter (pgtId).') - ->shouldHaveBeenCalledOnce(); + ->shouldThrow(Exception::class) + ->during('handle', [$request]); } - public function it_can_test_the_logger_when_missing_pgtIou(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, LoggerInterface $logger) + public function it_can_test_if_the_cache_is_working(CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from?pgtId=pgtId'); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); + $request = new ServerRequest( + Method::GET, + UtilsUri::withParams( + new Uri('http://from/it_can_test_if_the_cache_is_working'), + [ + 'pgtId' => 'pgtId', + 'pgtIou' => 'pgtIou', + ] + ) + ); $this - ->handle(); - - $logger - ->debug('Missing proxy callback parameter (pgtIou).') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_test_the_logger_when_no_parameter_is_in_the_url(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from'); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle(); - - $logger - ->debug('CAS server just checked the proxy callback endpoint.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_test_the_logger_when_parameters_are_in_the_url(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from?pgtId=pgtId&pgtIou=pgtIou'); - - $cacheItem - ->set('pgtId') - ->willReturn($cacheItem); - - $cacheItem - ->expiresAfter(300) - ->willReturn($cacheItem); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); + ->handle($request); $cache ->save($cacheItem) - ->willReturn(true); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle(); - - $logger - ->debug('Storing proxy callback parameters (pgtId and pgtIou).') ->shouldHaveBeenCalledOnce(); } @@ -153,11 +95,10 @@ public function it_is_initializable() $this->shouldHaveType(ProxyCallback::class); } - public function let(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, LoggerInterface $logger) + public function let(CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, ClientInterface $client, CasResponseBuilderInterface $casResponseBuilder) { $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from?pgtId=pgtId&pgtIou=pgtIou'); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); $cacheItem ->set('pgtId') @@ -175,6 +116,6 @@ public function let(ServerRequestInterface $serverRequest, CacheItemPoolInterfac ->save($cacheItem) ->willReturn(true); - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); + $this->beConstructedWith([], $cache, $casResponseBuilder, $client, Cas::getTestProperties(), $psr17); } } diff --git a/spec/EcPhp/CasLib/Handler/ProxySpec.php b/spec/EcPhp/CasLib/Handler/ProxySpec.php new file mode 100644 index 0000000..c2563c8 --- /dev/null +++ b/spec/EcPhp/CasLib/Handler/ProxySpec.php @@ -0,0 +1,85 @@ +shouldThrow(Exception::class) + ->during('handle', [$request]); + } + + public function it_can_detect_when_no_credentials() + { + $request = new ServerRequest(Method::GET, 'http://from'); + + $this + ->shouldThrow(Exception::class) + ->during('handle', [$request]); + } + + public function it_is_initializable() + { + $this->shouldHaveType(Proxy::class); + } + + public function let(CacheItemPoolInterface $cache) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17( + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory + ); + + $this->beConstructedWith( + [], + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + } +} diff --git a/spec/EcPhp/CasLib/Handler/ServiceValidateSpec.php b/spec/EcPhp/CasLib/Handler/ServiceValidateSpec.php new file mode 100644 index 0000000..5b352e9 --- /dev/null +++ b/spec/EcPhp/CasLib/Handler/ServiceValidateSpec.php @@ -0,0 +1,156 @@ +shouldThrow(Exception::class) + ->during('handle', [$request]); + } + + public function it_can_detect_when_response_type_is_invalid() + { + $request = new ServerRequest( + Method::GET, + 'http://from/it_can_detect_when_response_type_is_invalid?ticket=ST-TICKET-VALID' + ); + + $this + ->shouldThrow(Exception::class) + ->during('handle', [$request]); + } + + public function it_can_get_credentials_with_pgtUrl(CacheItemPoolInterface $cache, CacheItemInterface $cacheItem) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $cacheItem + ->set('pgtId') + ->willReturn($cacheItem); + + $cacheItem + ->expiresAfter(300) + ->willReturn($cacheItem); + + $cacheItem + ->get() + ->willReturn('pgtIou'); + + $cache + ->save($cacheItem) + ->willReturn(true); + + $cache + ->hasItem('pgtIou') + ->willReturn(true); + + $cache + ->getItem('pgtIou') + ->willReturn($cacheItem); + + $this->beConstructedWith( + [], + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + Cas::getTestPropertiesWithPgtUrl(), + $psr17 + ); + + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_get_credentials_with_pgtUrl') + ); + + $this + ->handle($request) + ->shouldImplement(ResponseInterface::class); + + $request = new ServerRequest( + Method::GET, + new Uri('http://from/it_can_get_credentials_with_pgtUrl/missing_pgt') + ); + + $this + ->shouldThrow(CasExceptionInterface::class) + ->during('handle', [$request]); + } + + public function it_can_get_credentials_without_pgtUrl() + { + $request = new ServerRequest( + Method::GET, + 'http://from/it_can_get_credentials_without_pgtUrl' + ); + + $this + ->handle($request) + ->shouldImplement(ResponseInterface::class); + } + + public function it_is_initializable() + { + $this->shouldHaveType(ServiceValidate::class); + } + + public function let(CacheItemPoolInterface $cache) + { + $psr17Factory = new Psr17Factory(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $this->beConstructedWith( + [], + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + Cas::getTestProperties(), + $psr17 + ); + } +} diff --git a/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php b/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php deleted file mode 100644 index 7d1a34e..0000000 --- a/spec/EcPhp/CasLib/Introspection/ServiceValidateSpec.php +++ /dev/null @@ -1,163 +0,0 @@ -withHeader('Content-Type', 'application/xml') - ->withBody($psr17Factory->createStream($body)); - - $credentials = [ - 'user' => 'user', - 'proxyGrantingTicket' => 'proxyGrantingTicket', - 'proxies' => [ - 'proxy' => [ - 'http://proxy1', - 'http://proxy2', - ], - ], - 'extendedAttributes' => [ - 'extendedAttribute' => [ - 'attributeValue' => [ - 0 => 'rex', - 1 => 'snoopy', - ], - '@attributes' => [ - 'name' => 'http://stork.eu/motherInLawDogName', - ], - ], - ], - ]; - - $parsed = [ - 'serviceResponse' => [ - 'authenticationSuccess' => $credentials, - ], - ]; - - $this - ->beConstructedWith($parsed, 'XML', $response); - - $this - ->getCredentials() - ->shouldReturn( - $credentials - ); - - $this - ->getFormat() - ->shouldReturn('XML'); - - $this - ->getProxies() - ->shouldReturn([ - 'proxy' => [ - 'http://proxy1', - 'http://proxy2', - ], - ]); - - $this - ->getResponse() - ->shouldReturn($response); - - $this - ->getParsedResponse() - ->shouldReturn($parsed); - } - - public function it_can_detect_a_service_validate_response() - { - $psr17Factory = new Psr17Factory(); - - $body = <<< 'EOF' - - - user - proxyGrantingTicket - - - EOF; - - $response = (new Response(200)) - ->withHeader('Content-Type', 'application/xml') - ->withBody($psr17Factory->createStream($body)); - - $credentials = [ - 'user' => 'user', - 'proxyGrantingTicket' => 'proxyGrantingTicket', - ]; - - $parsed = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => 'user', - 'proxyGrantingTicket' => 'proxyGrantingTicket', - ], - ], - ]; - - $this - ->beConstructedWith($parsed, 'XML', $response); - - $this - ->getCredentials() - ->shouldReturn($credentials); - - $this - ->getFormat() - ->shouldReturn('XML'); - - $this - ->getProxies() - ->shouldReturn([]); - - $this - ->getResponse() - ->shouldReturn($response); - } - - public function it_can_use_the_withParsedResponse_wither_method() - { - $psr17Factory = new Psr17Factory(); - $body = 'body'; - - $response = (new Response(200)) - ->withHeader('Content-Type', 'application/xml') - ->withBody($psr17Factory->createStream($body)); - - $this - ->beConstructedWith([], 'XML', $response); - - $this - ->withParsedResponse(['foo']) - ->shouldNotReturn($this); - - $this - ->withParsedResponse(['foo']) - ->shouldReturnAnInstanceOf(IntrospectionInterface::class); - } -} diff --git a/spec/EcPhp/CasLib/Redirect/LoginSpec.php b/spec/EcPhp/CasLib/Redirect/LoginSpec.php deleted file mode 100644 index 49dced7..0000000 --- a/spec/EcPhp/CasLib/Redirect/LoginSpec.php +++ /dev/null @@ -1,138 +0,0 @@ - range(1, 5), - ]; - - $this->beConstructedWith($serverRequest, $parameters, Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle() - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $this - ->handle() - ->getHeaderLine('Location') - ->shouldReturn('http://local/cas/login?custom%5B0%5D=1&custom%5B1%5D=2&custom%5B2%5D=3&custom%5B3%5D=4&custom%5B4%5D=5&service=http%3A%2F%2Fapp'); - } - - public function it_can_deal_with_renew_and_gateway_parameters(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $parameters = [ - 'renew' => true, - 'gateway' => true, - 'service' => 'service', - ]; - - $this->beConstructedWith($serverRequest, $parameters, Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle() - ->shouldBeNull(); - - $logger - ->error('Unable to get the Login response, gateway and renew parameter cannot be set together.') - ->shouldHaveBeenCalledOnce(); - - $logger - ->debug( - 'Login parameters are invalid, not redirecting to login page.', - [ - 'parameters' => [ - 'renew' => true, - 'gateway' => true, - 'service' => 'service', - ], - 'validatedParameters' => null, - ] - ) - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_deal_with_renew_parameter(ServerRequestInterface $serverRequest, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://app'); - - $parameters = [ - 'renew' => 'coin', - 'gateway' => false, - ]; - - $this->beConstructedWith($serverRequest, $parameters, Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle() - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $logger - ->debug( - 'Building service response redirection to {url}.', - [ - 'url' => 'http://local/cas/login?renew=true&service=http%3A%2F%2Fapp', - ] - ) - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_get_a_response(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $this->beConstructedWith($creator->fromGlobals(), [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle() - ->shouldBeAnInstanceOf(ResponseInterface::class); - } - - public function it_is_initializable(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $this->beConstructedWith($creator->fromGlobals(), [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this->shouldHaveType(Login::class); - } -} diff --git a/spec/EcPhp/CasLib/Redirect/LogoutSpec.php b/spec/EcPhp/CasLib/Redirect/LogoutSpec.php deleted file mode 100644 index 88ee632..0000000 --- a/spec/EcPhp/CasLib/Redirect/LogoutSpec.php +++ /dev/null @@ -1,54 +0,0 @@ -beConstructedWith($creator->fromGlobals(), [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this - ->handle() - ->shouldBeAnInstanceOf(ResponseInterface::class); - } - - public function it_is_initializable(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $this->beConstructedWith($creator->fromGlobals(), [], Cas::getTestProperties(), $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger); - - $this->shouldHaveType(Logout::class); - } -} diff --git a/spec/EcPhp/CasLib/Introspection/IntrospectorSpec.php b/spec/EcPhp/CasLib/Response/CasResponseBuilderSpec.php similarity index 63% rename from spec/EcPhp/CasLib/Introspection/IntrospectorSpec.php rename to spec/EcPhp/CasLib/Response/CasResponseBuilderSpec.php index d5da3d5..f6a1ef9 100644 --- a/spec/EcPhp/CasLib/Introspection/IntrospectorSpec.php +++ b/spec/EcPhp/CasLib/Response/CasResponseBuilderSpec.php @@ -9,19 +9,23 @@ declare(strict_types=1); -namespace spec\EcPhp\CasLib\Introspection; - -use EcPhp\CasLib\Introspection\Contract\AuthenticationFailure; -use EcPhp\CasLib\Introspection\Contract\Proxy; -use EcPhp\CasLib\Introspection\Contract\ProxyFailure; -use EcPhp\CasLib\Introspection\Contract\ServiceValidate; -use EcPhp\CasLib\Introspection\Introspector; -use InvalidArgumentException; +namespace spec\EcPhp\CasLib\Response; + +use EcPhp\CasLib\Contract\Response\CasResponseBuilderInterface; +use EcPhp\CasLib\Contract\Response\Type\AuthenticationFailure; +use EcPhp\CasLib\Contract\Response\Type\Proxy; +use EcPhp\CasLib\Contract\Response\Type\ProxyFailure; +use EcPhp\CasLib\Contract\Response\Type\ServiceValidate; +use EcPhp\CasLib\Response\Factory\AuthenticationFailureFactory; +use EcPhp\CasLib\Response\Factory\ProxyFactory; +use EcPhp\CasLib\Response\Factory\ProxyFailureFactory; +use EcPhp\CasLib\Response\Factory\ServiceValidateFactory; +use Exception; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Response; use PhpSpec\ObjectBehavior; -class IntrospectorSpec extends ObjectBehavior +class CasResponseBuilderSpec extends ObjectBehavior { public function it_can_detect_a_proxy_failure_response() { @@ -39,7 +43,8 @@ public function it_can_detect_a_proxy_failure_response() ->withHeader('Content-Type', 'application/xml') ->withBody($psr17Factory->createStream($body)); - $this->detect($response) + $this + ->fromResponse($response) ->shouldImplement(ProxyFailure::class); } @@ -62,7 +67,8 @@ public function it_can_detect_a_proxy_response() ->withHeader('Content-Type', 'application/xml') ->withBody($psr17Factory->createStream($body)); - $this->detect($response) + $this + ->fromResponse($response) ->shouldBeAnInstanceOf(Proxy::class); } @@ -83,7 +89,8 @@ public function it_can_detect_a_service_validate_response() ->withHeader('Content-Type', 'application/xml') ->withBody($psr17Factory->createStream($body)); - $this->detect($response) + $this + ->fromResponse($response) ->shouldBeAnInstanceOf(ServiceValidate::class); $body = <<< 'EOF' @@ -100,7 +107,8 @@ public function it_can_detect_a_service_validate_response() ->withHeader('Content-Type', 'application/json') ->withBody($psr17Factory->createStream($body)); - $this->detect($response) + $this + ->fromResponse($response) ->shouldBeAnInstanceOf(ServiceValidate::class); } @@ -124,7 +132,8 @@ public function it_can_detect_a_service_validate_response_with_proxy() ->withHeader('Content-Type', 'application/xml') ->withBody($psr17Factory->createStream($body)); - $this->detect($response) + $this + ->fromResponse($response) ->shouldBeAnInstanceOf(ServiceValidate::class); } @@ -141,8 +150,8 @@ public function it_can_detect_a_wrong_response() ->withBody($psr17Factory->createStream($body)); $this - ->shouldThrow(InvalidArgumentException::class) - ->during('detect', [$response]); + ->shouldThrow(Exception::class) + ->during('fromResponse', [$response]); $body = <<< 'EOF' FOO @@ -153,8 +162,8 @@ public function it_can_detect_a_wrong_response() ->withBody($psr17Factory->createStream($body)); $this - ->shouldThrow(InvalidArgumentException::class) - ->during('detect', [$response]); + ->shouldThrow(Exception::class) + ->during('fromResponse', [$response]); } public function it_can_detect_an_authentication_failure_response() @@ -173,7 +182,8 @@ public function it_can_detect_an_authentication_failure_response() ->withHeader('Content-Type', 'application/xml') ->withBody($psr17Factory->createStream($body)); - $this->detect($response) + $this + ->fromResponse($response) ->shouldBeAnInstanceOf(AuthenticationFailure::class); } @@ -196,8 +206,8 @@ public function it_can_detect_an_unknown_type_of_response() ->withBody($psr17Factory->createStream($body)); $this - ->shouldThrow(InvalidArgumentException::class) - ->during('detect', [$response]); + ->shouldThrow(Exception::class) + ->during('fromResponse', [$response]); } public function it_can_detect_an_unsupported_parse_format() @@ -213,12 +223,94 @@ public function it_can_detect_an_unsupported_parse_format() ->withBody($psr17Factory->createStream($body)); $this - ->shouldThrow(InvalidArgumentException::class) - ->during('parse', [$response, 'FOOBAR']); + ->shouldThrow(Exception::class) + ->during('fromResponse', [$response]); + } + + public function it_can_detect_an_unsupported_response_type() + { + $body = <<< 'EOF' + + + + + EOF; + + $headers = [ + 'Content-Type' => 'application/xml', + ]; + + $response = new Response( + 200, + $headers, + $body + ); + + $this + ->shouldThrow(Exception::class) + ->during('fromResponse', [$response]); + } + + public function it_can_detect_the_type_of_a_response() + { + $body = <<< 'EOF' + + + username + + + EOF; + + $headers = [ + 'Content-Type' => 'application/xml', + ]; + + $response = new Response( + 200, + $headers, + $body + ); + + $this + ->fromResponse($response) + ->shouldReturnAnInstanceOf(ServiceValidate::class); + + $body = <<< 'EOF' + + + username + + + EOF; + + $headers = [ + 'Content-Type' => 'application/foo', + ]; + + $response = new Response( + 200, + $headers, + $body + ); + + $this + ->shouldThrow(Exception::class) + ->during('fromResponse', [$response]); } public function it_is_initializable() { - $this->shouldHaveType(Introspector::class); + $this->shouldHaveType(CasResponseBuilderInterface::class); + } + + public function let() + { + $this + ->beConstructedWith( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ); } } diff --git a/spec/EcPhp/CasLib/Introspection/ProxyFailureSpec.php b/spec/EcPhp/CasLib/Response/Type/ProxyFailureSpec.php similarity index 58% rename from spec/EcPhp/CasLib/Introspection/ProxyFailureSpec.php rename to spec/EcPhp/CasLib/Response/Type/ProxyFailureSpec.php index cf4c81f..5f26564 100644 --- a/spec/EcPhp/CasLib/Introspection/ProxyFailureSpec.php +++ b/spec/EcPhp/CasLib/Response/Type/ProxyFailureSpec.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace spec\EcPhp\CasLib\Introspection; +namespace spec\EcPhp\CasLib\Response\Type; use Nyholm\Psr7\Response; use PhpSpec\ObjectBehavior; @@ -18,7 +18,19 @@ class ProxyFailureSpec extends ObjectBehavior { public function it_can_detect_a_proxy_failure_response() { - $response = (new Response(200)); + $body = [ + 'serviceResponse' => [ + 'proxyFailure' => "unrecognized pgt: 'PGT-123'", + ], + ]; + + $response = new Response( + 200, + [ + 'Content-Type' => 'application/json', + ], + json_encode($body) + ); $parsed = [ 'serviceResponse' => [ @@ -27,14 +39,14 @@ public function it_can_detect_a_proxy_failure_response() ]; $this - ->beConstructedWith($parsed, 'XML', $response); + ->beConstructedWith($response); $this ->getMessage() ->shouldReturn("unrecognized pgt: 'PGT-123'"); $this - ->getResponse() - ->shouldReturn($response); + ->toArray() + ->shouldReturn($parsed); } } diff --git a/spec/EcPhp/CasLib/Introspection/ProxySpec.php b/spec/EcPhp/CasLib/Response/Type/ProxySpec.php similarity index 50% rename from spec/EcPhp/CasLib/Introspection/ProxySpec.php rename to spec/EcPhp/CasLib/Response/Type/ProxySpec.php index 230def5..493e321 100644 --- a/spec/EcPhp/CasLib/Introspection/ProxySpec.php +++ b/spec/EcPhp/CasLib/Response/Type/ProxySpec.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace spec\EcPhp\CasLib\Introspection; +namespace spec\EcPhp\CasLib\Response\Type; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Response; @@ -23,11 +23,9 @@ public function it_can_detect_a_proxy_response() $body = <<< 'EOF' - + - PT-214-A3OoEPNr4Q9kNNuYzmfN8azU31aDUsuW8nk380k7wDExT5PFJpxR1TrNI3q3VGzyDdi0DpZ1LKb8IhPKZKQvavW-8hnfexYjmLCx7qWNsLib1W-DCzzoLVTosAUFzP3XDn5dNzoNtxIXV9KSztF9fYhwHvU0 + PGT-TICKET EOF; @@ -39,24 +37,20 @@ public function it_can_detect_a_proxy_response() $parsed = [ 'serviceResponse' => [ 'proxySuccess' => [ - 'proxyTicket' => 'PT-214-A3OoEPNr4Q9kNNuYzmfN8azU31aDUsuW8nk380k7wDExT5PFJpxR1TrNI3q3VGzyDdi0DpZ1LKb8IhPKZKQvavW-8hnfexYjmLCx7qWNsLib1W-DCzzoLVTosAUFzP3XDn5dNzoNtxIXV9KSztF9fYhwHvU0', + 'proxyTicket' => 'PGT-TICKET', ], ], ]; $this - ->beConstructedWith($parsed, 'XML', $response); + ->beConstructedWith($response); $this ->getProxyTicket() - ->shouldReturn('PT-214-A3OoEPNr4Q9kNNuYzmfN8azU31aDUsuW8nk380k7wDExT5PFJpxR1TrNI3q3VGzyDdi0DpZ1LKb8IhPKZKQvavW-8hnfexYjmLCx7qWNsLib1W-DCzzoLVTosAUFzP3XDn5dNzoNtxIXV9KSztF9fYhwHvU0'); + ->shouldReturn('PGT-TICKET'); $this - ->getFormat() - ->shouldReturn('XML'); - - $this - ->getResponse() - ->shouldReturn($response); + ->toArray() + ->shouldReturn($parsed); } } diff --git a/spec/EcPhp/CasLib/Response/Type/ServiceValidateSpec.php b/spec/EcPhp/CasLib/Response/Type/ServiceValidateSpec.php new file mode 100644 index 0000000..d3ebb4e --- /dev/null +++ b/spec/EcPhp/CasLib/Response/Type/ServiceValidateSpec.php @@ -0,0 +1,195 @@ + [ + 'authenticationSuccess' => [ + 'user' => 'user', + 'proxyGrantingTicket' => 'proxyGrantingTicket', + 'proxies' => [ + 'proxy' => [ + 'http://proxy1', + 'http://proxy2', + ], + ], + 'extendedAttributes' => [ + 'extendedAttribute' => [ + 'attributeValue' => [ + 0 => 'rex', + 1 => 'snoopy', + ], + '@attributes' => [ + 'name' => 'http://stork.eu/motherInLawDogName', + ], + ], + ], + ], + ], + ]; + + $response = (new Response(200)) + ->withHeader('Content-Type', 'application/json') + ->withBody($psr17Factory->createStream(json_encode($bodyArray))); + + $this + ->beConstructedWith($response); + + $this + ->getCredentials() + ->shouldReturn( + $bodyArray['serviceResponse']['authenticationSuccess'] + ); + + $this + ->getProxies() + ->shouldReturn([ + 'proxy' => [ + 'http://proxy1', + 'http://proxy2', + ], + ]); + + $this + ->toArray() + ->shouldReturn($bodyArray); + } + + public function it_can_detect_a_service_validate_response() + { + $psr17Factory = new Psr17Factory(); + + $body = <<< 'EOF' + + + user + proxyGrantingTicket + + + EOF; + + $response = (new Response(200)) + ->withHeader('Content-Type', 'application/xml') + ->withBody($psr17Factory->createStream($body)); + + $parsed = [ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'user', + 'proxyGrantingTicket' => 'proxyGrantingTicket', + ], + ], + ]; + + $this + ->beConstructedWith($response); + + $this + ->toArray() + ->shouldReturn($parsed); + + $this + ->getProxies() + ->shouldReturn([]); + + $this + ->toArray() + ->shouldReturn($parsed); + + $this + ->getBody() + ->__toString() + ->shouldReturn($body); + + $this + ->getHeader('Content-Type') + ->shouldReturn(['application/xml']); + + $this + ->withHeader('Content-Type', 'application/html') + ->getHeader('Content-Type') + ->shouldReturn(['application/html']); + + $this + ->getHeaderLine('Content-Type') + ->shouldReturn('application/xml'); + + $this + ->getHeaders() + ->shouldReturn([ + 'Content-Type' => [ + 'application/xml', + ], + ]); + + $this + ->getProtocolVersion() + ->shouldReturn('1.1'); + + $this + ->getReasonPhrase() + ->shouldReturn('OK'); + + $this + ->getStatusCode() + ->shouldReturn(200); + + $this + ->hasHeader('Content-Type') + ->shouldReturn(true); + + $this + ->hasHeader('foobar') + ->shouldReturn(false); + + $this + ->withAddedHeader('foobar', 'barfoo') + ->hasHeader('foobar') + ->shouldReturn(true); + + $this + ->withBody($psr17Factory->createStream('foobar')) + ->getBody() + ->__toString() + ->shouldReturn('foobar'); + + $this + ->withoutHeader('Content-Type') + ->hasHeader('Content-Type') + ->shouldReturn(false); + + $this + ->withProtocolVersion('2.0') + ->getProtocolVersion() + ->shouldReturn('2.0'); + + $this + ->withStatus(500, 'foo') + ->getStatusCode() + ->shouldReturn(500); + + $this + ->withStatus(500, 'foo') + ->getReasonPhrase() + ->shouldReturn('foo'); + } +} diff --git a/spec/EcPhp/CasLib/Service/ProxySpec.php b/spec/EcPhp/CasLib/Service/ProxySpec.php deleted file mode 100644 index 99790aa..0000000 --- a/spec/EcPhp/CasLib/Service/ProxySpec.php +++ /dev/null @@ -1,68 +0,0 @@ - - - username - pgtIou - - http://app/proxyCallback.php - - - - EOF; - - $response = new Response(200, ['Content-Type' => 'application/xml'], $body); - - $this - ->getCredentials($response) - ->shouldBeNull(); - } - - public function it_can_detect_when_no_credentials() - { - $response = new Response(500); - - $this - ->getCredentials($response) - ->shouldBeNull(); - } - - public function it_is_initializable() - { - $this->shouldHaveType(Proxy::class); - } - - public function let(ServerRequestInterface $serverRequest, ClientInterface $client, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - } -} diff --git a/spec/EcPhp/CasLib/Service/ProxyValidateSpec.php b/spec/EcPhp/CasLib/Service/ProxyValidateSpec.php deleted file mode 100644 index db78fdb..0000000 --- a/spec/EcPhp/CasLib/Service/ProxyValidateSpec.php +++ /dev/null @@ -1,122 +0,0 @@ -getCredentials($response) - ->shouldBeNull(); - } - - public function it_can_get_credentials_with_pgtUrl(ServerRequestInterface $serverRequest, ClientInterface $client, CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from'); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $cacheItem - ->set('pgtId') - ->willReturn($cacheItem); - - $cacheItem - ->expiresAfter(300) - ->willReturn($cacheItem); - - $cacheItem - ->get() - ->willReturn('pgtIou'); - - $cache - ->save($cacheItem) - ->willReturn(true); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $this->beConstructedWith($serverRequest, ['service' => 'service', 'ticket' => 'ST-ticket-pgt'], Cas::getTestPropertiesWithPgtUrl(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $response = $this->handle(); - - $response - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $this - ->getCredentials($response->getWrappedObject()) - ->shouldImplement(ResponseInterface::class); - - $logger - ->debug('Proxy validation service successful.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_get_credentials_without_pgtUrl(ServerRequestInterface $serverRequest, ClientInterface $client, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from'); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - - $this->beConstructedWith($serverRequest, ['service' => 'service', 'ticket' => 'ticket'], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $response = $this->handle(); - - $response - ->shouldBeAnInstanceOf(ResponseInterface::class); - - $this - ->getCredentials($response->getWrappedObject()) - ->shouldImplement(ResponseInterface::class); - - $logger - ->debug('Service validation service successful.') - ->shouldHaveBeenCalledOnce(); - } - - public function it_is_initializable(ServerRequestInterface $serverRequest, ClientInterface $client, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $this->shouldHaveType(ProxyValidate::class); - } - - public function let(ServerRequestInterface $serverRequest, ClientInterface $client, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - } -} diff --git a/spec/EcPhp/CasLib/Utils/ResponseSpec.php b/spec/EcPhp/CasLib/Utils/ResponseSpec.php new file mode 100644 index 0000000..37e3330 --- /dev/null +++ b/spec/EcPhp/CasLib/Utils/ResponseSpec.php @@ -0,0 +1,113 @@ + 'application/json', + ], + $body + ); + + $this + ->toArray($response) + ->shouldReturn([ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + ], + ], + ]); + } + + public function it_convert_a_xml_response() + { + $body = <<< 'EOF' + + + username + + + EOF; + + $response = new Response( + 200, + [ + 'Content-Type' => 'application/xml', + ], + $body + ); + + $this + ->toArray($response) + ->shouldReturn([ + 'serviceResponse' => [ + 'authenticationSuccess' => [ + 'user' => 'username', + ], + ], + ]); + } + + public function it_throws_when_body_is_empty() + { + $response = new Response( + 200, + [], + '' + ); + + $this + ->shouldThrow(CasExceptionInterface::class) + ->during('toArray', [$response]); + } + + public function it_throws_when_content_type_header_is_missing() + { + $body = <<< 'EOF' + + + username + + + EOF; + + $response = new Response( + 200, + [], + $body + ); + + $this + ->shouldThrow(CasExceptionInterface::class) + ->during('toArray', [$response]); + } +} diff --git a/spec/EcPhp/CasLib/Utils/UriSpec.php b/spec/EcPhp/CasLib/Utils/UriSpec.php index e38927e..c734063 100644 --- a/spec/EcPhp/CasLib/Utils/UriSpec.php +++ b/spec/EcPhp/CasLib/Utils/UriSpec.php @@ -132,7 +132,7 @@ public function it_can_get_a_single_param_from_uri() $this ->getParam($uri, 'param3') - ->shouldBeNull(); + ->shouldReturn(''); $this ->getParam($uri, 'param3', 'foo') diff --git a/spec/tests/EcPhp/CasLib/CasSpec.php b/spec/tests/EcPhp/CasLib/CasSpec.php index 108c0c0..313b41d 100644 --- a/spec/tests/EcPhp/CasLib/CasSpec.php +++ b/spec/tests/EcPhp/CasLib/CasSpec.php @@ -12,13 +12,17 @@ namespace spec\tests\EcPhp\CasLib; use EcPhp\CasLib\Cas; -use EcPhp\CasLib\Introspection\Introspector; +use EcPhp\CasLib\Contract\Response\Type\ServiceValidate; +use EcPhp\CasLib\Response\CasResponseBuilder; +use EcPhp\CasLib\Response\Factory\AuthenticationFailureFactory; +use EcPhp\CasLib\Response\Factory\ProxyFactory; +use EcPhp\CasLib\Response\Factory\ProxyFailureFactory; +use EcPhp\CasLib\Response\Factory\ServiceValidateFactory; +use Ergebnis\Http\Method; +use loophp\psr17\Psr17; use Nyholm\Psr7\Factory\Psr17Factory; -use Nyholm\Psr7Server\ServerRequestCreator; +use Nyholm\Psr7\ServerRequest; use PhpSpec\ObjectBehavior; -use Psr\Cache\CacheItemPoolInterface; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; use spec\EcPhp\CasLib\Cas as CasSpecUtils; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\HttpClient\Psr18Client; @@ -27,73 +31,65 @@ class CasSpec extends ObjectBehavior { public function it_can_test_the_proxy_mode_with_pgtUrl() { - $properties = CasSpecUtils::getTestPropertiesWithPgtUrl(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $serverRequest = $creator->fromGlobals(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + + $cache = new ArrayAdapter(); - $introspector = new Introspector(); + $cacheItem = $cache->getItem('pgtIou'); + $cacheItem->set('pgtId'); + + $cache + ->save($cacheItem); $this->beConstructedWith(new Cas( - $serverRequest, - $properties, - $client, - $psr17Factory, - $psr17Factory, - $psr17Factory, - $psr17Factory, - new ArrayAdapter(), - new NullLogger(), - $introspector + CasSpecUtils::getTestPropertiesWithPgtUrl(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, + $cache, + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) )); + $request = new ServerRequest( + Method::GET, + 'http://from/it_can_test_the_proxy_mode_with_pgtUrl' + ); + $this - ->requestTicketValidation(['service' => 'service', 'ticket' => 'ticket'], null) - ->getBody() - ->__toString() - ->shouldReturn('{"serviceResponse":{"authenticationSuccess":{"user":"username","proxies":{"proxy":"http:\/\/app\/proxyCallback.php"}}}}'); + ->requestTicketValidation($request, ['ticket' => 'ST-TICKET-VALID']) + ->shouldBeAnInstanceOf(ServiceValidate::class); } - public function it_can_test_the_proxy_mode_without_pgtUrl(CacheItemPoolInterface $cache, LoggerInterface $logger) + public function it_can_test_the_proxy_mode_without_pgtUrl() { - $properties = CasSpecUtils::getTestProperties(); - $client = new Psr18Client(CasSpecUtils::getHttpClientMock()); - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, // ServerRequestFactory - $psr17Factory, // UriFactory - $psr17Factory, // UploadedFileFactory - $psr17Factory // StreamFactory - ); - $serverRequest = $creator->fromGlobals(); - - $introspector = new Introspector(); + $psr17 = new Psr17($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); $this->beConstructedWith(new Cas( - $serverRequest, - $properties, - $client, - $psr17Factory, - $psr17Factory, - $psr17Factory, - $psr17Factory, + CasSpecUtils::getTestProperties(), + new Psr18Client(CasSpecUtils::getHttpClientMock()), + $psr17, new ArrayAdapter(), - new NullLogger(), - $introspector + new CasResponseBuilder( + new AuthenticationFailureFactory(), + new ProxyFactory(), + new ProxyFailureFactory(), + new ServiceValidateFactory() + ) )); + $request = new ServerRequest( + Method::GET, + 'http://from/it_can_test_the_proxy_mode_without_pgtUrl' + ); + $this - ->requestTicketValidation(['service' => 'service', 'ticket' => 'ticket'], null) - ->getBody() - ->__toString() - ->shouldReturn('{"serviceResponse":{"authenticationSuccess":{"user":"username"}}}'); + ->requestTicketValidation($request, ['ticket' => 'ST-TICKET-VALID']) + ->shouldBeAnInstanceOf(ServiceValidate::class); } } diff --git a/spec/tests/EcPhp/CasLib/Service/ProxyValidateSpec.php b/spec/tests/EcPhp/CasLib/Service/ProxyValidateSpec.php deleted file mode 100644 index aca939d..0000000 --- a/spec/tests/EcPhp/CasLib/Service/ProxyValidateSpec.php +++ /dev/null @@ -1,200 +0,0 @@ -set('pgtId') - ->willReturn($cacheItem); - - $cacheItem - ->expiresAfter(300) - ->willReturn($cacheItem); - - $cacheItem - ->get() - ->willReturn('pgtId'); - - $cache - ->save($cacheItem) - ->willReturn(true); - - $cache - ->hasItem('pgtIou') - ->willReturn(true); - - $cache - ->getItem('pgtIou') - ->willReturn($cacheItem); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $this - ->getClient() - ->shouldBeAnInstanceOf(ClientInterface::class); - - $this - ->getLogger() - ->shouldBeAnInstanceOf(LoggerInterface::class); - - $this - ->getCache() - ->shouldBeAnInstanceOf(CacheItemPoolInterface::class); - - $this - ->getUriFactory() - ->shouldBeAnInstanceOf(UriFactoryInterface::class); - - $this - ->getServerRequest() - ->shouldBeAnInstanceOf(ServerRequestInterface::class); - - $this - ->getStreamFactory() - ->shouldBeAnInstanceOf(StreamFactoryInterface::class); - - $this - ->getRequestFactory() - ->shouldBeAnInstanceOf(RequestFactoryInterface::class); - - $this - ->getResponseFactory() - ->shouldBeAnInstanceOf(ResponseFactoryInterface::class); - - $this - ->getRequest() - ->shouldBeAnInstanceOf(RequestInterface::class); - - $this - ->getIntrospector() - ->shouldBeAnInstanceOf(IntrospectorInterface::class); - - $response = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'proxyGrantingTicket' => 'pgtIou', - ], - ], - ]; - - $this - ->updateParsedResponseWithPgt($response) - ->shouldReturn( - [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'proxyGrantingTicket' => 'pgtId', - ], - ], - ] - ); - } - - public function it_can_detect_when_no_credentials() - { - $response = new Response(500); - - $this - ->getCredentials($response) - ->shouldBeNull(); - } - - public function it_can_log_debugging_information_when_trying_to_get_unexisting_pgtIou(CacheItemPoolInterface $cache, CacheItemInterface $cacheItem, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from'); - $client = new Psr18Client(Cas::getHttpClientMock()); - - $cache - ->hasItem('pgtIou') - ->willReturn(false); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $response = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'proxyGrantingTicket' => 'pgtIou', - ], - ], - ]; - - $this - ->updateParsedResponseWithPgt($response) - ->shouldReturn(null); - - $logger - ->error('CAS validation failed: pgtIou not found in the cache.', ['pgtIou' => 'pgtIou']) - ->shouldHaveBeenCalledOnce(); - } - - public function it_can_parse_a_response(CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $serverRequest = new ServerRequest('GET', 'http://from'); - $client = new Psr18Client(Cas::getHttpClientMock()); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - - $response = new Response(200, [], 'foo'); - - $this - ->parse($response) - ->shouldBeArray(); - - $logger - ->error('Unable to parse the response with the specified format {format}.', ['format' => 'XML', 'response' => 'foo']) - ->shouldHaveBeenCalledOnce(); - } - - public function it_is_initializable() - { - $this->shouldHaveType(ProxyValidate::class); - } - - public function let(ServerRequestInterface $serverRequest, ClientInterface $client, CacheItemPoolInterface $cache, LoggerInterface $logger) - { - $psr17Factory = new Psr17Factory(); - - $this->beConstructedWith($serverRequest, [], Cas::getTestProperties(), $client, $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory, $cache, $logger, new Introspector()); - } -} diff --git a/src/Cas.php b/src/Cas.php index 97fe0b0..baf38f9 100644 --- a/src/Cas.php +++ b/src/Cas.php @@ -11,25 +11,23 @@ namespace EcPhp\CasLib; -use EcPhp\CasLib\Configuration\PropertiesInterface; +use EcPhp\CasLib\Contract\CasInterface; +use EcPhp\CasLib\Contract\Configuration\PropertiesInterface; +use EcPhp\CasLib\Contract\Response\CasResponseBuilderInterface; +use EcPhp\CasLib\Exception\CasException; +use EcPhp\CasLib\Handler\Login; +use EcPhp\CasLib\Handler\Logout; +use EcPhp\CasLib\Handler\Proxy; use EcPhp\CasLib\Handler\ProxyCallback; -use EcPhp\CasLib\Introspection\Contract\IntrospectionInterface; -use EcPhp\CasLib\Introspection\Contract\IntrospectorInterface; -use EcPhp\CasLib\Redirect\Login; -use EcPhp\CasLib\Redirect\Logout; -use EcPhp\CasLib\Service\Proxy; -use EcPhp\CasLib\Service\ProxyValidate; -use EcPhp\CasLib\Service\ServiceValidate; +use EcPhp\CasLib\Handler\ServiceValidate; use EcPhp\CasLib\Utils\Uri; +use loophp\psr17\Psr17Interface; use Psr\Cache\CacheItemPoolInterface; use Psr\Http\Client\ClientInterface; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\UriFactoryInterface; -use Psr\Log\LoggerInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Throwable; use function array_key_exists; @@ -37,318 +35,178 @@ final class Cas implements CasInterface { private CacheItemPoolInterface $cache; - private ClientInterface $client; - - private IntrospectorInterface $introspector; + private CasResponseBuilderInterface $casResponseBuilder; - private LoggerInterface $logger; + private ClientInterface $client; private PropertiesInterface $properties; - private RequestFactoryInterface $requestFactory; - - private ResponseFactoryInterface $responseFactory; - - private ServerRequestInterface $serverRequest; - - private StreamFactoryInterface $streamFactory; - - private UriFactoryInterface $uriFactory; + private Psr17Interface $psr17; public function __construct( - ServerRequestInterface $serverRequest, PropertiesInterface $properties, ClientInterface $client, - UriFactoryInterface $uriFactory, - ResponseFactoryInterface $responseFactory, - RequestFactoryInterface $requestFactory, - StreamFactoryInterface $streamFactory, + Psr17Interface $psr17, CacheItemPoolInterface $cache, - LoggerInterface $logger, - IntrospectorInterface $introspector + CasResponseBuilderInterface $casResponseBuilder ) { - $this->serverRequest = $serverRequest; - $this->properties = $properties; - $this->client = $client; - $this->uriFactory = $uriFactory; - $this->responseFactory = $responseFactory; - $this->requestFactory = $requestFactory; - $this->streamFactory = $streamFactory; $this->cache = $cache; - $this->logger = $logger; - $this->introspector = $introspector; + $this->client = $client; + $this->casResponseBuilder = $casResponseBuilder; + $this->properties = $properties; + $this->psr17 = $psr17; } - public function authenticate(array $parameters = []): ?array - { - if (null === $response = $this->requestTicketValidation($parameters)) { - $this - ->getLogger() - ->error('Unable to authenticate the request.'); - - return null; + public function authenticate( + ServerRequestInterface $request, + array $parameters = [] + ): array { + try { + $response = $this->requestTicketValidation($request, $parameters); + } catch (Throwable $exception) { + throw CasException::unableToAuthenticate($exception); } - return $this->getIntrospector()->detect($response)->getParsedResponse(); - } - - public function detect(ResponseInterface $response): IntrospectionInterface - { - return $this->getIntrospector()->detect($response); - } - - public function getProperties(): PropertiesInterface - { - return $this->properties; - } - - public function handleProxyCallback( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - $proxyCallback = new ProxyCallback( - $this->getServerRequest(), - $parameters, - $this->getProperties(), - $this->getUriFactory(), - $this->getResponseFactory(), - $this->getStreamFactory(), - $this->getCache(), - $this->getLogger() - ); - - return $response ?? $proxyCallback->handle(); - } - - public function login(array $parameters = []): ?ResponseInterface - { - $login = new Login( - $this->getServerRequest(), - $parameters, - $this->getProperties(), - $this->getUriFactory(), - $this->getResponseFactory(), - $this->getStreamFactory(), - $this->getCache(), - $this->getLogger() - ); - - return $login->handle(); - } - - public function logout(array $parameters = []): ?ResponseInterface - { - $logout = new Logout( - $this->getServerRequest(), - $parameters, - $this->getProperties(), - $this->getUriFactory(), - $this->getResponseFactory(), - $this->getStreamFactory(), - $this->getCache(), - $this->getLogger() - ); - - return $logout->handle(); - } - - public function requestProxyTicket(array $parameters = [], ?ResponseInterface $response = null): ?ResponseInterface - { - $proxyRequestService = new Proxy( - $this->getServerRequest(), - $parameters, - $this->getProperties(), - $this->getHttpClient(), - $this->getUriFactory(), - $this->getResponseFactory(), - $this->getRequestFactory(), - $this->getStreamFactory(), - $this->getCache(), - $this->getLogger(), - $this->getIntrospector() - ); - - if (null === $response) { - if (null === $response = $proxyRequestService->handle()) { - $this - ->getLogger() - ->error('Error during the proxy ticket request.'); - - return null; - } + try { + $casResponse = $this + ->casResponseBuilder + ->fromResponse($response); + } catch (Throwable $exception) { + throw CasException::unableToAuthenticate($exception); } - $credentials = $proxyRequestService->getCredentials($response); - - if (null === $credentials) { - $this - ->getLogger() - ->error('Unable to authenticate the user.'); + try { + $credentials = $casResponse->toArray(); + } catch (Throwable $exception) { + throw CasException::unableToAuthenticate($exception); } return $credentials; } - public function requestProxyValidate( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - $proxyValidateService = new ProxyValidate( - $this->getServerRequest(), - $parameters, - $this->getProperties(), - $this->getHttpClient(), - $this->getUriFactory(), - $this->getResponseFactory(), - $this->getRequestFactory(), - $this->getStreamFactory(), - $this->getCache(), - $this->getLogger(), - $this->getIntrospector() - ); - - if (null === $response) { - if (null === $response = $proxyValidateService->handle()) { - $this - ->getLogger() - ->error('Error during the proxy validate request.'); - - return null; - } - } - - $credentials = $proxyValidateService->getCredentials($response); - - if (null === $credentials) { - $this - ->getLogger() - ->error('Unable to authenticate the user.'); - } - - return $credentials; + public function handleProxyCallback( + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this + ->process( + $request, + new ProxyCallback( + $parameters, + $this->cache, + $this->casResponseBuilder, + $this->client, + $this->properties, + $this->psr17, + ) + ); + } + + public function login( + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this + ->process( + $request, + new Login( + $parameters, + $this->cache, + $this->casResponseBuilder, + $this->client, + $this->properties, + $this->psr17, + ) + ); + } + + public function logout( + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this + ->process( + $request, + new Logout( + $parameters, + $this->cache, + $this->casResponseBuilder, + $this->client, + $this->properties, + $this->psr17, + ) + ); + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + return $handler->handle($request); + } + + public function requestProxyTicket( + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this + ->process( + $request, + new Proxy( + $parameters, + $this->cache, + $this->casResponseBuilder, + $this->client, + $this->properties, + $this->psr17, + ) + ); } public function requestServiceValidate( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - $serviceValidateService = new ServiceValidate( - $this->getServerRequest(), - $parameters, - $this->getProperties(), - $this->getHttpClient(), - $this->getUriFactory(), - $this->getResponseFactory(), - $this->getRequestFactory(), - $this->getStreamFactory(), - $this->getCache(), - $this->getLogger(), - $this->getIntrospector() - ); - - if (null === $response) { - if (null === $response = $serviceValidateService->handle()) { - $this - ->getLogger() - ->error('Error during the service validate request.'); - - return null; - } - } - - $credentials = $serviceValidateService->getCredentials($response); - - if (null === $credentials) { - $this - ->getLogger() - ->error('Unable to authenticate the user.'); - } - - return $credentials; + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this + ->process( + $request, + new ServiceValidate( + $parameters, + $this->cache, + $this->casResponseBuilder, + $this->client, + $this->properties, + $this->psr17, + ) + ); } public function requestTicketValidation( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - if (false === $this->supportAuthentication($parameters)) { - return null; + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + if (false === $this->supportAuthentication($request, $parameters)) { + throw CasException::unsupportedRequest(); } /** @var string $ticket */ $ticket = Uri::getParam( - $this->getServerRequest()->getUri(), + $request->getUri(), 'ticket', '' ); - $parameters += ['ticket' => $ticket]; - - return true === $this->proxyMode() ? - $this->requestProxyValidate($parameters, $response) : - $this->requestServiceValidate($parameters, $response); - } - - public function supportAuthentication(array $parameters = []): bool - { - return array_key_exists('ticket', $parameters) || Uri::hasParams($this->getServerRequest()->getUri(), 'ticket'); - } - - public function withServerRequest(ServerRequestInterface $serverRequest): CasInterface - { - $clone = clone $this; - $clone->serverRequest = $serverRequest; - - return $clone; - } - - private function getCache(): CacheItemPoolInterface - { - return $this->cache; - } - - private function getHttpClient(): ClientInterface - { - return $this->client; - } - - private function getIntrospector(): IntrospectorInterface - { - return $this->introspector; - } - - private function getLogger(): LoggerInterface - { - return $this->logger; - } - - private function getRequestFactory(): RequestFactoryInterface - { - return $this->requestFactory; - } - - private function getResponseFactory(): ResponseFactoryInterface - { - return $this->responseFactory; - } - - private function getServerRequest(): ServerRequestInterface - { - return $this->serverRequest; - } - - private function getStreamFactory(): StreamFactoryInterface - { - return $this->streamFactory; - } - - private function getUriFactory(): UriFactoryInterface - { - return $this->uriFactory; + return $this + ->requestServiceValidate( + $request, + $parameters + ['ticket' => $ticket] + ); } - private function proxyMode(): bool - { - return isset($this->getProperties()['protocol']['serviceValidate']['default_parameters']['pgtUrl']); + public function supportAuthentication( + ServerRequestInterface $request, + array $parameters = [] + ): bool { + return array_key_exists('ticket', $parameters) || Uri::hasParams($request->getUri(), 'ticket'); } } diff --git a/src/CasInterface.php b/src/CasInterface.php deleted file mode 100644 index df13e51..0000000 --- a/src/CasInterface.php +++ /dev/null @@ -1,165 +0,0 @@ -getMessage()), + 0, + $previous + ); + } + + public static function missingResponseContentTypeHeader(): self + { + return new self( + 'Missing "Content-Type" header, unable to detect response format.' + ); + } + + public static function unableToAuthenticate(?Throwable $previous = null) + { + return new self( + sprintf('Authentication failure: %s', $previous->getMessage()), + 0, + $previous + ); + } + + public static function unableToConvertResponseFromJson(Throwable $previous): self + { + return new self( + 'Unable to convert JSON Response to array.', + 0, + $previous + ); + } + + public static function unableToConvertResponseFromXml(Throwable $previous): self + { + return new self( + 'Unable to convert XML Response to array.', + 0, + $previous + ); + } + + public static function unableToLoadXml(Throwable $previous): self + { + return new self( + 'Unable to load the body of the XML Response.', + 0, + $previous + ); + } + + public static function unsupportedRequest(): self + { + return new self('The request does not support CAS authentication.'); + } + + public static function unsupportedResponseFormat(string $format): self + { + return new self( + sprintf('Unsupported response format: %s', $format) + ); + } +} diff --git a/src/Redirect/RedirectInterface.php b/src/Exception/CasExceptionInterface.php similarity index 62% rename from src/Redirect/RedirectInterface.php rename to src/Exception/CasExceptionInterface.php index 942c6b0..a8afeab 100644 --- a/src/Redirect/RedirectInterface.php +++ b/src/Exception/CasExceptionInterface.php @@ -9,10 +9,10 @@ declare(strict_types=1); -namespace EcPhp\CasLib\Redirect; +namespace EcPhp\CasLib\Exception; -use EcPhp\CasLib\Handler\HandlerInterface; +use Throwable; -interface RedirectInterface extends HandlerInterface +interface CasExceptionInterface extends Throwable { } diff --git a/src/Exception/CasHandlerException.php b/src/Exception/CasHandlerException.php new file mode 100644 index 0000000..eb359fa --- /dev/null +++ b/src/Exception/CasHandlerException.php @@ -0,0 +1,129 @@ +getBody()) + ); + } + + public static function errorWhileDoingRequest(Throwable $previous): self + { + $exception = CasException::errorWhileDoingRequest($previous); + + return new self($exception->getMessage(), 0, $exception); + } + + public static function getItemFromCacheFailure(Throwable $exception): self + { + return new self( + sprintf('Unable to get item from cache: %s', $exception->getMessage()), + 0, + $exception + ); + } + + public static function invalidProxyResponseType(ResponseInterface $response): self + { + return new self( + sprintf( + 'CAS proxy failure: Invalid response type, %s given while expecting %s.', + get_class($response), + Proxy::class + ) + ); + } + + public static function loginInvalidParameters(): self + { + return new self( + 'Login parameters are invalid.' + ); + } + + public static function loginRenewAndGatewayParametersAreSet(): self + { + return new self( + 'Unable to get the Login response, gateway and renew parameter cannot be set together.' + ); + } + + public static function missingPGT(Throwable $exception): self + { + return new self( + $exception->getMessage(), + 0, + $exception + ); + } + + public static function pgtIdIsNull(): self + { + return new self( + 'CAS proxy callback failure: PGT ID is null' + ); + } + + public static function pgtIouIsNull(): self + { + return new self( + 'CAS proxy callback failure: PGT IOU is null' + ); + } + + public static function serviceValidateInvalidPGTIdValue(): self + { + return new self( + 'CAS service validation failed: Invalid PGT ID value.' + ); + } + + public static function serviceValidatePGTNotFound(): self + { + return new self( + 'CAS service validation failed: ProxyGrantingTicket not found.' + ); + } + + public static function serviceValidateUnableToGetPGTFromCache(Throwable $exception): self + { + return new self( + sprintf('CAS service validation failed: Unable to get PGT (%s).', $exception->getMessage()), + 0, + $exception + ); + } + + public static function serviceValidateValidationFailed(ResponseInterface $response): self + { + return new self( + sprintf( + 'CAS service validation failure: Invalid response type, %s given while expecting %s.', + get_class($response), + ServiceValidate::class + ) + ); + } +} diff --git a/src/Exception/CasResponseBuilderException.php b/src/Exception/CasResponseBuilderException.php new file mode 100644 index 0000000..2607ae1 --- /dev/null +++ b/src/Exception/CasResponseBuilderException.php @@ -0,0 +1,38 @@ +serverRequest = $serverRequest; + $this->cache = $cache; + $this->casResponseBuilder = $casResponseBuilder; + $this->client = $client; $this->parameters = $parameters; $this->properties = $properties; - $this->uriFactory = $uriFactory; - $this->responseFactory = $responseFactory; - $this->streamFactory = $streamFactory; - $this->cache = $cache; - $this->logger = $logger; + $this->psr17 = $psr17; } - /** - * @param mixed[]|string[]|UriInterface[] $query - */ - protected function buildUri(UriInterface $from, string $name, array $query = []): UriInterface - { + protected function buildUri( + UriInterface $from, + string $type, + array $queryParams = [] + ): UriInterface { $properties = $this->getProperties(); - // Remove parameters that are not allowed. - $query = array_intersect_key( - $query, - (array) array_combine( - $properties['protocol'][$name]['allowed_parameters'] ?? [], - $properties['protocol'][$name]['allowed_parameters'] ?? [] - ) - ) + Uri::getParams($from); - + $queryParams += Uri::getParams($from); $baseUrl = parse_url($properties['base_url']); if (false === $baseUrl) { - $baseUrl = ['path' => '']; - $properties['base_url'] = ''; + throw new CasHandlerException( + sprintf('Unable to parse URL: %s', $properties['base_url']) + ); } - $baseUrl += ['path' => '']; - - if (true === array_key_exists('service', $query)) { - $query['service'] = (string) $query['service']; + if (true === array_key_exists('service', $queryParams)) { + $queryParams['service'] = (string) $queryParams['service']; } - // Filter out empty $query parameters - $query = array_filter( - $query, - static function ($item): bool { - if ([] === $item) { - return false; - } - - return '' !== $item; - } - ); - - return $this->getUriFactory() + return $this + ->getPsr17() ->createUri($properties['base_url']) - ->withPath($baseUrl['path'] . $properties['protocol'][$name]['path']) - ->withQuery(http_build_query($query)) + ->withPath(sprintf('%s%s', $baseUrl['path'], $properties['protocol'][$type]['path'])) + ->withQuery(http_build_query($queryParams)) ->withFragment($from->getFragment()); } @@ -119,30 +90,22 @@ static function ($item): bool { */ protected function formatProtocolParameters(array $parameters): array { - $parameters = array_filter( - $parameters - ); - $parameters = array_map( - static function ($parameter) { - return true === $parameter ? 'true' : $parameter; - }, - $parameters + static fn ($parameter) => true === $parameter ? 'true' : $parameter, + array_filter($parameters) ); if (true === array_key_exists('service', $parameters)) { - $service = $this->getUriFactory()->createUri( - $parameters['service'] - ); - - $service = Uri::removeParams( - $service, + $parameters['service'] = (string) Uri::removeParams( + $this + ->getPsr17() + ->createUri($parameters['service']), 'ticket' ); - - $parameters['service'] = (string) $service; } + ksort($parameters); + return $parameters; } @@ -151,51 +114,31 @@ protected function getCache(): CacheItemPoolInterface return $this->cache; } - protected function getLogger(): LoggerInterface + protected function getCasResponseBuilder(): CasResponseBuilderInterface { - return $this->logger; + return $this->casResponseBuilder; } - /** - * @return array[] - */ - protected function getParameters(): array + protected function getClient(): ClientInterface { - return $this->parameters + ($this->getProtocolProperties()['default_parameters'] ?? []); - } - - protected function getProperties(): PropertiesInterface - { - return $this->properties; + return $this->client; } /** - * Get the scoped properties of the protocol endpoint. - * * @return array[] */ - protected function getProtocolProperties(): array - { - return []; - } - - protected function getResponseFactory(): ResponseFactoryInterface - { - return $this->responseFactory; - } - - protected function getServerRequest(): ServerRequestInterface + protected function getParameters(): array { - return $this->serverRequest; + return $this->parameters; } - protected function getStreamFactory(): StreamFactoryInterface + protected function getProperties(): PropertiesInterface { - return $this->streamFactory; + return $this->properties; } - protected function getUriFactory(): UriFactoryInterface + protected function getPsr17(): Psr17Interface { - return $this->uriFactory; + return $this->psr17; } } diff --git a/src/Handler/Login.php b/src/Handler/Login.php new file mode 100644 index 0000000..c4ddf2c --- /dev/null +++ b/src/Handler/Login.php @@ -0,0 +1,79 @@ +getParameters(); + $parameters += Uri::getParams($request->getUri()); + $parameters += $this->getProperties()['protocol'][HandlerInterface::TYPE_LOGIN]['default_parameters'] ?? []; + $parameters += [ + 'service' => (string) $request->getUri(), + ]; + $parameters = $this->formatProtocolParameters($parameters); + + $this->validate($request, $parameters); + + return $this + ->getPsr17() + ->createResponse(302) + ->withHeader( + 'Location', + (string) $this + ->buildUri( + $request->getUri(), + HandlerInterface::TYPE_LOGIN, + $parameters + ) + ); + } + + /** + * @param string[] $parameters + * + * @throws CasExceptionInterface + */ + private function validate( + RequestInterface $request, + array $parameters + ): void { + $uri = $request->getUri(); + + $renew = $parameters['renew'] ?? false; + $gateway = $parameters['gateway'] ?? false; + + if ('true' === $renew && 'true' === $gateway) { + throw CasHandlerException::loginRenewAndGatewayParametersAreSet(); + } + + foreach (['gateway', 'renew'] as $queryParameter) { + if (false === array_key_exists($queryParameter, $parameters)) { + continue; + } + + if ('true' !== Uri::getParam($uri, $queryParameter, 'true')) { + throw CasHandlerException::loginInvalidParameters(); + } + } + } +} diff --git a/src/Handler/Logout.php b/src/Handler/Logout.php new file mode 100644 index 0000000..90344fd --- /dev/null +++ b/src/Handler/Logout.php @@ -0,0 +1,40 @@ +getParameters(); + $parameters += Uri::getParams($request->getUri()); + $parameters += $this->getProperties()['protocol'][HandlerInterface::TYPE_LOGOUT]['default_parameters'] ?? []; + + return $this + ->getPsr17() + ->createResponse(302) + ->withHeader( + 'Location', + (string) $this + ->buildUri( + $request->getUri(), + HandlerInterface::TYPE_LOGOUT, + $this->formatProtocolParameters($parameters) + ) + ); + } +} diff --git a/src/Handler/Proxy.php b/src/Handler/Proxy.php new file mode 100644 index 0000000..ab8eefc --- /dev/null +++ b/src/Handler/Proxy.php @@ -0,0 +1,61 @@ +getProperties(); + + $parameters = $this->getParameters(); + $parameters += Uri::getParams($request->getUri()); + $parameters += $properties['protocol'][HandlerInterface::TYPE_PROXY]['default_parameters'] ?? []; + $parameters += ['service' => (string) $request->getUri()]; + + $request = $this + ->getPsr17() + ->createRequest( + Method::GET, + $this + ->buildUri( + $request->getUri(), + HandlerInterface::TYPE_PROXY, + $this->formatProtocolParameters($parameters) + ) + ); + + try { + $response = $this + ->getClient() + ->sendRequest($request); + } catch (Throwable $exception) { + throw CasHandlerException::errorWhileDoingRequest($exception); + } + + $response = $this->getCasResponseBuilder()->fromResponse($response); + + if (false === ($response instanceof \EcPhp\CasLib\Contract\Response\Type\Proxy)) { + throw CasHandlerException::invalidProxyResponseType($response); + } + + return $response; + } +} diff --git a/src/Handler/ProxyCallback.php b/src/Handler/ProxyCallback.php index bede7b3..055e5aa 100644 --- a/src/Handler/ProxyCallback.php +++ b/src/Handler/ProxyCallback.php @@ -11,63 +11,48 @@ namespace EcPhp\CasLib\Handler; +use EcPhp\CasLib\Contract\Handler\HandlerInterface; +use EcPhp\CasLib\Exception\CasHandlerException; use EcPhp\CasLib\Utils\Uri; use Exception; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Throwable; final class ProxyCallback extends Handler implements HandlerInterface { - public function handle(): ?ResponseInterface + public function handle(ServerRequestInterface $request): ResponseInterface { - $serverRequest = $this->getServerRequest(); $response = $this - ->getResponseFactory() - ->createResponse(200); + ->getPsr17() + ->createResponse(); - // POST parameters prevails over GET parameters. - $parameters = $this->getParameters() + - (array) $serverRequest->getParsedBody() + - Uri::getParams($serverRequest->getUri()) + - ['pgtId' => null, 'pgtIou' => null]; + $parameters = $this->getParameters(); + $parameters += Uri::getParams($request->getUri()); + $parameters += (array) $request->getBody(); + $parameters += ['pgtId' => null, 'pgtIou' => null]; if (null === $parameters['pgtId'] && null === $parameters['pgtIou']) { - $this - ->getLogger() - ->debug( - 'CAS server just checked the proxy callback endpoint.' - ); - + // We cannot return an exception here because prior sending the + // PGT ID and PGTIOU, a request is made by the CAS server in order + // to check the existence of the proxy callback endpoint. return $response; } if (null === $parameters['pgtIou']) { - $this - ->getLogger() - ->debug( - 'Missing proxy callback parameter (pgtIou).' - ); - - return $response->withStatus(500); + throw CasHandlerException::pgtIouIsNull(); } if (null === $parameters['pgtId']) { - $this - ->getLogger() - ->debug( - 'Missing proxy callback parameter (pgtId).' - ); - - return $response->withStatus(500); + throw CasHandlerException::pgtIdIsNull(); } try { - $cacheItem = $this->getCache()->getItem($parameters['pgtIou']); - } catch (Exception $exception) { - $this - ->getLogger() - ->error($exception->getMessage()); - - return $response->withStatus(500); + $cacheItem = $this + ->getCache() + ->getItem($parameters['pgtIou']); + } catch (Throwable $exception) { + throw CasHandlerException::getItemFromCacheFailure($exception); } $this @@ -78,12 +63,6 @@ public function handle(): ?ResponseInterface ->expiresAfter(300) ); - $this - ->getLogger() - ->debug( - 'Storing proxy callback parameters (pgtId and pgtIou).' - ); - return $response; } } diff --git a/src/Handler/ServiceValidate.php b/src/Handler/ServiceValidate.php new file mode 100644 index 0000000..67e18a9 --- /dev/null +++ b/src/Handler/ServiceValidate.php @@ -0,0 +1,116 @@ +getProperties(); + + $type = $properties['protocol'][HandlerInterface::TYPE_SERVICE_VALIDATE]['default_parameters']['pgtUrl'] ?? false + ? HandlerInterface::TYPE_PROXY_VALIDATE + : HandlerInterface::TYPE_SERVICE_VALIDATE; + + $parameters = $this->getParameters(); + $parameters += Uri::getParams($request->getUri()); + $parameters += $properties['protocol'][$type]['default_parameters'] ?? []; + $parameters += ['service' => (string) $request->getUri()]; + + $request = $this + ->getPsr17() + ->createRequest( + Method::GET, + $this + ->buildUri( + $request->getUri(), + $type, + $this->formatProtocolParameters($parameters) + ) + ); + + try { + $response = $this + ->getClient() + ->sendRequest($request); + } catch (Throwable $exception) { + throw CasHandlerException::errorWhileDoingRequest($exception); + } + + $response = $this->getCasResponseBuilder()->fromResponse($response); + + if ($response instanceof AuthenticationFailure) { + throw CasHandlerException::authenticationFailure($response); + } + + if (false === ($response instanceof TypeServiceValidate)) { + throw CasHandlerException::serviceValidateValidationFailed($response); + } + + if (HandlerInterface::TYPE_SERVICE_VALIDATE === $type) { + return $response; + } + + /** @var TypeServiceValidate $response */ + try { + $proxyGrantingTicket = $response->getProxyGrantingTicket(); + } catch (Throwable $exception) { + throw CasHandlerException::missingPGT($exception); + } + + $hasPgtIou = $this + ->getCache() + ->hasItem($proxyGrantingTicket); + + if (false === $hasPgtIou) { + throw CasHandlerException::serviceValidatePGTNotFound(); + } + + try { + $pgtId = $this + ->getCache() + ->getItem($proxyGrantingTicket); + } catch (Throwable $exception) { + throw CasHandlerException::serviceValidateUnableToGetPGTFromCache($exception); + } + + if (null === $pgtId->get()) { + throw CasHandlerException::serviceValidateInvalidPGTIdValue(); + } + + return $response + ->withBody( + $this + ->getPsr17() + ->createStream( + str_replace( + $proxyGrantingTicket, + $pgtId->get(), + (string) $response->getBody() + ) + ) + ); + } +} diff --git a/src/Introspection/Contract/IntrospectionInterface.php b/src/Introspection/Contract/IntrospectionInterface.php deleted file mode 100644 index 0b68b02..0000000 --- a/src/Introspection/Contract/IntrospectionInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - public function parse(ResponseInterface $response, string $format = 'XML'): array; -} diff --git a/src/Introspection/Introspection.php b/src/Introspection/Introspection.php deleted file mode 100644 index a8d86be..0000000 --- a/src/Introspection/Introspection.php +++ /dev/null @@ -1,54 +0,0 @@ -response = $response; - $this->parsedResponse = $parsedResponse; - $this->format = $format; - } - - public function getFormat(): string - { - return $this->format; - } - - public function getParsedResponse(): array - { - return $this->parsedResponse; - } - - public function getResponse(): ResponseInterface - { - return $this->response; - } - - public function withParsedResponse(array $parsedResponse): IntrospectionInterface - { - $clone = clone $this; - $clone->parsedResponse = $parsedResponse; - - return $clone; - } -} diff --git a/src/Introspection/Introspector.php b/src/Introspection/Introspector.php deleted file mode 100644 index 64138a5..0000000 --- a/src/Introspection/Introspector.php +++ /dev/null @@ -1,143 +0,0 @@ -getStatusCode()) { - throw new InvalidArgumentException('Unable to detect the response format.'); - } - - if (true === $response->hasHeader('Content-Type')) { - $header = substr($response->getHeaderLine('Content-Type'), 0, 16); - - switch ($header) { - case 'application/json': - $format = 'JSON'; - - break; - - case 'application/xml': - $format = 'XML'; - - break; - } - } - - if (null === $format) { - throw new InvalidArgumentException('Unable to detect the response format.'); - } - - try { - $data = $this->parse($response, $format); - } catch (InvalidArgumentException $exception) { - throw new InvalidArgumentException($exception->getMessage()); - } - - if (false === array_key_exists('serviceResponse', $data)) { - throw new InvalidArgumentException('Unable to find the response type.'); - } - - if (array_key_exists('authenticationFailure', $data['serviceResponse'])) { - return new AuthenticationFailure($data, $format, $response); - } - - if (array_key_exists('proxyFailure', $data['serviceResponse'])) { - return new ProxyFailure($data, $format, $response); - } - - if (array_key_exists('authenticationSuccess', $data['serviceResponse']) && array_key_exists('user', $data['serviceResponse']['authenticationSuccess'])) { - return new ServiceValidate($data, $format, $response); - } - - if (array_key_exists('proxySuccess', $data['serviceResponse']) && array_key_exists('proxyTicket', $data['serviceResponse']['proxySuccess'])) { - return new Proxy($data, $format, $response); - } - - throw new InvalidArgumentException('Unable to find the response type.'); - } - - /** - * @throws InvalidArgumentException - * - * @return mixed[] - */ - public function parse(ResponseInterface $response, string $format = 'XML'): array - { - $body = (string) $response->getBody(); - - if ('' === $body) { - throw new InvalidArgumentException('Empty response body'); - } - - if ('XML' === $format) { - try { - $dom = new DOMDocument(); - - $dom - ->loadXML( - $body, - LIBXML_NSCLEAN | LIBXML_NOCDATA | LIBXML_NOBLANKS | LIBXML_NONET - ); - - $this->removeDomNamespace($dom, 'cas'); - - $data = XML2Array::createArray($dom); - } catch (Exception $e) { - throw new InvalidArgumentException('Unable to parse the response using XML format.', 0, $e); - } - - return $data; - } - - if ('JSON' === $format) { - $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.'); - } - - return $json; - } - - 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/Introspection/ServiceValidate.php b/src/Introspection/ServiceValidate.php deleted file mode 100644 index c4dff54..0000000 --- a/src/Introspection/ServiceValidate.php +++ /dev/null @@ -1,31 +0,0 @@ -getParsedResponse()['serviceResponse']['authenticationSuccess']; - } - - public function getProxies(): array - { - $hasProxy = isset($this->getParsedResponse()['serviceResponse']['authenticationSuccess']['proxies']); - - return true === $hasProxy ? - $this->getParsedResponse()['serviceResponse']['authenticationSuccess']['proxies'] : - []; - } -} diff --git a/src/Redirect/Login.php b/src/Redirect/Login.php deleted file mode 100644 index 6f47d03..0000000 --- a/src/Redirect/Login.php +++ /dev/null @@ -1,114 +0,0 @@ -formatProtocolParameters($this->getParameters()); - $validatedParameters = $this->validate($parameters); - - if (null === $validatedParameters) { - $this - ->getLogger() - ->debug( - 'Login parameters are invalid, not redirecting to login page.', - [ - 'parameters' => $parameters, - 'validatedParameters' => $validatedParameters, - ] - ); - - return null; - } - - return $this->createRedirectResponse((string) $this->getUri($validatedParameters)); - } - - protected function formatProtocolParameters(array $parameters): array - { - $parameters = parent::formatProtocolParameters($parameters); - - foreach (['gateway', 'renew'] as $queryParameter) { - if (false === array_key_exists($queryParameter, $parameters)) { - continue; - } - - $parameters[$queryParameter] = 'true'; - } - - return $parameters; - } - - protected function getProtocolProperties(): array - { - $protocolProperties = $this->getProperties()['protocol']['login'] ?? []; - - $protocolProperties['default_parameters'] += [ - 'service' => (string) $this->getServerRequest()->getUri(), - ]; - - return $protocolProperties; - } - - /** - * @param string[] $parameters - */ - private function getUri(array $parameters = []): UriInterface - { - return $this->buildUri( - $this->getServerRequest()->getUri(), - 'login', - $parameters - ); - } - - /** - * @param string[] $parameters - * - * @return string[]|null - */ - private function validate(array $parameters): ?array - { - $uri = $this->getServerRequest()->getUri(); - - $renew = $parameters['renew'] ?? false; - $gateway = $parameters['gateway'] ?? false; - - if ('true' === $renew && 'true' === $gateway) { - $this - ->getLogger() - ->error('Unable to get the Login response, gateway and renew parameter cannot be set together.'); - - return null; - } - - foreach (['gateway', 'renew'] as $queryParameter) { - if (false === array_key_exists($queryParameter, $parameters)) { - continue; - } - - if ('true' !== Uri::getParam($uri, $queryParameter, 'true')) { - return null; - } - } - - return $parameters; - } -} diff --git a/src/Redirect/Logout.php b/src/Redirect/Logout.php deleted file mode 100644 index 2314bfe..0000000 --- a/src/Redirect/Logout.php +++ /dev/null @@ -1,36 +0,0 @@ -createRedirectResponse((string) $this->getUri()); - } - - protected function getProtocolProperties(): array - { - return $this->getProperties()['protocol']['logout'] ?? []; - } - - private function getUri(): UriInterface - { - $serverRequest = $this->getServerRequest()->getUri(); - $parameters = $this->formatProtocolParameters($this->getParameters()); - - return $this->buildUri($serverRequest, 'logout', $parameters); - } -} diff --git a/src/Redirect/Redirect.php b/src/Redirect/Redirect.php deleted file mode 100644 index f908170..0000000 --- a/src/Redirect/Redirect.php +++ /dev/null @@ -1,30 +0,0 @@ -getLogger() - ->debug('Building service response redirection to {url}.', ['url' => $url]); - - return $this - ->getResponseFactory() - ->createResponse(302) - ->withHeader('Location', $url); - } -} diff --git a/src/Response/CasResponseBuilder.php b/src/Response/CasResponseBuilder.php new file mode 100644 index 0000000..25ebe1d --- /dev/null +++ b/src/Response/CasResponseBuilder.php @@ -0,0 +1,80 @@ +authenticationFailureFactory = $authenticationFailureFactory; + $this->proxyFactory = $proxyFactory; + $this->proxyFailureFactory = $proxyFailureFactory; + $this->serviceValidateFactory = $serviceValidateFactory; + } + + public function fromResponse(ResponseInterface $response): CasResponseInterface + { + if (200 !== $code = $response->getStatusCode()) { + throw CasResponseBuilderException::invalidStatusCode($code); + } + + $data = (new ResponseUtils())->toArray($response); + + if (false === array_key_exists('serviceResponse', $data)) { + throw CasResponseBuilderException::invalidResponseType(); + } + + if (array_key_exists('authenticationFailure', $data['serviceResponse'])) { + return $this->authenticationFailureFactory->decorate($response); + } + + if (array_key_exists('proxyFailure', $data['serviceResponse'])) { + return $this->proxyFailureFactory->decorate($response); + } + + if (array_key_exists('authenticationSuccess', $data['serviceResponse'])) { + return $this->serviceValidateFactory->decorate($response); + } + + if (array_key_exists('proxySuccess', $data['serviceResponse'])) { + return $this->proxyFactory->decorate($response); + } + + throw CasResponseBuilderException::unknownResponseType(); + } +} diff --git a/src/Response/Factory/AuthenticationFailureFactory.php b/src/Response/Factory/AuthenticationFailureFactory.php new file mode 100644 index 0000000..c8a3e72 --- /dev/null +++ b/src/Response/Factory/AuthenticationFailureFactory.php @@ -0,0 +1,25 @@ +response = $response; + } + + public function getBody() + { + return $this->response->getBody(); + } + + public function getHeader($name) + { + return $this->response->getHeader($name); + } + + public function getHeaderLine($name) + { + return $this->response->getHeaderLine($name); + } + + public function getHeaders() + { + return $this->response->getHeaders(); + } + + public function getProtocolVersion() + { + return $this->response->getProtocolVersion(); + } + + public function getReasonPhrase() + { + return $this->response->getReasonPhrase(); + } + + public function getStatusCode() + { + return $this->response->getStatusCode(); + } + + public function hasHeader($name) + { + return $this->response->hasHeader($name); + } + + public function toArray(): array + { + return (new ResponseUtils())->toArray($this->response); + } + + public function withAddedHeader($name, $value) + { + $clone = clone $this; + $clone->response = $this->response->withAddedHeader($name, $value); + + return $clone; + } + + public function withBody(StreamInterface $body) + { + $clone = clone $this; + $clone->response = $this->response->withBody($body); + + return $clone; + } + + public function withHeader($name, $value) + { + $clone = clone $this; + $clone->response = $this->response->withHeader($name, $value); + + return $clone; + } + + public function withoutHeader($name) + { + $clone = clone $this; + $clone->response = $this->response->withoutHeader($name); + + return $clone; + } + + public function withProtocolVersion($version) + { + $clone = clone $this; + $clone->response = $this->response->withProtocolVersion($version); + + return $clone; + } + + public function withStatus($code, $reasonPhrase = '') + { + $clone = clone $this; + $clone->response = $this->response->withStatus($code, $reasonPhrase); + + return $clone; + } +} diff --git a/src/Introspection/Proxy.php b/src/Response/Type/Proxy.php similarity index 50% rename from src/Introspection/Proxy.php rename to src/Response/Type/Proxy.php index 446fa93..995aa5e 100644 --- a/src/Introspection/Proxy.php +++ b/src/Response/Type/Proxy.php @@ -9,14 +9,14 @@ declare(strict_types=1); -namespace EcPhp\CasLib\Introspection; +namespace EcPhp\CasLib\Response\Type; -use EcPhp\CasLib\Introspection\Contract\Proxy as ProxyInterface; +use EcPhp\CasLib\Contract\Response\Type\Proxy as ProxyInterface; -final class Proxy extends Introspection implements ProxyInterface +final class Proxy extends CasResponse implements ProxyInterface { public function getProxyTicket(): string { - return $this->getParsedResponse()['serviceResponse']['proxySuccess']['proxyTicket']; + return $this->toArray()['serviceResponse']['proxySuccess']['proxyTicket']; } } diff --git a/src/Introspection/ProxyFailure.php b/src/Response/Type/ProxyFailure.php similarity index 51% rename from src/Introspection/ProxyFailure.php rename to src/Response/Type/ProxyFailure.php index 3fac641..24577d3 100644 --- a/src/Introspection/ProxyFailure.php +++ b/src/Response/Type/ProxyFailure.php @@ -9,14 +9,14 @@ declare(strict_types=1); -namespace EcPhp\CasLib\Introspection; +namespace EcPhp\CasLib\Response\Type; -use EcPhp\CasLib\Introspection\Contract\ProxyFailure as ProxyFailureInterface; +use EcPhp\CasLib\Contract\Response\Type\ProxyFailure as ProxyFailureInterface; -final class ProxyFailure extends Introspection implements ProxyFailureInterface +final class ProxyFailure extends CasResponse implements ProxyFailureInterface { public function getMessage(): string { - return $this->getParsedResponse()['serviceResponse']['proxyFailure']; + return $this->toArray()['serviceResponse']['proxyFailure']; } } diff --git a/src/Response/Type/ServiceValidate.php b/src/Response/Type/ServiceValidate.php new file mode 100644 index 0000000..863ff32 --- /dev/null +++ b/src/Response/Type/ServiceValidate.php @@ -0,0 +1,51 @@ +toArray()['serviceResponse']['authenticationSuccess']; + } + + public function getProxies(): array + { + $hasProxy = isset($this->toArray()['serviceResponse']['authenticationSuccess']['proxies']); + + return true === $hasProxy ? + $this->toArray()['serviceResponse']['authenticationSuccess']['proxies'] : + []; + } + + public function getProxyGrantingTicket(): string + { + $credentials = $this->getCredentials(); + + $proxyGrantingTicket = array_key_exists( + 'proxyGrantingTicket', + $credentials + ); + + if (false === $proxyGrantingTicket) { + // TODO: Create a CasResponseException class. + throw new Exception('Service validate response: Missing proxy granting ticket key in CAS response body.'); + } + + return $credentials['proxyGrantingTicket']; + } +} diff --git a/src/Service/Proxy.php b/src/Service/Proxy.php deleted file mode 100644 index ceb7b1a..0000000 --- a/src/Service/Proxy.php +++ /dev/null @@ -1,52 +0,0 @@ -getIntrospector()->detect($response); - } catch (InvalidArgumentException $exception) { - $this - ->getLogger() - ->error($exception->getMessage()); - - return null; - } - - if (false === ($introspect instanceof \EcPhp\CasLib\Introspection\Contract\Proxy)) { - return null; - } - - return $response; - } - - protected function getProtocolProperties(): array - { - return $this->getProperties()['protocol']['proxy'] ?? []; - } - - protected function getUri(): UriInterface - { - return $this->buildUri( - $this->getServerRequest()->getUri(), - 'proxy', - $this->formatProtocolParameters($this->getParameters()) - ); - } -} diff --git a/src/Service/ProxyValidate.php b/src/Service/ProxyValidate.php deleted file mode 100644 index fb1478b..0000000 --- a/src/Service/ProxyValidate.php +++ /dev/null @@ -1,39 +0,0 @@ -getProperties()['protocol']['proxyValidate'] ?? []; - - $protocolProperties['default_parameters'] += [ - 'service' => (string) $this->getServerRequest()->getUri(), - 'ticket' => Uri::getParam($this->getServerRequest()->getUri(), 'ticket'), - ]; - - return $protocolProperties; - } - - protected function getUri(): UriInterface - { - return $this->buildUri( - $this->getServerRequest()->getUri(), - 'proxyValidate', - $this->formatProtocolParameters($this->getParameters()) - ); - } -} diff --git a/src/Service/Service.php b/src/Service/Service.php deleted file mode 100644 index 7a9adcb..0000000 --- a/src/Service/Service.php +++ /dev/null @@ -1,272 +0,0 @@ -client = $client; - $this->requestFactory = $requestFactory; - $this->introspector = $introspector; - } - - public function getCredentials(ResponseInterface $response): ?ResponseInterface - { - try { - $introspect = $this->getIntrospector()->detect($response); - } catch (InvalidArgumentException $exception) { - $this - ->getLogger() - ->error($exception->getMessage()); - - return null; - } - - if (false === ($introspect instanceof ServiceValidate)) { - $this - ->getLogger() - ->error( - 'Service validation failed.', - [ - 'response' => (string) $response->getBody(), - ] - ); - - return null; - } - - $parsedResponse = $introspect->getParsedResponse(); - $proxyGrantingTicket = array_key_exists( - 'proxyGrantingTicket', - $parsedResponse['serviceResponse']['authenticationSuccess'] - ); - - if (false === $proxyGrantingTicket) { - $this - ->getLogger() - ->debug('Service validation service successful.'); - - return $response->withHeader('Content-Type', 'application/json'); - } - - $parsedResponse = $this->updateParsedResponseWithPgt($parsedResponse); - - if (null === $parsedResponse) { - return null; - } - - $body = json_encode($parsedResponse); - - if (false === $body) { - return null; - } - - $this - ->getLogger() - ->debug('Proxy validation service successful.'); - - return $response - ->withBody($this->getStreamFactory()->createStream($body)) - ->withHeader('Content-Type', 'application/json'); - } - - public function handle(): ?ResponseInterface - { - try { - $response = $this->getClient()->sendRequest($this->getRequest()); - } catch (ClientExceptionInterface $exception) { - $this - ->getLogger() - ->error($exception->getMessage()); - - $response = null; - } - - return null === $response ? $response : $this->normalize($response); - } - - protected function getClient(): ClientInterface - { - return $this->client; - } - - protected function getIntrospector(): IntrospectorInterface - { - return $this->introspector; - } - - protected function getRequest(): RequestInterface - { - return $this->getRequestFactory()->createRequest('GET', $this->getUri()); - } - - protected function getRequestFactory(): RequestFactoryInterface - { - return $this->requestFactory; - } - - /** - * Get the URI. - */ - abstract protected function getUri(): UriInterface; - - /** - * Parse the response format. - * - * @return array[]|string[] - * The parsed response. - */ - protected function parse(ResponseInterface $response): array - { - $format = $this->getProtocolProperties()['default_parameters']['format'] ?? 'XML'; - - try { - $array = $this->getIntrospector()->parse($response, $format); - } catch (InvalidArgumentException $exception) { - $this - ->getLogger() - ->error( - 'Unable to parse the response with the specified format {format}.', - [ - 'format' => $format, - 'response' => (string) $response->getBody(), - ] - ); - - $array = []; - } - - return $array; - } - - /** - * @param array[] $response - * - * @return array[]|null - */ - protected function updateParsedResponseWithPgt(array $response): ?array - { - $pgt = $response['serviceResponse']['authenticationSuccess']['proxyGrantingTicket']; - - $hasPgtIou = $this->getCache()->hasItem($pgt); - - if (false === $hasPgtIou) { - $this - ->getLogger() - ->error('CAS validation failed: pgtIou not found in the cache.', ['pgtIou' => $pgt]); - - return null; - } - - $response['serviceResponse']['authenticationSuccess']['proxyGrantingTicket'] = $this - ->getCache() - ->getItem($pgt) - ->get(); - - return $response; - } - - /** - * Normalize a response. - */ - private function normalize(ResponseInterface $response): ResponseInterface - { - $body = $this->parse($response); - - if ([] === $body) { - $this - ->getLogger() - ->error( - 'Unable to parse the response during the normalization process.', - [ - 'body' => (string) $response->getBody(), - ] - ); - - return $response; - } - - $body = json_encode($body); - - if (false === $body || JSON_ERROR_NONE !== json_last_error()) { - $this - ->getLogger() - ->error( - 'Unable to encode the response in JSON during the normalization process.', - [ - 'body' => (string) $response->getBody(), - ] - ); - - return $response; - } - - $this - ->getLogger() - ->debug('Response normalization succeeded.', ['body' => $body]); - - return $response - ->withBody($this->getStreamFactory()->createStream($body)) - ->withHeader('Content-Type', 'application/json'); - } -} diff --git a/src/Service/ServiceValidate.php b/src/Service/ServiceValidate.php deleted file mode 100644 index 33fa863..0000000 --- a/src/Service/ServiceValidate.php +++ /dev/null @@ -1,39 +0,0 @@ -getProperties()['protocol']['serviceValidate'] ?? []; - - $protocolProperties['default_parameters'] += [ - 'service' => (string) $this->getServerRequest()->getUri(), - 'ticket' => Uri::getParam($this->getServerRequest()->getUri(), 'ticket'), - ]; - - return $protocolProperties; - } - - protected function getUri(): UriInterface - { - return $this->buildUri( - $this->getServerRequest()->getUri(), - 'serviceValidate', - $this->formatProtocolParameters($this->getParameters()) - ); - } -} diff --git a/src/Utils/Response.php b/src/Utils/Response.php new file mode 100644 index 0000000..3e25071 --- /dev/null +++ b/src/Utils/Response.php @@ -0,0 +1,100 @@ +getBody(); + + if ('' === $body) { + throw CasException::emptyResponseBodyFailure(); + } + + if (false === $response->hasHeader('Content-Type')) { + throw CasException::missingResponseContentTypeHeader(); + } + + $header = substr($response->getHeaderLine('Content-Type'), 0, 16); + + switch (true) { + case 0 === strpos($header, 'application/json'): + try { + $json = json_decode($body, true, 512, JSON_THROW_ON_ERROR); + } catch (Throwable $exception) { + throw CasException::unableToConvertResponseFromJson($exception); + } + + return $json; + + case 0 === strpos($header, 'text/html'): + case 0 === strpos($header, 'text/xml'): + case 0 === strpos($header, 'application/xml'): + set_error_handler( + static function ($errno, $errstr, $errfile, $errline): void { + throw CasException::unableToLoadXml( + new ErrorException($errstr, 0, $errno, $errfile, $errline) + ); + } + ); + $dom = new DOMDocument(); + $dom + ->loadXML( + $body, + LIBXML_NSCLEAN | LIBXML_NOCDATA | LIBXML_NOBLANKS | LIBXML_NONET + ); + restore_error_handler(); + + $this->removeDomNamespace($dom, 'cas'); + + try { + $data = XML2Array::createArray($dom); + } catch (Throwable $exception) { + throw CasException::unableToConvertResponseFromXml($exception); + } + + return $data; + } + + throw CasException::unsupportedResponseFormat($header); + } + + 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/Uri.php b/src/Utils/Uri.php index 0b0ce8a..65df87c 100644 --- a/src/Utils/Uri.php +++ b/src/Utils/Uri.php @@ -11,26 +11,64 @@ namespace EcPhp\CasLib\Utils; +use InvalidArgumentException; use League\Uri\Components\Query; use Psr\Http\Message\UriInterface; +use Throwable; +use TypeError; final class Uri { - public static function getParam(UriInterface $uri, string $param, ?string $default = null): ?string - { + /** + * Get a parameter from the URI query string. + * + * @param UriInterface $uri + * The URI. + * @param string $param + * The parameter key. + * @param string $default + * The default value if the parameter doesn't exist. + * + * @return string + * The value of the parameter to get. + */ + public static function getParam( + UriInterface $uri, + string $param, + string $default = '' + ): string { return self::getParams($uri)[$param] ?? $default; } /** + * Get the parameters from the URI query string. + * + * @param UriInterface $uri + * The URI. + * * @return string[] + * The parameters, as "key/value" pairs. */ public static function getParams(UriInterface $uri): array { - return iterator_to_array(Query::createFromUri($uri)->pairs()); + $pairs = []; + + try { + $pairs = Query::createFromUri($uri)->pairs(); + } catch (Throwable $exception) { + // Ignore the exception. + } + + return iterator_to_array($pairs); } /** + * Check wether an URI has the requested parameters. + * + * @param UriInterface $uri + * The URI. * @param string ...$keys + * The parameter keys to check. */ public static function hasParams(UriInterface $uri, string ...$keys): bool { @@ -40,16 +78,18 @@ public static function hasParams(UriInterface $uri, string ...$keys): bool /** * Remove one or more parameters from an URI. * - * @param \Psr\Http\Message\UriInterface $uri + * @param UriInterface $uri * The URI. * @param string ...$keys * The parameter(s) to remove. * - * @return \Psr\Http\Message\UriInterface + * @return UriInterface * A new URI without the parameter(s) to remove. */ - public static function removeParams(UriInterface $uri, string ...$keys): UriInterface - { + public static function removeParams( + UriInterface $uri, + string ...$keys + ): UriInterface { return $uri ->withQuery( http_build_query( @@ -61,6 +101,24 @@ public static function removeParams(UriInterface $uri, string ...$keys): UriInte ); } + /** + * Add a parameter to an URI. + * + * @param UriInterface $uri + * The URI. + * @param string $key + * The key of the parameter to add to the URI. + * @param string $value + * The value of the parameter to add to the URI. + * @param bool $force + * If true, overwrite any existing parameter from the URI. + * + * @throws TypeError + * @throws InvalidArgumentException + * + * @return UriInterface + * A new URI with the added parameter. + */ public static function withParam( UriInterface $uri, string $key, @@ -77,13 +135,29 @@ public static function withParam( } /** + * Add parameters to an URI. + * + * @param UriInterface $uri + * The URI. * @param string[] $params + * The set of parameters to add. + * @param bool $force + * If true, overwrite any existing parameter from the URI. + * + * @throws TypeError + * @throws InvalidArgumentException + * + * @return UriInterface + * A new URI with the added parameters. */ public static function withParams( UriInterface $uri, array $params, bool $force = true ): UriInterface { + // Reduce operation + // Cannot use `array_reduce` because it + // doesn't pass the key to the callback. foreach ($params as $key => $value) { $uri = self::withParam($uri, $key, $value, $force); } diff --git a/tests/Cas.php b/tests/Cas.php index 6291b65..29662d9 100644 --- a/tests/Cas.php +++ b/tests/Cas.php @@ -11,18 +11,14 @@ namespace tests\EcPhp\CasLib; -use EcPhp\CasLib\CasInterface; -use EcPhp\CasLib\Configuration\PropertiesInterface; -use EcPhp\CasLib\Introspection\Contract\IntrospectionInterface; +use EcPhp\CasLib\Contract\CasInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; -class Cas implements CasInterface +final class Cas implements CasInterface { - /** - * @var \EcPhp\CasLib\Cas - */ - private $cas; + private CasInterface $cas; public function __construct( CasInterface $cas @@ -30,78 +26,56 @@ public function __construct( $this->cas = $cas; } - public function authenticate(array $parameters = []): ?array + public function authenticate(ServerRequestInterface $request, array $parameters = []): array { - return $this->cas->authenticate($parameters); + return $this->cas->authenticate($request, $parameters); } - public function detect( - ResponseInterface $response - ): IntrospectionInterface { - return $this->cas->detect($response); + public function handleProxyCallback( + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this->cas->handleProxyCallback($request, $parameters); } - public function getProperties(): PropertiesInterface + public function login(ServerRequestInterface $request, array $parameters = []): ResponseInterface { - return $this->cas->getProperties(); + return $this->cas->login($request, $parameters); } - public function handleProxyCallback( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - return $this->cas->handleProxyCallback($parameters, $response); - } - - public function login(array $parameters = []): ?ResponseInterface + public function logout(ServerRequestInterface $request, array $parameters = []): ResponseInterface { - return $this->cas->login($parameters); + return $this->cas->logout($request, $parameters); } - public function logout(array $parameters = []): ?ResponseInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - return $this->cas->logout($parameters); + return $this->cas->process($request, $handler); } public function requestProxyTicket( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - return $this->cas->requestProxyTicket($parameters, $response); - } - - public function requestProxyValidate( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - return $this->cas->requestProxyValidate($parameters, $response); + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this->cas->requestProxyTicket($request, $parameters); } public function requestServiceValidate( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - return $this->cas->requestServiceValidate($parameters, $response); + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this->cas->requestServiceValidate($request, $parameters); } public function requestTicketValidation( - array $parameters = [], - ?ResponseInterface $response = null - ): ?ResponseInterface { - return $this->cas->requestTicketValidation($parameters, $response); + ServerRequestInterface $request, + array $parameters = [] + ): ResponseInterface { + return $this->cas->requestTicketValidation($request, $parameters); } - public function supportAuthentication(array $parameters = []): bool + public function supportAuthentication(ServerRequestInterface $request, array $parameters = []): bool { - return $this->cas->supportAuthentication($parameters); - } - - public function withServerRequest( - ServerRequestInterface $serverRequest - ): CasInterface { - $clone = clone $this; - $clone->cas = $clone->cas->withServerRequest($serverRequest); - - return $clone; + return $this->cas->supportAuthentication($request, $parameters); } } diff --git a/tests/Service/ProxyValidate.php b/tests/Service/ProxyValidate.php deleted file mode 100644 index 7b558e6..0000000 --- a/tests/Service/ProxyValidate.php +++ /dev/null @@ -1,114 +0,0 @@ -getProperties()['protocol']['proxyValidate'] ?? []; - - $protocolProperties['default_parameters'] += [ - 'service' => (string) $this->getServerRequest()->getUri(), - 'ticket' => Uri::getParam($this->getServerRequest()->getUri(), 'ticket'), - ]; - - return $protocolProperties; - } - - /** - * Get the URI. - */ - protected function getUri(): UriInterface - { - return $this->buildUri( - $this->getServerRequest()->getUri(), - 'proxyValidate', - $this->formatProtocolParameters($this->getParameters()) - ); - } -}