From 9eaa1b74e88269de4f7ae57f402f2edf4c7807b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nagy=20Kriszti=C3=A1n?= Date: Fri, 14 Jun 2024 13:12:21 +0000 Subject: [PATCH] SECURITY-480: compatibility with the central test suite Co-authored-by: Laszlo Losonczy Co-authored-by: Dora Kaszasne Sztanko --- .github/workflows/php.yml | 64 +- .gitmodules | 3 + Dockerfile | 51 ++ Makefile | 13 +- README.md | 38 +- composer.json | 8 +- composer.lock | 703 +++++------------- docker-compose.yaml | 6 +- phpunit.xml | 20 + src/Escher/AuthElements.php | 32 +- src/Escher/Escher.php | 89 ++- src/Escher/RequestCanonicalizer.php | 34 +- src/Escher/RequestHelper.php | 4 +- src/Escher/Signer.php | 2 +- src/Escher/Utils.php | 2 +- test-cases | 1 + test/Escher/Test/EndToEnd/CentralTest.php | 170 +++++ test/Escher/Test/Helper/JsonTestCase.php | 198 +++++ .../{unit => Escher/Test/Helper}/TestBase.php | 24 +- .../Test/Unit/AuthenticateRequestTest.php | 402 ++++++++++ .../Test/Unit}/InternalTest.php | 37 +- .../Test/Unit/RequestCanonicalizerTest.php | 20 + .../Test/Unit}/SignRequestUsingHeaderTest.php | 166 +++-- .../Unit}/SignRequestUsingQueryStringTest.php | 24 +- .../Test/Unit}/SigningProcessTest.php | 29 +- test/bootstrap.php | 3 - test/phpunit.xml | 30 - test/unit/AuthenticateRequestTest.php | 284 ------- test/unit/RequestCanonicalizerTest.php | 28 - 29 files changed, 1359 insertions(+), 1126 deletions(-) create mode 100644 .gitmodules create mode 100644 Dockerfile create mode 100644 phpunit.xml create mode 160000 test-cases create mode 100644 test/Escher/Test/EndToEnd/CentralTest.php create mode 100644 test/Escher/Test/Helper/JsonTestCase.php rename test/{unit => Escher/Test/Helper}/TestBase.php (51%) create mode 100644 test/Escher/Test/Unit/AuthenticateRequestTest.php rename test/{unit => Escher/Test/Unit}/InternalTest.php (78%) create mode 100644 test/Escher/Test/Unit/RequestCanonicalizerTest.php rename test/{unit => Escher/Test/Unit}/SignRequestUsingHeaderTest.php (68%) rename test/{unit => Escher/Test/Unit}/SignRequestUsingQueryStringTest.php (71%) rename test/{unit => Escher/Test/Unit}/SigningProcessTest.php (90%) delete mode 100644 test/bootstrap.php delete mode 100644 test/phpunit.xml delete mode 100644 test/unit/AuthenticateRequestTest.php delete mode 100644 test/unit/RequestCanonicalizerTest.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dd4547d..b28c0be 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,28 +1,36 @@ -name: PHP - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - php-versions: ['7.3', '7.4', '8.0'] - - steps: - - name: Install prerequesits - run: sudo apt update && sudo apt install -y php-mbstring - - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring - tools: composer - - name: Install dependencies - run: composer update && composer install - - name: Test - run: composer test - - name: Deploy - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - run: | - curl -XPOST -f -H'content-type:application/json' "https://packagist.org/api/update-package?username=emartech&apiToken=${{secrets.PACKAGIST_API_TOKEN}}" -d"{\"repository\":{\"url\":\"${{secrets.PACKAGIST_PACKAGE_URL}}\"}}" \ No newline at end of file +name: PHP + +on: + push: + branches: [ master ] + tags: + - 'v*' + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + + steps: + - name: Install prerequesits + run: sudo apt update && sudo apt install -y php-mbstring + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring + tools: composer + - name: Install dependencies + run: composer update && composer install + - name: Test + run: composer test + - name: Deploy + if: startsWith(github.ref, 'refs/tags') && matrix.php-versions == '8.3' + run: | + curl -XPOST -f -H'content-type:application/json' "https://packagist.org/api/update-package?username=emartech&apiToken=${{secrets.PACKAGIST_API_TOKEN}}" -d"{\"repository\":{\"url\":\"${{secrets.PACKAGIST_PACKAGE_URL}}\"}}" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c402990 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test-cases"] + path = test-cases + url = git@github.com:EscherAuth/test-cases.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a9f98ee --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM php:7.3-alpine3.15 + +ENV COMPOSER_ALLOW_SUPERUSER 1 +ENV COMPOSER_HOME /tmp +ENV COMPOSER_VERSION 2.2.23 + +RUN set -eux ; \ + apk add --no-cache --virtual .composer-rundeps \ + bash \ + coreutils \ + git \ + make \ + openssh-client \ + patch \ + subversion \ + tini \ + bzip2 \ + bzip2-dev \ + zlib \ + zlib-dev \ + libzip \ + libzip-dev \ + unzip \ + zip + +RUN set -eux ; \ + # install necessary/useful extensions not included in base image + docker-php-ext-install \ + bz2 \ + zip \ + ; \ + # download installer.php, see https://getcomposer.org/download/ + curl \ + --silent \ + --fail \ + --location \ + --retry 3 \ + --output /tmp/installer.php \ + --url https://raw.githubusercontent.com/composer/getcomposer.org/f24b8f860b95b52167f91bbd3e3a7bcafe043038/web/installer \ + ; \ + # install composer phar binary + php /tmp/installer.php \ + --no-ansi \ + --install-dir=/usr/bin \ + --filename=composer \ + --version=${COMPOSER_VERSION} \ + ; \ + composer --ansi --version --no-interaction ; \ + composer diagnose ; \ + rm -f /tmp/installer.php ; \ + find /tmp -type d -exec chmod -v 1777 {} + \ diff --git a/Makefile b/Makefile index a1d68b0..c5a03a2 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,10 @@ -tests: ## Run tests in docker - @docker-compose up -d --build - @docker-compose run web /bin/bash -l -c "/var/www/html/vendor/bin/phpunit -c /var/www/html/test/phpunit.xml" +.PHONY: test + +build: ; docker compose build + +install: ; docker compose run --rm web composer install +update: ; docker compose run --rm web composer update + +test: ; docker compose run --rm web ./vendor/bin/phpunit --do-not-cache-result -c phpunit.xml +test-only: ; docker compose run --rm web ./vendor/bin/phpunit --do-not-cache-result --group only -c phpunit.xml + diff --git a/README.md b/README.md index e9ce9c8..1fd8674 100644 --- a/README.md +++ b/README.md @@ -88,24 +88,24 @@ Exceptions | 5xxx | Expired exception | | 6xxx | Signature exceptions | -| Code | Message | -|------|-------------------------------------------------------------------------------------| -| 1001 | Escher authentication is missing | -| 1100 | The {PARAM} header is missing | -| 1101 | Query key: {PARAM} is missing | -| 1102 | The host header is missing | -| 2001 | Date header is invalid, the expected format is Wed, 04 Nov 2015 09:20:22 GMT | -| 2002 | Auth header format is invalid | -| 2003 | Invalid {PARAM} query key format | -| 2004 | Date header is invalid, the expected format is 20151104T092022Z | -| 3001 | Invalid Escher key | -| 3002 | Hash algorithm is invalid. Only SHA256 and SHA512 are allowed | -| 3003 | Credential scope is invalid | -| 3004 | Date in the authorization header is invalid. It must be the same as the date header | -| 4001 | The host header is not signed | -| 4002 | The {PARAM} header is not signed | -| 5001 | The request date is not within the accepted time range | -| 6001 | The signatures do not match | +| Code | Message | +|------|------------------------------------------------------------------------------| +| 1001 | The authorization header is missing | +| 1100 | The {PARAM} header is missing | +| 1101 | Query key: {PARAM} is missing | +| 1102 | The host header is missing | +| 2001 | Date header is invalid, the expected format is Wed, 04 Nov 2015 09:20:22 GMT | +| 2002 | Could not parse auth header | +| 2003 | Invalid {PARAM} query key format | +| 2004 | Date header is invalid, the expected format is 20151104T092022Z | +| 3001 | Invalid Escher key | +| 3002 | Only SHA256 and SHA512 hash algorithms are allowed | +| 3003 | The credential scope is invalid | +| 3004 | The credential date does not match with the request date | +| 4001 | The host header is not signed | +| 4002 | The {PARAM} header is not signed | +| 5001 | The request date is not within the accepted time range | +| 6001 | The signatures do not match | Configuration ------------- @@ -115,4 +115,4 @@ TBA Running tests ------------- 1. Install packages with Composer: `composer install` -2. Run tests with `make tests` \ No newline at end of file +2. Run tests with `make tests` diff --git a/composer.json b/composer.json index 634b48d..bf89c65 100755 --- a/composer.json +++ b/composer.json @@ -29,13 +29,17 @@ ], "type": "library", "autoload": { - "psr-4": {"": "src/"} + "psr-4": {"Escher\\": "src/Escher"} + }, + "autoload-dev": { + "psr-4": {"Escher\\Test\\": "test/Escher/Test"} }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "9.5.3" + "ext-json": "*", + "phpunit/phpunit": "9.6.19" }, "keywords": [ "escher", diff --git a/composer.lock b/composer.lock index e653bc4..23e1438 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,35 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cbc853f25ba791393487207ceaa9f594", + "content-hash": "568f0e9302e647308b8226a3a83e2a10", "packages": [], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -58,7 +59,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -74,41 +75,42 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -124,7 +126,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -132,25 +134,25 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", @@ -186,26 +188,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2020-12-20T10:01:03+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -246,22 +249,28 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2020-06-27T14:33:11+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -297,254 +306,29 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" - }, - "time": "2021-02-23T14:00:09+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" - }, - "time": "2020-09-03T19:13:55+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" - }, - "time": "2020-09-17T18:55:26+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "1.13.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.11.x-dev" - } + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" - }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.5", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -559,8 +343,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -593,7 +377,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -601,20 +386,20 @@ "type": "github" } ], - "time": "2020-11-28T06:44:49+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -653,7 +438,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -661,7 +446,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -846,20 +631,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.3", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "27241ac75fc37ecf862b6e002bf713b6566cbe41" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/27241ac75fc37ecf862b6e002bf713b6566cbe41", - "reference": "27241ac75fc37ecf862b6e002bf713b6566cbe41", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -867,34 +652,29 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", "phpunit/php-timer": "^5.0.2", "sebastian/cli-parser": "^1.0.1", "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", + "sebastian/comparator": "^4.0.8", "sebastian/diff": "^4.0.3", "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", + "sebastian/exporter": "^4.0.5", "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3", + "sebastian/type": "^3.2", "sebastian/version": "^3.0.2" }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -902,15 +682,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -933,32 +713,37 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.3" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2021-03-17T07:30:34+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -993,7 +778,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -1001,7 +786,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -1116,16 +901,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -1178,7 +963,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -1186,24 +971,24 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1235,7 +1020,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1243,20 +1028,20 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -1301,7 +1086,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -1309,20 +1094,20 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -1364,7 +1149,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -1372,20 +1157,20 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -1434,14 +1219,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -1449,20 +1234,20 @@ "type": "github" } ], - "time": "2020-09-28T05:24:23+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.2", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -1505,7 +1290,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -1513,24 +1298,24 @@ "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1562,7 +1347,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -1570,7 +1355,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -1686,16 +1471,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -1734,10 +1519,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -1745,20 +1530,20 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -1770,7 +1555,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1791,8 +1576,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -1800,32 +1584,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1848,7 +1632,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -1856,7 +1640,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -1911,97 +1695,18 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.22.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" - }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -2030,7 +1735,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -2038,65 +1743,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" - }, - "time": "2021-03-09T10:59:23+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -2108,5 +1755,5 @@ "php": ">=7.3" }, "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.2.0" } diff --git a/docker-compose.yaml b/docker-compose.yaml index 11cda3e..5b07848 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,8 +1,6 @@ -version: '3' services: web: - image: php:7.3 - networks: - - default + build: . + working_dir: /var/www/html/ volumes: - ".:/var/www/html/" diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..78302a3 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,20 @@ + + + + + + test/Escher/Test/Unit/ + + + test/Escher/Test/EndToEnd/ + + + diff --git a/src/Escher/AuthElements.php b/src/Escher/AuthElements.php index 0399527..c162822 100644 --- a/src/Escher/AuthElements.php +++ b/src/Escher/AuthElements.php @@ -66,24 +66,24 @@ public static function parseFromHeaders(array $headerList, $authHeaderKey, $date public static function parseAuthHeader($headerContent, $algoPrefix) { $pattern = '/^' . $algoPrefix . '-HMAC-([A-Z0-9\,]+)(.*)' . - 'Credential=([A-Za-z0-9\/\-_]+),(.*)' . + 'Credential=([A-Za-z0-9\/\-_ ]+),(.*)' . 'SignedHeaders=([A-Za-z\-;]+),(.*)' . 'Signature=([0-9a-f]+)$/'; if (!preg_match($pattern, $headerContent, $matches)) { - throw new Exception('Auth header format is invalid', Exception::CODE_FORMAT_INVALID_AUTH_HEADER); + throw new Exception('Could not parse auth header', Exception::CODE_FORMAT_INVALID_AUTH_HEADER); } - return array( + return [ 'Algorithm' => $matches[1], 'Credentials' => $matches[3], 'SignedHeaders' => $matches[5], 'Signature' => $matches[7], - ); + ]; } public static function parseFromQuery($headerList, $queryParams, $vendorKey, $algoPrefix) { - $elementParts = array(); + $elementParts = []; $paramKey = self::checkParam($queryParams, $vendorKey, 'Algorithm'); $pattern = '/^' . $algoPrefix . '-HMAC-([A-Z0-9\,]+)$/'; @@ -104,13 +104,13 @@ public static function parseFromQuery($headerList, $queryParams, $vendorKey, $al private static function basicQueryParamKeys() { - return array( + return [ 'Credentials', 'Date', 'Expires', 'SignedHeaders', 'Signature' - ); + ]; } /** @@ -141,7 +141,7 @@ public function validateDates(RequestHelper $helper, $clockSkew) { $shortDate = $this->dateTime->format('Ymd'); if ($shortDate !== $this->getShortDate()) { - throw new Exception('Date in the authorization header is invalid. It must be the same as the date header', Exception::CODE_ARGUMENT_INVALID_DATE); + throw new Exception('The credential date does not match with the request date', Exception::CODE_ARGUMENT_INVALID_DATE); } if (!$this->isInAcceptableInterval($helper->getTimeStamp(), Utils::getTimeStampOfDateTime($this->dateTime), $clockSkew)) { @@ -152,7 +152,7 @@ public function validateDates(RequestHelper $helper, $clockSkew) public function validateCredentials(RequestHelper $helper, $credentialScope) { if (!$this->checkCredentials($credentialScope)) { - throw new Exception('Credential scope is invalid', Exception::CODE_ARGUMENT_INVALID_CREDENTIAL_SCOPE); + throw new Exception('The credential scope is invalid', Exception::CODE_ARGUMENT_INVALID_CREDENTIAL_SCOPE); } } @@ -192,9 +192,9 @@ private function lookupSecretKey($accessKeyId, $keyDB) public function validateHashAlgo() { - if(!in_array(strtoupper($this->getAlgorithm()), array('SHA256','SHA512'))) + if(!in_array(strtoupper($this->getAlgorithm()), ['SHA256','SHA512'])) { - throw new Exception('Hash algorithm is invalid. Only SHA256 and SHA512 are allowed', Exception::CODE_ARGUMENT_INVALID_HASH); + throw new Exception('Only SHA256 and SHA512 hash algorithms are allowed', Exception::CODE_ARGUMENT_INVALID_HASH); } } @@ -202,14 +202,16 @@ public function validateHashAlgo() * @param string $dateHeaderKey * @throws Exception */ - public function validateMandatorySignedHeaders($dateHeaderKey) + public function validateMandatorySignedHeaders($mandatorySignedHeaders) { $signedHeaders = $this->getSignedHeaders(); if (!in_array('host', $signedHeaders)) { throw new Exception('The host header is not signed', Exception::CODE_NOT_SIGNED_HOST_HEADER); } - if ($this->isFromHeaders && !in_array(strtolower($dateHeaderKey), $signedHeaders)) { - throw new Exception('The ' . strtolower($dateHeaderKey) . ' header is not signed', Exception::CODE_NOT_SIGNED_HEADER_PARAM); + foreach ($mandatorySignedHeaders as $headerName) { + if ($this->isFromHeaders && !in_array(strtolower($headerName), $signedHeaders)) { + throw new Exception('The ' . strtolower($headerName) . ' header is not signed', Exception::CODE_NOT_SIGNED_HEADER_PARAM); + } } } @@ -253,7 +255,7 @@ private function stripAuthParams(RequestHelper $helper, $vendorKey) $url = $helper->getCurrentUrl(); $signaturePattern = "/(?P[?&])X-${vendorKey}-Signature=[a-fA-F0-9]{64}(?P&?)/"; - return preg_replace_callback($signaturePattern, array($this, 'handleStripAuthParamMatches'), $url); + return preg_replace_callback($signaturePattern, [$this, 'handleStripAuthParamMatches'], $url); } private function getExpires() diff --git a/src/Escher/Escher.php b/src/Escher/Escher.php index 3d8d1ce..7bef580 100644 --- a/src/Escher/Escher.php +++ b/src/Escher/Escher.php @@ -27,6 +27,11 @@ class Escher private $authHeaderKey = self::DEFAULT_AUTH_HEADER_KEY; private $dateHeaderKey = self::DEFAULT_DATE_HEADER_KEY; + /** + * @var array + */ + public $debugInfo = []; + public function __construct($credentialScope) { $this->credentialScope = $credentialScope; @@ -53,7 +58,7 @@ private static function now() * @return mixed * @throws Exception */ - public function authenticate($keyDB, array $serverVars = null, $requestBody = null) + public function authenticate($keyDB, array $serverVars = null, $requestBody = null, $mandatorySignedHeaders = []) { $serverVars = null === $serverVars ? $_SERVER : $serverVars; $requestBody = null === $requestBody ? $this->fetchRequestBodyFor($serverVars['REQUEST_METHOD']) : $requestBody; @@ -61,9 +66,23 @@ public function authenticate($keyDB, array $serverVars = null, $requestBody = nu $algoPrefix = $this->algoPrefix; $vendorKey = $this->vendorKey; $helper = new RequestHelper($serverVars, $requestBody, $this->authHeaderKey, $this->dateHeaderKey); + + if (!in_array(strtolower($helper->getRequestMethod()), ['get', 'head', 'post', 'put', 'delete', 'connect', 'options', 'trace', 'patch'])) { + throw new Exception('The request method is invalid'); + } + if (!is_array($mandatorySignedHeaders)) { + throw new Exception('The mandatorySignedHeaders parameter must be undefined or array of strings'); + } + foreach ($mandatorySignedHeaders as $headerName) { + if (!is_string($headerName)) { + throw new Exception('The mandatorySignedHeaders parameter must be undefined or array of strings'); + } + } + $authElements = $helper->getAuthElements($this->vendorKey, $algoPrefix); - $authElements->validateMandatorySignedHeaders($this->dateHeaderKey); + $mandatorySignedHeaders[] = $this->dateHeaderKey; + $authElements->validateMandatorySignedHeaders($mandatorySignedHeaders); $authElements->validateHashAlgo(); $authElements->validateDates($helper, $this->clockSkew); $authElements->validateCredentials($helper, $this->credentialScope); @@ -76,7 +95,12 @@ public function presignUrl($accessKeyId, $secretKey, $url, $expires = Escher::DE $date = $date ?: self::now(); $url = $this->appendSigningParams($accessKeyId, $url, $date, $expires); - list($host, $path, $query) = $this->parseUrl($url); + list($host, $port, $path, $query) = $this->parseUrl($url); + $portInOriginalUrl = $port && strpos($url, ':' . $port) !== false; + $portInParsedHost = $port && strpos($host, ':' . $port) !== false; + if ($portInOriginalUrl && !$portInParsedHost) { + $host .= ':' . $port; + } list($signature) = $this->calculateSignature( $secretKey, @@ -85,18 +109,25 @@ public function presignUrl($accessKeyId, $secretKey, $url, $expires = Escher::DE $path, $query, self::UNSIGNED_PAYLOAD, - array('host' => $host), - (array('host')) + ['host' => $host], + ['host'] ); $url = $this->addGetParameter($url, $this->generateParamName('Signature'), $signature); return $url; } - public function signRequest($accessKeyId, $secretKey, $method, $url, $requestBody, $headerList = array(), $headersToSign = array(), DateTime $date = null) + public function signRequest($accessKeyId, $secretKey, $method, $url, $requestBody, $headerList = [], $headersToSign = [], DateTime $date = null) { + if (!in_array(strtolower($method), ['get', 'head', 'post', 'put', 'delete', 'connect', 'options', 'trace', 'patch'])) { + throw new Exception('The request method is invalid'); + } + if (!$accessKeyId || !$secretKey) { + throw new Exception('Invalid Escher key'); + } + $date = $date ?: self::now(); - list($host, $path, $query) = $this->parseUrl($url); + list($host, , $path, $query) = $this->parseUrl($url); list($headerList, $headersToSign) = $this->addMandatoryHeaders( $headerList, $headersToSign, $this->dateHeaderKey, $date, $host ); @@ -117,13 +148,13 @@ public function signRequest($accessKeyId, $secretKey, $method, $url, $requestBod private function appendSigningParams($accessKeyId, $url, $date, $expires) { - $signingParams = array( + $signingParams = [ 'Algorithm' => $this->algoPrefix. '-HMAC-' . $this->hashAlgo, 'Credentials' => $accessKeyId . '/' . $this->fullCredentialScope($date), 'Date' => $this->toLongDate($date), 'Expires' => $expires, 'SignedHeaders' => 'host', - ); + ]; foreach ($signingParams as $param => $value) { $url = $this->addGetParameter($url, $this->generateParamName($param), $value); @@ -138,18 +169,19 @@ private function generateParamName($param) public function getSignature($secretKey, DateTime $date, $method, $url, $requestBody, $headerList, $signedHeaders) { - list(, $path, $query) = $this->parseUrl($url); + list(, , $path, $query) = $this->parseUrl($url); return $this->calculateSignature($secretKey, $date, $method, $path, $query, $requestBody, $headerList, $signedHeaders); } private function parseUrl($url) { - $urlParts = parse_url($url); + $urlParts = parse_url(str_replace('#', '%23', $url)); $defaultPort = $urlParts['scheme'] === 'http' ? 80 : 443; - $host = $urlParts['host'] . (isset($urlParts['port']) && $urlParts['port'] != $defaultPort ? ':' . $urlParts['port'] : ''); - $path = isset($urlParts['path']) ? $urlParts['path'] : null; - $query = isset($urlParts['query']) ? $urlParts['query'] : ''; - return array($host, $path, $query); + $port = isset($urlParts['port']) ? intval($urlParts['port']) : null; + $host = $urlParts['host'] . ($port && $port !== $defaultPort ? ':' . $port : ''); + $path = $urlParts['path'] ?? null; + $query = $urlParts['query'] ?? ''; + return [$host, $port, $path, $query]; } private function toLongDate(DateTime $date) @@ -157,6 +189,11 @@ private function toLongDate(DateTime $date) return $date->format(self::LONG_DATE); } + private function toHeaderDate(DateTime $date) + { + return str_replace(' +0000', ' GMT', $date->format('r')); + } + private function addGetParameter($url, $key, $value) { $glue = '?'; @@ -192,7 +229,7 @@ private function generateAuthHeader($secretKey, $accessKeyId, $authHeaderKey, $d $this->algoPrefix, $accessKeyId ); - return array(strtolower($authHeaderKey) => $authHeaderValue); + return [strtolower($authHeaderKey) => $authHeaderValue]; } private function calculateSignature($secretKey, $date, $method, $path, $query, $requestBody, array $headerList, array $headersToSign) @@ -201,9 +238,9 @@ private function calculateSignature($secretKey, $date, $method, $path, $query, $ $algoPrefix = $this->algoPrefix; $requestUri = $path . ($query ? '?' . $query : ''); // canonicalization works with raw headers - $rawHeaderLines = array(); + $rawHeaderLines = []; foreach ($headerList as $headerKey => $headerValue) { - $rawHeaderLines []= $headerKey . ':' . $headerValue; + $rawHeaderLines[] = $headerKey . ':' . $headerValue; } $canonicalizedRequest = RequestCanonicalizer::canonicalize( $method, @@ -213,6 +250,7 @@ private function calculateSignature($secretKey, $date, $method, $path, $query, $ $headersToSign, $hashAlgo ); + $this->debugInfo['canonicalizedRequest'] = $canonicalizedRequest; $stringToSign = Signer::createStringToSign( $this->credentialScope, @@ -221,6 +259,7 @@ private function calculateSignature($secretKey, $date, $method, $path, $query, $ $hashAlgo, $algoPrefix ); + $this->debugInfo['stringToSign'] = $stringToSign; $signerKey = Signer::calculateSigningKey( $secretKey, @@ -235,9 +274,7 @@ private function calculateSignature($secretKey, $date, $method, $path, $query, $ $hashAlgo ); - return array( - $signature, $canonicalizedRequest - ); + return [$signature, $canonicalizedRequest]; } /** @@ -250,11 +287,15 @@ private function calculateSignature($secretKey, $date, $method, $path, $query, $ */ private function addMandatoryHeaders($headerList, $headersToSign, $dateHeaderKey, $date, $host) { - $mandatoryHeaders = array(strtolower($dateHeaderKey) => $this->toLongDate($date), 'host' => $host); + $dateHeaderKey = strtolower($dateHeaderKey); + $mandatoryHeaders = [ + $dateHeaderKey => $dateHeaderKey === 'date' ? $this->toHeaderDate($date) : $this->toLongDate($date), + 'host' => $host, + ]; $headerList = Utils::keysToLower($headerList) + $mandatoryHeaders; $headersToSign = array_unique(array_merge(array_map('strtolower', $headersToSign), array_keys($mandatoryHeaders))); sort($headersToSign); - return array($headerList, $headersToSign); + return [$headerList, $headersToSign]; } /** @@ -265,7 +306,7 @@ private function addMandatoryHeaders($headerList, $headersToSign, $dateHeaderKey */ private function fetchRequestBodyFor($method) { - return in_array($method, array('PUT', 'POST')) ? file_get_contents('php://input') : ''; + return in_array($method, ['PUT', 'POST', 'PATCH']) ? file_get_contents('php://input') : ''; } /** diff --git a/src/Escher/RequestCanonicalizer.php b/src/Escher/RequestCanonicalizer.php index 5c212fb..3a39410 100644 --- a/src/Escher/RequestCanonicalizer.php +++ b/src/Escher/RequestCanonicalizer.php @@ -8,10 +8,10 @@ class RequestCanonicalizer public static function canonicalize($method, $requestUri, $payload, $rawHeaders, array $headersToSign, $hashAlgo) { list($path, $query) = array_pad(explode('?', $requestUri, 2), 2, ''); - $lines = array(); + $lines = []; $lines[] = strtoupper($method); $lines[] = self::normalizePath($path); - $lines[] = self::urlEncodeQueryString($query, $rawHeaders); + $lines[] = self::urlEncodeQueryString($query); sort($headersToSign); $lines = array_merge($lines, self::canonicalizeHeaders($rawHeaders, $headersToSign)); @@ -24,13 +24,13 @@ public static function canonicalize($method, $requestUri, $payload, $rawHeaders, return implode("\n", $lines); } - public static function urlEncodeQueryString($query, $headers) + public static function urlEncodeQueryString($query) { if (empty($query)) { return ''; } $pairs = explode('&', $query); - $encodedParts = array(); + $encodedParts = []; foreach ($pairs as $pair) { $keyValues = array_pad(explode('=', $pair), 2, ''); if (strpos($keyValues[0], ' ') !== false) { @@ -39,10 +39,10 @@ public static function urlEncodeQueryString($query, $headers) } $keyValues[0] = urldecode($keyValues[0]); $keyValues[1] = urldecode($keyValues[1]); - $encodedParts[] = implode('=', array( - self::rawUrlEncode($keyValues[0], $headers), - self::rawUrlEncode($keyValues[1], $headers), - )); + $encodedParts[] = implode('=', [ + self::rawUrlEncode($keyValues[0]), + self::rawUrlEncode($keyValues[1]), + ]); } sort($encodedParts); return implode('&', $encodedParts); @@ -59,7 +59,7 @@ private static function normalizePath($path) } $path = implode('/', $path); - $path = str_replace(array('./', '//'), array('', '/'), $path); + $path = str_replace(['./', '//'], ['', '/'], $path); if (empty($path)) { return '/'; @@ -74,7 +74,7 @@ private static function normalizePath($path) */ private static function canonicalizeHeaders($rawHeaders, array $headersToSign) { - $result = array(); + $result = []; foreach (explode("\n", $rawHeaders) as $header) { // TODO: add multiline header handling list ($key, $value) = explode(':', $header, 2); @@ -93,17 +93,9 @@ private static function canonicalizeHeaders($rawHeaders, array $headersToSign) return $result; } - private static function rawUrlEncode($urlComponent, $headers) + private static function rawUrlEncode($urlComponent) { - if(strpos($headers, "application/x-www-form-urlencoded")) { - $result = rawurlencode(str_replace('+', ' ', $urlComponent)); - } else { - $result = rawurlencode($urlComponent); - } - if (version_compare(PHP_VERSION, '5.3.4') === -1) { - $result = str_replace('%7E', '~', $result); - } - return $result; + return str_replace(['%21', '%2A'], ['!', '*'], rawurlencode($urlComponent)); } /** @@ -112,7 +104,7 @@ private static function rawUrlEncode($urlComponent, $headers) */ private static function nomalizeHeaderValue($value) { - $result = array(); + $result = []; foreach (explode('"', trim($value)) as $index => $piece) { $result[] = $index % 2 === 1 ? $piece : preg_replace('/\s+/', ' ', $piece); } diff --git a/src/Escher/RequestHelper.php b/src/Escher/RequestHelper.php index cf84cf8..d4577c9 100644 --- a/src/Escher/RequestHelper.php +++ b/src/Escher/RequestHelper.php @@ -38,7 +38,7 @@ public function getAuthElements($vendorKey, $algoPrefix) if($this->getRequestMethod() === 'GET' && isset($queryParams[$this->paramKey($vendorKey, 'Signature')])) { return AuthElements::parseFromQuery($headerList, $queryParams, $vendorKey, $algoPrefix); } - throw new Exception('Escher authentication is missing', Exception::CODE_MISSING_AUTH); + throw new Exception('The authorization header is missing', Exception::CODE_MISSING_AUTH); } public function getTimeStamp() @@ -74,7 +74,7 @@ public function getCurrentUrl() private function process(array $serverVars) { - $headerList = array(); + $headerList = []; foreach ($serverVars as $key => $value) { if (strpos($key, 'HTTP_') === 0) { $headerList[strtolower(str_replace('_', '-', substr($key, 5)))] = $value; diff --git a/src/Escher/Signer.php b/src/Escher/Signer.php index dc1498a..feeac7c 100644 --- a/src/Escher/Signer.php +++ b/src/Escher/Signer.php @@ -13,7 +13,7 @@ public static function createStringToSign($credentialScope, $canonicalRequestStr $date->setTimezone(new DateTimeZone('GMT')); $formattedDate = $date->format(Escher::LONG_DATE); $scope = substr($formattedDate,0, 8) . '/' . $credentialScope; - $lines = array(); + $lines = []; $lines[] = $algoPrefix . '-HMAC-' . strtoupper($hashAlgo); $lines[] = $formattedDate; $lines[] = $scope; diff --git a/src/Escher/Utils.php b/src/Escher/Utils.php index d9ef424..3c7d8ab 100644 --- a/src/Escher/Utils.php +++ b/src/Escher/Utils.php @@ -22,7 +22,7 @@ public static function keysToLower($array) { if (count($array) === 0) { - return array(); + return []; } return array_combine( array_map('strtolower', array_keys($array)), diff --git a/test-cases b/test-cases new file mode 160000 index 0000000..94d4468 --- /dev/null +++ b/test-cases @@ -0,0 +1 @@ +Subproject commit 94d4468f4ccb364469a91fb04ed8fd0cff41b885 diff --git a/test/Escher/Test/EndToEnd/CentralTest.php b/test/Escher/Test/EndToEnd/CentralTest.php new file mode 100644 index 0000000..cddd30f --- /dev/null +++ b/test/Escher/Test/EndToEnd/CentralTest.php @@ -0,0 +1,170 @@ + 'PHP does not handle multiple headers with the same name directly, the server before PHP does the conversion into the comma separated list', + 'emarsys_testsuite/signrequest/get-header-key-duplicate' => 'PHP does not handle multiple headers with the same name directly, the server before PHP does the conversion into the comma separated list', + 'test_cases/signrequest/error-invalid-request-url' => 'Not applicable for the PHP implementation', + 'test_cases/authenticate/error-post-body-null' => 'Not applicable for the PHP implementation', + 'test_cases/authenticate/error-invalid-request-url' => 'Not applicable for the PHP implementation', + ]; + + public function signRequestTestCases() + { + $data = []; + foreach (JsonTestCase::getTestCases('signrequest') as $testCase) { + $data["{$testCase->suite}/{$testCase->type}/{$testCase->name}"] = [$testCase]; + } + return $data; + } + + public function authenticateTestCases() + { + $data = []; + foreach (JsonTestCase::getTestCases('authenticate') as $testCase) { + $data["{$testCase->suite}/{$testCase->type}/{$testCase->name}"] = [$testCase]; + } + return $data; + } + + public function presignUrlTestCases() + { + $data = []; + foreach (JsonTestCase::getTestCases('presignurl') as $testCase) { + $data["{$testCase->suite}/{$testCase->type}/{$testCase->name}"] = [$testCase]; + } + return $data; + } + + /** + * @test + * @dataProvider signRequestTestCases + */ + public function signRequestTests(JsonTestCase $testCase) + { + if (array_key_exists("{$testCase->suite}/{$testCase->type}/{$testCase->name}", self::$ignoredTestCases)) { + $this->markTestSkipped(self::$ignoredTestCases["{$testCase->suite}/{$testCase->type}/{$testCase->name}"]); + } + + $escher = Escher::create($testCase->getCredentialScope()) + ->setAlgoPrefix($testCase->getAlgoPrefix()) + ->setVendorKey($testCase->getVendorKey()) + ->setHashAlgo($testCase->getHashAlgo()) + ->setAuthHeaderKey($testCase->getAuthHeaderName()) + ->setDateHeaderKey($testCase->getDateHeaderName()); + + $request = $testCase->getRequest(); + + try { + $signedHeaders = $escher->signRequest( + $testCase->getApiKey(), + $testCase->getApiSecret(), + $request['method'], + 'https://' . $request['headers']['Host'] . $request['url'], + $request['body'], + $request['headers'], + $testCase->getHeadersToSign(), + $testCase->getCurrentTime() + ); + + if ($testCase->hasExpectedCanonicalizedRequest()) { + $this->assertEquals($testCase->getExpectedCanonicalizedRequest(), $escher->debugInfo['canonicalizedRequest']); + } + if ($testCase->hasExpectedStringToSign()) { + $this->assertEquals($testCase->getExpectedStringToSign(), $escher->debugInfo['stringToSign']); + } + if ($testCase->hasExpectedHeaders()) { + $this->assertEquals($testCase->getExpectedHeaders(), $signedHeaders); + } else { + $this->fail('no request in expected'); + } + } catch (Exception $e) { + if ($testCase->hasExpectedError()) { + $this->assertEquals($testCase->getExpectedError(), $e->getMessage()); + } else { + throw $e; + } + } + } + + /** + * @test + * @dataProvider authenticateTestCases + */ + public function authenticateTests(JsonTestCase $testCase) + { + if (array_key_exists("{$testCase->suite}/{$testCase->type}/{$testCase->name}", self::$ignoredTestCases)) { + $this->markTestSkipped(self::$ignoredTestCases["{$testCase->suite}/{$testCase->type}/{$testCase->name}"]); + } + + $escher = Escher::create($testCase->getCredentialScope()) + ->setAlgoPrefix($testCase->getAlgoPrefix()) + ->setVendorKey($testCase->getVendorKey()) + ->setAuthHeaderKey($testCase->getAuthHeaderName()) + ->setDateHeaderKey($testCase->getDateHeaderName()); + + $request = $testCase->getRequest(); + $serverVars = [ + 'REQUEST_METHOD' => $request['method'], + 'REQUEST_URI' => $request['url'], + 'REQUEST_TIME' => $testCase->getCurrentTime()->format('U'), + 'HTTPS' => 'on', + 'SERVER_PORT' => '443', + 'SERVER_NAME' => $request['headers']['Host'], + ]; + foreach ($request['headers'] as $k => $v) { + $serverVars['HTTP_' . str_replace('-', '_', strtoupper($k))] = $v; + } + + try { + $apiKey = $escher->authenticate($testCase->getKeyDb(), $serverVars, $request['body'] ?? null, $testCase->getMandatorySignedHeaders()); + if ($testCase->hasExpectedApiKey()) { + $this->assertEquals($testCase->getExpectedApiKey(), $apiKey); + } else { + $this->fail('no apiKey in expected'); + } + } catch (Exception $e) { + if ($testCase->hasExpectedError()) { + $this->assertEquals($testCase->getExpectedError(), $e->getMessage()); + } else { + throw $e; + } + } + } + + /** + * @test + * @dataProvider presignUrlTestCases + */ + public function presignUrlTests(JsonTestCase $testCase) + { + if (array_key_exists("{$testCase->suite}/{$testCase->type}/{$testCase->name}", self::$ignoredTestCases)) { + $this->markTestSkipped(self::$ignoredTestCases["{$testCase->suite}/{$testCase->type}/{$testCase->name}"]); + } + + $escher = Escher::create($testCase->getCredentialScope()) + ->setAlgoPrefix($testCase->getAlgoPrefix()) + ->setVendorKey($testCase->getVendorKey()) + ->setAuthHeaderKey($testCase->getAuthHeaderName()) + ->setDateHeaderKey($testCase->getDateHeaderName()); + + $request = $testCase->getRequest(); + $url = $escher->presignUrl( + $testCase->getApiKey(), + $testCase->getApiSecret(), + $request['url'], + $request['expires'], + $testCase->getCurrentTime() + ); + + $this->assertEquals($testCase->getExpectedUrl(), $url); + } +} diff --git a/test/Escher/Test/Helper/JsonTestCase.php b/test/Escher/Test/Helper/JsonTestCase.php new file mode 100644 index 0000000..fb07553 --- /dev/null +++ b/test/Escher/Test/Helper/JsonTestCase.php @@ -0,0 +1,198 @@ +[^/]+)/(?P[^-]+)-(?P[^.]+)\.json$#', \RegexIterator::GET_MATCH); + + $cases = []; + foreach ($matches as $match) { + if ($match['type'] !== $type) { + continue; + } + if ($match['suite'] === '.conflict') { + continue; + } + $cases[] = new JsonTestCase($match[0], $match['suite'], $match['type'], $match['name']); + } + + return $cases; + } + + public function __construct(string $path, string $suite, string $type, string $name) + { + $this->suite = $suite; + $this->type = $type; + $this->name = $name; + + $this->data = json_decode(file_get_contents($path), true); + } + + public function getCredentialScope(): string + { + return $this->data['config']['credentialScope']; + } + + public function getHeadersToSign(): array + { + return $this->data['headersToSign']; + } + + public function getRequest(): array + { + $request = $this->data['request']; + $request['headers'] = []; + foreach ($this->data['request']['headers'] ?? [] as $h) { + $request['headers'][$h[0]] = $h[1]; + } + + return $request; + } + + public function hasExpectedCanonicalizedRequest(): bool + { + return array_key_exists('canonicalizedRequest', $this->data['expected']); + } + + public function hasExpectedStringToSign(): bool + { + return array_key_exists('stringToSign', $this->data['expected']); + } + + public function hasExpectedHeaders(): bool + { + return array_key_exists('request', $this->data['expected']); + } + + public function hasExpectedApiKey(): bool + { + return array_key_exists('apiKey', $this->data['expected']); + } + + public function hasExpectedError(): bool + { + return array_key_exists('error', $this->data['expected']); + } + + public function getExpectedCanonicalizedRequest(): string + { + return $this->data['expected']['canonicalizedRequest']; + } + + public function getExpectedStringToSign(): string + { + return $this->data['expected']['stringToSign']; + } + + public function getExpectedHeaders(): array + { + $headers = []; + foreach ($this->data['expected']['request']['headers'] ?: [] as $h) { + $headers[strtolower($h[0])] = $h[1]; + } + return $headers; + } + + public function getExpectedUrl(): string + { + return $this->data['expected']['url']; + } + + public function getExpectedApiKey(): string + { + return $this->data['expected']['apiKey']; + } + + public function getExpectedError(): string + { + return $this->data['expected']['error']; + } + + public function getMandatorySignedHeaders() + { + return $this->data['mandatorySignedHeaders'] ?: []; + } + + public function getAlgoPrefix(): string + { + return $this->data['config']['algoPrefix']; + } + + public function getVendorKey(): string + { + return $this->data['config']['vendorKey']; + } + + public function getHashAlgo(): string + { + return $this->data['config']['hashAlgo']; + } + + public function getAuthHeaderName(): ?string + { + return $this->data['config']['authHeaderName']; + } + + public function getDateHeaderName(): ?string + { + return $this->data['config']['dateHeaderName']; + } + + public function getApiKey(): string + { + return $this->data['config']['accessKeyId']; + } + + public function getApiSecret(): ?string + { + return $this->data['config']['apiSecret']; + } + + public function getKeyDb(): array + { + $keyDb = []; + foreach ($this->data['keyDb'] as $v) { + $keyDb[$v[0]] = $v[1]; + } + return $keyDb; + } + + public function getCurrentTime(): DateTime + { + $timeFormats = ['Y-m-d\TH:i:s.000Z', 'Y-m-d\TH:i:s\Z', 'l, d M Y H:i:s \G\M\T']; + + foreach ($timeFormats as $format) { + $currentTime = DateTime::createFromFormat($format, $this->data['config']['date'], new DateTimeZone('UTC')); + if ($currentTime) { + return $currentTime; + } + } + + throw new \Exception("Invalid time => {$this->data['config']['date']}"); + } +} diff --git a/test/unit/TestBase.php b/test/Escher/Test/Helper/TestBase.php similarity index 51% rename from test/unit/TestBase.php rename to test/Escher/Test/Helper/TestBase.php index 549f9f7..1dd41e8 100644 --- a/test/unit/TestBase.php +++ b/test/Escher/Test/Helper/TestBase.php @@ -1,32 +1,32 @@ assertEquals($expected, $actual, $message); } - /** - * @param string $credentialScope - * @return Escher - */ - protected function createEscher($credentialScope = 'us-east-1/host/aws4_request') + protected function createEscher(string $credentialScope = 'us-east-1/host/aws4_request'): Escher { return Escher::create($credentialScope) - ->setAlgoPrefix('EMS')->setVendorKey('EMS')->setAuthHeaderKey('X-Ems-Auth')->setDateHeaderKey('X-Ems-Date'); + ->setAlgoPrefix('EMS') + ->setVendorKey('EMS') + ->setAuthHeaderKey('X-Ems-Auth') + ->setDateHeaderKey('X-Ems-Date'); } - /** - * @return DateTime - */ - protected function getDate() + protected function getDate(): DateTime { - return new DateTime('2011/05/11 12:00:00', new DateTimeZone("UTC")); + return new DateTime('2011/05/11 12:00:00', new DateTimeZone('UTC')); } } diff --git a/test/Escher/Test/Unit/AuthenticateRequestTest.php b/test/Escher/Test/Unit/AuthenticateRequestTest.php new file mode 100644 index 0000000..3c1b41f --- /dev/null +++ b/test/Escher/Test/Unit/AuthenticateRequestTest.php @@ -0,0 +1,402 @@ + '20110909T233600Z', + 'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'REQUEST_TIME' => $this->strtotime('20110909T233600Z'), + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'iam.amazonaws.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/', + 'HTTPS' => '', + 'SERVER_PORT' => '80', + 'SERVER_NAME' => 'iam.amazonaws.com', + ]; + $keyDB = ['AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY']; + $accessKeyId = $this->createEscher('us-east-1/iam/aws4_request') + ->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08'); + $this->assertEquals('AKIDEXAMPLE', $accessKeyId); + } + + /** + * @test + * @dataProvider validPortProvider + * @param $httpHost + * @param $serverName + * @param $serverPort + * @param $https + * @param $signature + * @throws Exception + */ + public function itShouldAuthenticateRequestRegardlessDefaultPortProvidedOrNot( + $httpHost, + $serverName, + $serverPort, + $https, + $signature + ) { + $serverVars = [ + 'HTTP_X_EMS_DATE' => '20110909T233600Z', + 'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=' . $signature, + 'REQUEST_TIME' => $this->strtotime('20110909T233600Z'), + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => $httpHost, + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/', + 'HTTPS' => $https, + 'SERVER_PORT' => $serverPort, + 'SERVER_NAME' => $serverName, + ]; + $keyDB = ['AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY']; + $accessKeyId = $this->createEscher('us-east-1/iam/aws4_request') + ->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08'); + $this->assertEquals('AKIDEXAMPLE', $accessKeyId); + } + + public function validPortProvider() + { + return [ + 'default http port not provided' => [ + 'iam.amazonaws.com', + 'iam.amazonaws.com', + '80', + '', + 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd' + ], + 'default http port provided' => [ + 'iam.amazonaws.com:80', + 'iam.amazonaws.com', + '80', + '', + 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd' + ], + 'default https port not provided' => [ + 'iam.amazonaws.com', + 'iam.amazonaws.com', + '443', + 'on', + 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd' + ], + 'default https port provided' => [ + 'iam.amazonaws.com:443', + 'iam.amazonaws.com', + '443', + 'on', + 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd' + ], + 'custom http port' => [ + 'iam.amazonaws.com:123', + 'iam.amazonaws.com', + '123', + '', + '9584a4a527986bbbead79b56523d50e1c8161155933a644674d0b2f2a0bce19a' + ], + 'custom https port' => [ + 'iam.amazonaws.com:123', + 'iam.amazonaws.com', + '123', + 'on', + '9584a4a527986bbbead79b56523d50e1c8161155933a644674d0b2f2a0bce19a' + ], + 'default http port as custom https port' => [ + 'iam.amazonaws.com:80', + 'iam.amazonaws.com', + '80', + 'on', + 'b5daefdecb7124f47fafad18549e18a1a9c5accc4216a146c919d0635eccc370' + ], + 'default https port as custom http port' => [ + 'iam.amazonaws.com:443', + 'iam.amazonaws.com', + '443', + '', + 'b36c465c5a6bb79e6c6ac666e9c3847d5c997e035321429b7c25777ea86af35c' + ] + ]; + } + + /** + * @test + * @dataProvider requestTamperingProvider + * @param $tamperedKey + * @param $tamperedValue + * @param $expectedErrorMessage + * @param $expectedErrorCode + * @throws Exception + */ + public function itShouldFailToValidateInvalidRequests( + $tamperedKey, + $tamperedValue, + $expectedErrorMessage, + $expectedErrorCode + ) { + $serverVars = [ + 'HTTP_X_EMS_DATE' => '20110909T233600Z', + 'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'REQUEST_TIME' => $this->strtotime('20110909T233600Z'), + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'iam.amazonaws.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/', + 'HTTPS' => '', + 'SERVER_PORT' => '80', + 'SERVER_NAME' => 'iam.amazonaws.com', + ]; + + // replace server variable + $serverVars[$tamperedKey] = $tamperedValue; + + $keyDB = ['AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY']; + + try { + $this->createEscher('us-east-1/iam/aws4_request') + ->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08'); + $this->fail('Should fail to validate!'); + } catch (Exception $ex) { + $this->assertStringStartsWith($expectedErrorMessage, $ex->getMessage()); + $this->assertEquals($expectedErrorCode, $ex->getCode()); + } + } + + public function requestTamperingProvider() + { + return [ + 'wrong auth header' => [ + 'HTTP_X_EMS_AUTH', + 'Malformed auth header', + 'Could not parse auth header', + 2002 + ], + 'wrong date' => [ + 'HTTP_X_EMS_DATE', + 'INVALIDDATE', + 'Date header is invalid, the expected format is 20151104T092022Z', + 2004 + ], + 'invalid Escher key' => [ + 'HTTP_X_EMS_AUTH', + 'EMS-HMAC-SHA256 Credential=FOOBAR/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'Invalid Escher key', + 3001 + ], + 'wrong hash algo' => [ + 'HTTP_X_EMS_AUTH', + 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'Only SHA256 and SHA512 hash algorithms are allowed', + 3002 + ], + 'invalid credential' => [ + 'HTTP_X_EMS_AUTH', + 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-2/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'The credential scope is invalid', + 3003 + ], + 'host not signed' => [ + 'HTTP_X_EMS_AUTH', + 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'The host header is not signed', + 4001 + ], + 'date not signed' => [ + 'HTTP_X_EMS_AUTH', + 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + 'The x-ems-date header is not signed', + 4002 + ], + 'wrong request time' => [ + 'REQUEST_TIME', + '20110909T113600Z', + 'The request date is not within the accepted time range', + 5001 + ], + 'tampered signature' => [ + 'HTTP_X_EMS_AUTH', + 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'The signatures do not match', + 6001 + ], + ]; + } + + /** + * @test + * @throws Exception + */ + public function itShouldValidateRequestUsingQueryString() + { + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20110511T120000Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires=123456&X-EMS-SignedHeaders=host&X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67', + 'HTTPS' => '', + 'SERVER_PORT' => '80', + 'SERVER_NAME' => 'example.com', + ]; + $keyDB = ['th3K3y' => 'very_secure']; + + $accessKeyId = $this->createEscher('us-east-1/host/aws4_request')->authenticate($keyDB, $serverVars, ''); + $this->assertEquals('th3K3y', $accessKeyId); + } + + /** + * @test + * @throws Exception + */ + public function itShouldValidatePresignedUrlRequestWithSpecialCharacters() + { + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'service.example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host&X-EMS-Signature=661f2147c77b6784be5a60a8b842a96de6327653f1ed5d4305da43103c69a6f5', + 'HTTPS' => 'on', + 'SERVER_PORT' => '443', + 'SERVER_NAME' => 'service.example.com', + ]; + $keyDB = ['service_api_key' => 'service_secret']; + + $accessKeyId = $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); + $this->assertEquals('service_api_key', $accessKeyId); + } + + /** + * @test + */ + public function itShouldFailToValidateInvalidQueryStrings() + { + $this->expectException(Escher\Exception::class); + $this->expectExceptionMessage('The signatures do not match'); + $this->expectExceptionCode(6001); + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20110511T120000Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires=' . PHP_INT_MAX . '&X-EMS-SignedHeaders=host&X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67', + 'HTTPS' => '', + 'SERVER_PORT' => '80', + 'SERVER_NAME' => 'example.com', + ]; + + $keyDB = ['th3K3y' => 'very_secure']; + $this->createEscher('us-east-1/host/aws4_request')->authenticate($keyDB, $serverVars, ''); + } + + /** + * @test + * @throws Exception + */ + public function itShouldValidatePresignedUrlRequestWithUnindexedArray() + { + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'service.example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B%5D=1¶m1%5B%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host&X-EMS-Signature=ddb1e6479f28752c23a2a7f12fa54d3f21c4b36b8247e88e5992975a10ba616c', + 'HTTPS' => 'on', + 'SERVER_PORT' => '443', + 'SERVER_NAME' => 'service.example.com', + ]; + $keyDB = ['service_api_key' => 'service_secret']; + + $accessKeyId = $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); + $this->assertEquals('service_api_key', $accessKeyId); + } + + /** + * @test + * @throws Exception + */ + public function itShouldValidatePresignedUrlRequestWithIndexedArray() + { + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'service.example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B0%5D=1¶m1%5B1%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host&X-EMS-Signature=196bc22e36ea13d2bfe59c3fb42fbf67a09ec501a79924284d9281d7d8c773ce', + 'HTTPS' => 'on', + 'SERVER_PORT' => '443', + 'SERVER_NAME' => 'service.example.com', + ]; + $keyDB = ['service_api_key' => 'service_secret']; + + $accessKeyId = $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); + $this->assertEquals('service_api_key', $accessKeyId); + } + + /** + * @test + * @throws Exception + */ + public function itShouldValidatePresignedUrlIfSignatureIsTheFirstParam() + { + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'service.example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/login?X-EMS-Signature=196bc22e36ea13d2bfe59c3fb42fbf67a09ec501a79924284d9281d7d8c773ce&id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B0%5D=1¶m1%5B1%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host', + 'HTTPS' => 'on', + 'SERVER_PORT' => '443', + 'SERVER_NAME' => 'service.example.com', + ]; + $keyDB = ['service_api_key' => 'service_secret']; + + $accessKeyId = $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); + $this->assertEquals('service_api_key', $accessKeyId); + } + + /** + * @test + * @throws Exception + */ + public function itShouldValidatePresignedUrlIfSignatureIsInTheMiddleOfTheQueryString() + { + $serverVars = [ + 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => 'service.example.com', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&X-EMS-Signature=196bc22e36ea13d2bfe59c3fb42fbf67a09ec501a79924284d9281d7d8c773ce&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B0%5D=1¶m1%5B1%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host', + 'HTTPS' => 'on', + 'SERVER_PORT' => '443', + 'SERVER_NAME' => 'service.example.com', + ]; + $keyDB = ['service_api_key' => 'service_secret']; + + $accessKeyId = $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); + $this->assertEquals('service_api_key', $accessKeyId); + } + + /** + * @param $dateString + * @return string + * @throws Exception + */ + private function strtotime($dateString) + { + return Utils::parseLongDate($dateString)->format('U'); + } +} + diff --git a/test/unit/InternalTest.php b/test/Escher/Test/Unit/InternalTest.php similarity index 78% rename from test/unit/InternalTest.php rename to test/Escher/Test/Unit/InternalTest.php index d5176b6..1c9ed91 100644 --- a/test/unit/InternalTest.php +++ b/test/Escher/Test/Unit/InternalTest.php @@ -1,9 +1,13 @@ time(), 'REQUEST_METHOD' => 'GET', 'HTTP_HOST' => 'iam.amazonaws.com', 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', 'REQUEST_URI' => '/path?query=string' - ); + ]; $requestBody = 'BODY'; $helper = new RequestHelper($serverVars, $requestBody, 'Authorization', 'X-Ems-Date'); $this->assertEquals($requestBody, $helper->getRequestBody()); - $expectedHeaders = array( + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', 'host' => 'iam.amazonaws.com', - ); + ]; $this->assertEqualMaps($expectedHeaders, $helper->getHeaderList()); } @@ -53,28 +57,29 @@ public function itShouldCollectBodyAndHeadersFromServerVariables() */ public function itShouldParseAuthorizationHeader($authHeaderName, $dateHeaderName) { - $headerList = array( - 'host' => 'iam.amazonaws.com', + $headerList = [ + 'host' => 'iam.amazonaws.com', $dateHeaderName => '20110909T233600Z', $authHeaderName => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); + ]; $authHeader = AuthElements::parseFromHeaders($headerList, $authHeaderName, $dateHeaderName, 'EMS'); $this->assertEquals(new DateTime('20110909T233600Z', new DateTimeZone('GMT')), $authHeader->getDateTime()); $this->assertEquals('AKIDEXAMPLE', $authHeader->getAccessKeyId()); $this->assertEquals('20110909', $authHeader->getShortDate()); $this->assertEquals('us-east-1/iam/aws4_request', $authHeader->getCredentialScope()); - $this->assertEquals(array('content-type', 'host', 'x-ems-date'), $authHeader->getSignedHeaders()); - $this->assertEquals('f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', $authHeader->getSignature()); + $this->assertEquals(['content-type', 'host', 'x-ems-date'], $authHeader->getSignedHeaders()); + $this->assertEquals('f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + $authHeader->getSignature()); } public function headerNames() { - return array( - 'default' => array('authorization', 'date'), - 'upcase' => array('Authorization', 'Date'), - 'custom' => array('x-ems-auth', 'x-ems-date'), - 'custom upcase' => array('X-Ems-Auth', 'X-Ems-Date'), - ); + return [ + 'default' => ['authorization', 'date'], + 'upcase' => ['Authorization', 'Date'], + 'custom' => ['x-ems-auth', 'x-ems-date'], + 'custom upcase' => ['X-Ems-Auth', 'X-Ems-Date'], + ]; } } diff --git a/test/Escher/Test/Unit/RequestCanonicalizerTest.php b/test/Escher/Test/Unit/RequestCanonicalizerTest.php new file mode 100644 index 0000000..fc790e6 --- /dev/null +++ b/test/Escher/Test/Unit/RequestCanonicalizerTest.php @@ -0,0 +1,20 @@ +assertEquals($query, $result); + } +} diff --git a/test/unit/SignRequestUsingHeaderTest.php b/test/Escher/Test/Unit/SignRequestUsingHeaderTest.php similarity index 68% rename from test/unit/SignRequestUsingHeaderTest.php rename to test/Escher/Test/Unit/SignRequestUsingHeaderTest.php index 6874f78..a1b2a48 100644 --- a/test/unit/SignRequestUsingHeaderTest.php +++ b/test/Escher/Test/Unit/SignRequestUsingHeaderTest.php @@ -1,5 +1,11 @@ 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', - ); - $expectedHeaders = array( + 'host' => 'iam.amazonaws.com', + ]; + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', + 'host' => 'iam.amazonaws.com', 'x-ems-date' => '20110909T233600Z', 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); - $headersToSign = array('content-type', 'host', 'x-ems-date'); + ]; + $headersToSign = ['content-type', 'host', 'x-ems-date']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -32,22 +39,23 @@ public function itShouldSignRequest() */ public function itShouldSignRequestWithUppercaseHeader() { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', - 'TEST' => 'TEST message' - ); - $expectedHeaders = array( + 'host' => 'iam.amazonaws.com', + 'TEST' => 'TEST message' + ]; + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', + 'host' => 'iam.amazonaws.com', 'x-ems-date' => '20110909T233600Z', 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;test;x-ems-date, Signature=f6ae6c5a72056a6f9ad42a9bbfebb868243b4fe451c38b2817739f75c197d26f', - 'test' => 'TEST message', - ); - $headersToSign = array('content-type', 'host', 'x-ems-date', 'TEST'); + 'test' => 'TEST message', + ]; + $headersToSign = ['content-type', 'host', 'x-ems-date', 'TEST']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -58,19 +66,20 @@ public function itShouldSignRequestWithUppercaseHeader() */ public function itShouldAutomagicallyAddHostHeader() { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - ); - $expectedHeaders = array( + ]; + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', + 'host' => 'iam.amazonaws.com', 'x-ems-date' => '20110909T233600Z', 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); - $headersToSign = array('content-type', 'host', 'x-ems-date'); + ]; + $headersToSign = ['content-type', 'host', 'x-ems-date']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -82,10 +91,10 @@ public function itShouldAutomagicallyAddHostHeader() */ public function itShouldAutomagicallyAddHostHeaderWithPort($url, $expectedHost) { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - ); - $headersToSign = array('content-type', 'host', 'x-ems-date'); + ]; + $headersToSign = ['content-type', 'host', 'x-ems-date']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', 'POST', $url, 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() @@ -95,16 +104,16 @@ public function itShouldAutomagicallyAddHostHeaderWithPort($url, $expectedHost) public function urlAndHostProvider() { - return array( - 'http - custom port' => array('http://iam.amazonaws.com:5000/', 'iam.amazonaws.com:5000'), - 'https - custom port' => array('https://iam.amazonaws.com:5000/', 'iam.amazonaws.com:5000'), + return [ + 'http - custom port' => ['http://iam.amazonaws.com:5000/', 'iam.amazonaws.com:5000'], + 'https - custom port' => ['https://iam.amazonaws.com:5000/', 'iam.amazonaws.com:5000'], - 'http - default port' => array('http://iam.amazonaws.com:80/', 'iam.amazonaws.com'), - 'https - default port' => array('https://iam.amazonaws.com:443/', 'iam.amazonaws.com'), + 'http - default port' => ['http://iam.amazonaws.com:80/', 'iam.amazonaws.com'], + 'https - default port' => ['https://iam.amazonaws.com:443/', 'iam.amazonaws.com'], - 'http - https port' => array('http://iam.amazonaws.com:443/', 'iam.amazonaws.com:443'), - 'https - http port' => array('https://iam.amazonaws.com:80/', 'iam.amazonaws.com:80') - ); + 'http - https port' => ['http://iam.amazonaws.com:443/', 'iam.amazonaws.com:443'], + 'https - http port' => ['https://iam.amazonaws.com:80/', 'iam.amazonaws.com:80'] + ]; } /** @@ -113,19 +122,20 @@ public function urlAndHostProvider() */ public function itShouldAutomagicallyAddDateAndHostToSignedHeaders() { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - ); - $expectedHeaders = array( + ]; + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', + 'host' => 'iam.amazonaws.com', 'x-ems-date' => '20110909T233600Z', 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); - $headersToSign = array('content-type'); + ]; + $headersToSign = ['content-type']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -136,22 +146,23 @@ public function itShouldAutomagicallyAddDateAndHostToSignedHeaders() */ public function itShouldOnlySignHeadersExplicitlySetToBeSigned() { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', 'x-a-header' => 'that/should/not/be/signed', - ); - $expectedHeaders = array( + ]; + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', + 'host' => 'iam.amazonaws.com', 'x-a-header' => 'that/should/not/be/signed', 'x-ems-date' => '20110909T233600Z', 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); + ]; - $headersToSign = array('content-type', 'host', 'x-ems-date'); + $headersToSign = ['content-type', 'host', 'x-ems-date']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -162,20 +173,21 @@ public function itShouldOnlySignHeadersExplicitlySetToBeSigned() */ public function itShouldUseTheProvidedAuthHeaderName() { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - ); - $expectedHeaders = array( - 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', - 'x-ems-date' => '20110909T233600Z', + ]; + $expectedHeaders = [ + 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', + 'host' => 'iam.amazonaws.com', + 'x-ems-date' => '20110909T233600Z', 'custom-auth-header' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); + ]; - $headersToSign = array('content-type', 'host', 'x-ems-date'); + $headersToSign = ['content-type', 'host', 'x-ems-date']; $actualHeaders = $this->createEscher('us-east-1/iam/aws4_request')->setAuthHeaderKey('Custom-Auth-Header')->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -186,21 +198,22 @@ public function itShouldUseTheProvidedAuthHeaderName() */ public function itShouldUseTheProvidedAlgoPrefix() { - $inputHeaders = array( + $inputHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - ); - $expectedHeaders = array( + ]; + $expectedHeaders = [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8', - 'host' => 'iam.amazonaws.com', - 'x-ems-date' => '20110909T233600Z', - 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - ); + 'host' => 'iam.amazonaws.com', + 'x-ems-date' => '20110909T233600Z', + 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', + ]; $escher = $this->createEscher('us-east-1/iam/aws4_request'); - $headersToSign = array('content-type', 'host', 'x-ems-date'); + $headersToSign = ['content-type', 'host', 'x-ems-date']; $actualHeaders = $escher->signRequest( 'AKIDEXAMPLE', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', - 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, $this->getDate() + 'POST', 'http://iam.amazonaws.com/', 'Action=ListUsers&Version=2010-05-08', $inputHeaders, $headersToSign, + $this->getDate() ); $this->assertEqualMaps($expectedHeaders, $actualHeaders); } @@ -211,33 +224,30 @@ public function itShouldUseTheProvidedAlgoPrefix() */ public function itShouldGenerateSignedHeaders() { - $inputHeaders = array( + $inputHeaders = [ 'Some-Custom-Header' => 'FooBar' - ); + ]; $date = new DateTime('2011/05/11 12:00:00', new DateTimeZone("UTC")); $escher = $this->createEscher('us-east-1/host/aws4_request'); $actualHeaders = $escher->signRequest( 'th3K3y', 'very_secure', - 'GET', 'http://example.com/something', '', $inputHeaders, array(), $date + 'GET', 'http://example.com/something', '', $inputHeaders, [], $date ); - $expectedHeaders = array( + $expectedHeaders = [ 'host' => 'example.com', 'some-custom-header' => 'FooBar', 'x-ems-date' => '20110511T120000Z', 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=th3K3y/20110511/us-east-1/host/aws4_request, SignedHeaders=host;x-ems-date, Signature=e7c1c7b2616d27ecbe3cd81ed3464ea4f6e2a11ad6f7792b23d67f7867e9abb4', - ); + ]; $this->assertEqualMaps($expectedHeaders, $actualHeaders); } - /** - * @return DateTime - */ - protected function getDate() + protected function getDate(): DateTime { - return new DateTime('20110909T233600Z', new DateTimeZone("UTC")); + return new DateTime('20110909T233600Z', new DateTimeZone('UTC')); } } diff --git a/test/unit/SignRequestUsingQueryStringTest.php b/test/Escher/Test/Unit/SignRequestUsingQueryStringTest.php similarity index 71% rename from test/unit/SignRequestUsingQueryStringTest.php rename to test/Escher/Test/Unit/SignRequestUsingQueryStringTest.php index 0718686..c06d67c 100644 --- a/test/unit/SignRequestUsingQueryStringTest.php +++ b/test/Escher/Test/Unit/SignRequestUsingQueryStringTest.php @@ -1,5 +1,11 @@ createEscher()->presignUrl('th3K3y', 'very_secure', 'http://example.com/something?foo=bar&baz=barbaz', $this->expires, $this->getDate()); + $signedUrl = $this->createEscher()->presignUrl('th3K3y', 'very_secure', + 'http://example.com/something?foo=bar&baz=barbaz', $this->expires, $this->getDate()); $expectedSignedUrl = 'http://example.com/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires=123456&X-EMS-SignedHeaders=host&X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'; @@ -21,25 +28,14 @@ public function itShouldGenerateSignedUrl() */ public function itShouldHandlePort() { - $signedUrl = $this->createEscher()->presignUrl('th3K3y', 'very_secure', 'http://example.com:5000/something?foo=bar&baz=barbaz', $this->expires, $this->getDate()); + $signedUrl = $this->createEscher()->presignUrl('th3K3y', 'very_secure', + 'http://example.com:5000/something?foo=bar&baz=barbaz', $this->expires, $this->getDate()); $expectedSignedUrl = 'http://example.com:5000/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires=123456&X-EMS-SignedHeaders=host&X-EMS-Signature=7f7032b393945a0167fe65d35a7e2827a781ecab9019d814adf95c23bfa5e458'; $this->assertEquals($expectedSignedUrl, $signedUrl); } - /** - * @test - */ - public function itShouldRespectWhenUrlHasLocationHash() - { - $signedUrl = $this->createEscher()->presignUrl('th3K3y', 'very_secure', 'http://example.com:5000/something?foo=bar&baz=barbaz#/client_fragment', $this->expires, $this->getDate()); - - $expectedSignedUrl = 'http://example.com:5000/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires=123456&X-EMS-SignedHeaders=host&X-EMS-Signature=7f7032b393945a0167fe65d35a7e2827a781ecab9019d814adf95c23bfa5e458#/client_fragment'; - - $this->assertEquals($expectedSignedUrl, $signedUrl); - } - /** * @test */ diff --git a/test/unit/SigningProcessTest.php b/test/Escher/Test/Unit/SigningProcessTest.php similarity index 90% rename from test/unit/SigningProcessTest.php rename to test/Escher/Test/Unit/SigningProcessTest.php index 9ae67fd..9c2f057 100644 --- a/test/unit/SigningProcessTest.php +++ b/test/Escher/Test/Unit/SigningProcessTest.php @@ -1,14 +1,17 @@ $this->amazonFixtures, 'emarsys' => $this->emarsysFixtures) as $suiteName => $suiteFixtures) { + $fixtures = []; + foreach(['aws4' => $this->amazonFixtures, 'emarsys' => $this->emarsysFixtures] as $suiteName => $suiteFixtures) { foreach ($suiteFixtures as $fixtureName) { $inputFixture = $this->fixture($suiteName, $fixtureName, $input); $outputFixture = $this->fixture($suiteName, $fixtureName, $output); - $fixtures["$suiteName : $fixtureName"] = array($inputFixture, $outputFixture); + $fixtures["$suiteName : $fixtureName"] = [$inputFixture, $outputFixture]; } } return $fixtures; @@ -123,7 +126,7 @@ public function headerFileList() public function itShouldCalculateCanonicalRequest($rawRequest, $canonicalRequestString) { list($method, $requestUri, $body, $headerLines) = $this->parseRawRequest($rawRequest); - $headersToSign = array(); + $headersToSign = []; foreach ($headerLines as $headerLine) { if ("\t" != $headerLine[0] && false !== strpos($headerLine, ':')) { list ($headerKey) = explode(':', $headerLine, 2); @@ -151,12 +154,12 @@ private function parseRawRequest($content) $rows = explode("\n", $content); list($method, $requestUri) = explode(' ', $rows[0]); - return array( + return [ $method, $requestUri, $rows[count($rows) - 1], array_slice($rows, 1, -2), - ); + ]; } private function hex2bin($hexstr) @@ -180,6 +183,6 @@ private function hex2bin($hexstr) private function fixture($suiteName, $fixtureName, $extension) { - return file_get_contents(dirname(__FILE__) . "/../fixtures/{$suiteName}_testsuite/{$fixtureName}.{$extension}"); + return file_get_contents(__DIR__ . "/../../../fixtures/{$suiteName}_testsuite/{$fixtureName}.{$extension}"); } } diff --git a/test/bootstrap.php b/test/bootstrap.php deleted file mode 100644 index 3afa675..0000000 --- a/test/bootstrap.php +++ /dev/null @@ -1,3 +0,0 @@ - - - - - unit - - - - - - - - - - ../src - - - diff --git a/test/unit/AuthenticateRequestTest.php b/test/unit/AuthenticateRequestTest.php deleted file mode 100644 index 91a4bd3..0000000 --- a/test/unit/AuthenticateRequestTest.php +++ /dev/null @@ -1,284 +0,0 @@ - '20110909T233600Z', - 'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - 'REQUEST_TIME' => $this->strtotime('20110909T233600Z'), - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => 'iam.amazonaws.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/', - 'HTTPS' => '', - 'SERVER_PORT' => '80', - 'SERVER_NAME' => 'iam.amazonaws.com', - ); - $keyDB = array('AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'); - $accessKeyId = $this->createEscher('us-east-1/iam/aws4_request') - ->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08'); - $this->assertEquals('AKIDEXAMPLE', $accessKeyId); - } - - /** - * @test - * @dataProvider validPortProvider - * @param $httpHost - * @param $serverName - * @param $serverPort - * @param $https - * @param $signature - * @throws Exception - */ - public function itShouldAuthenticateRequestRegardlessDefaultPortProvidedOrNot($httpHost, $serverName, $serverPort, $https, $signature) - { - $serverVars = array( - 'HTTP_X_EMS_DATE' => '20110909T233600Z', - 'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=' . $signature, - 'REQUEST_TIME' => $this->strtotime('20110909T233600Z'), - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => $httpHost, - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/', - 'HTTPS' => $https, - 'SERVER_PORT' => $serverPort, - 'SERVER_NAME' => $serverName, - ); - $keyDB = array('AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'); - $accessKeyId = $this->createEscher('us-east-1/iam/aws4_request') - ->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08'); - $this->assertEquals('AKIDEXAMPLE', $accessKeyId); - } - - public function validPortProvider() - { - return array ( - 'default http port not provided' => array('iam.amazonaws.com', 'iam.amazonaws.com', '80', '', 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd'), - 'default http port provided' => array('iam.amazonaws.com:80', 'iam.amazonaws.com', '80', '', 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd'), - 'default https port not provided' => array('iam.amazonaws.com', 'iam.amazonaws.com', '443', 'on', 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd'), - 'default https port provided' => array('iam.amazonaws.com:443', 'iam.amazonaws.com', '443', 'on', 'f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd'), - 'custom http port' => array('iam.amazonaws.com:123', 'iam.amazonaws.com', '123', '', '9584a4a527986bbbead79b56523d50e1c8161155933a644674d0b2f2a0bce19a'), - 'custom https port' => array('iam.amazonaws.com:123', 'iam.amazonaws.com', '123', 'on', '9584a4a527986bbbead79b56523d50e1c8161155933a644674d0b2f2a0bce19a'), - 'default http port as custom https port' => array('iam.amazonaws.com:80', 'iam.amazonaws.com', '80', 'on', 'b5daefdecb7124f47fafad18549e18a1a9c5accc4216a146c919d0635eccc370'), - 'default https port as custom http port' => array('iam.amazonaws.com:443', 'iam.amazonaws.com', '443', '', 'b36c465c5a6bb79e6c6ac666e9c3847d5c997e035321429b7c25777ea86af35c') - ); - } - - /** - * @test - * @dataProvider requestTamperingProvider - * @param $tamperedKey - * @param $tamperedValue - * @param $expectedErrorMessage - * @param $expectedErrorCode - * @throws Exception - */ - public function itShouldFailToValidateInvalidRequests($tamperedKey, $tamperedValue, $expectedErrorMessage, $expectedErrorCode) - { - $serverVars = array( - 'HTTP_X_EMS_DATE' => '20110909T233600Z', - 'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', - 'REQUEST_TIME' => $this->strtotime('20110909T233600Z'), - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => 'iam.amazonaws.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/', - 'HTTPS' => '', - 'SERVER_PORT' => '80', - 'SERVER_NAME' => 'iam.amazonaws.com', - ); - - // replace server variable - $serverVars[$tamperedKey] = $tamperedValue; - - $keyDB = array('AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'); - - try { - $this->createEscher('us-east-1/iam/aws4_request') - ->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08'); - $this->fail('Should fail to validate!'); - } catch (Exception $ex) { - $this->assertStringStartsWith($expectedErrorMessage, $ex->getMessage()); - $this->assertEquals($expectedErrorCode, $ex->getCode()); - } - } - - public function requestTamperingProvider() - { - return array( - 'wrong auth header' => array('HTTP_X_EMS_AUTH', 'Malformed auth header', 'Auth header format is invalid', 2002), - 'wrong date' => array('HTTP_X_EMS_DATE', 'INVALIDDATE', 'Date header is invalid, the expected format is 20151104T092022Z', 2004), - 'invalid Escher key' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=FOOBAR/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Invalid Escher key', 3001), - 'wrong hash algo' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Hash algorithm is invalid. Only SHA256 and SHA512 are allowed', 3002), - 'invalid credential' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-2/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Credential scope is invalid', 3003), - 'host not signed' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'The host header is not signed', 4001), - 'date not signed' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'The x-ems-date header is not signed', 4002), - 'wrong request time' => array('REQUEST_TIME', '20110909T113600Z', 'The request date is not within the accepted time range', 5001), - 'tampered signature' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'The signatures do not match', 6001), - ); - } - - /** - * @test - * @throws Exception - */ - public function itShouldValidateRequestUsingQueryString() - { - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20110511T120000Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires=123456&X-EMS-SignedHeaders=host&X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67', - 'HTTPS' => '', - 'SERVER_PORT' => '80', - 'SERVER_NAME' => 'example.com', - ); - $keyDB = array('th3K3y' => 'very_secure'); - $this->createEscher('us-east-1/host/aws4_request')->authenticate($keyDB, $serverVars, ''); - } - - /** - * @test - * @throws Exception - */ - public function itShouldValidatePresignedUrlRequestWithSpecialCharacters() - { - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'service.example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host&X-EMS-Signature=661f2147c77b6784be5a60a8b842a96de6327653f1ed5d4305da43103c69a6f5', - 'HTTPS' => 'on', - 'SERVER_PORT' => '443', - 'SERVER_NAME' => 'service.example.com', - ); - $keyDB = array('service_api_key' => 'service_secret'); - $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); - } - - /** - * @test - */ - public function itShouldFailToValidateInvalidQueryStrings() - { - $this->expectException(Escher\Exception::class); - $this->expectExceptionMessage('The signatures do not match'); - $this->expectExceptionCode(6001); - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20110511T120000Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/something?foo=bar&baz=barbaz&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&X-EMS-Date=20110511T120000Z&X-EMS-Expires='.PHP_INT_MAX.'&X-EMS-SignedHeaders=host&X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67', - 'HTTPS' => '', - 'SERVER_PORT' => '80', - 'SERVER_NAME' => 'example.com', - ); - - $keyDB = array('th3K3y' => 'very_secure'); - $this->createEscher('us-east-1/host/aws4_request')->authenticate($keyDB, $serverVars, ''); - } - - /** - * @test - * @throws Exception - */ - public function itShouldValidatePresignedUrlRequestWithUnindexedArray() - { - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'service.example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B%5D=1¶m1%5B%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host&X-EMS-Signature=ddb1e6479f28752c23a2a7f12fa54d3f21c4b36b8247e88e5992975a10ba616c', - 'HTTPS' => 'on', - 'SERVER_PORT' => '443', - 'SERVER_NAME' => 'service.example.com', - ); - $keyDB = array('service_api_key' => 'service_secret'); - $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); - } - - /** - * @test - * @throws Exception - */ - public function itShouldValidatePresignedUrlRequestWithIndexedArray() - { - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'service.example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B0%5D=1¶m1%5B1%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host&X-EMS-Signature=196bc22e36ea13d2bfe59c3fb42fbf67a09ec501a79924284d9281d7d8c773ce', - 'HTTPS' => 'on', - 'SERVER_PORT' => '443', - 'SERVER_NAME' => 'service.example.com', - ); - $keyDB = array('service_api_key' => 'service_secret'); - $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); - } - - /** - * @test - * @throws Exception - */ - public function itShouldValidatePresignedUrlIfSignatureIsTheFirstParam() - { - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'service.example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/login?X-EMS-Signature=196bc22e36ea13d2bfe59c3fb42fbf67a09ec501a79924284d9281d7d8c773ce&id=12345678&domain=login.example.com&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B0%5D=1¶m1%5B1%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host', - 'HTTPS' => 'on', - 'SERVER_PORT' => '443', - 'SERVER_NAME' => 'service.example.com', - ); - $keyDB = array('service_api_key' => 'service_secret'); - $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); - } - - /** - * @test - * @throws Exception - */ - public function itShouldValidatePresignedUrlIfSignatureIsInTheMiddleOfTheQueryString() - { - $serverVars = array( - 'REQUEST_TIME' => $this->strtotime('20150310T173248Z'), - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'service.example.com', - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'REQUEST_URI' => '/login?id=12345678&domain=login.example.com&X-EMS-Signature=196bc22e36ea13d2bfe59c3fb42fbf67a09ec501a79924284d9281d7d8c773ce&redirect_to=https%3A%2F%2Fhome.dev%2Fbootstrap.php%3Fr%3Dservice%2Findex%26service%3Dservice_name¶m1%5B0%5D=1¶m1%5B1%5D=2%3F&X-EMS-Algorithm=EMS-HMAC-SHA256&X-EMS-Credentials=service_api_key%2F20150310%2Feu%2Fservice%2Fems_request&X-EMS-Date=20150310T173248Z&X-EMS-Expires=86400&X-EMS-SignedHeaders=host', - 'HTTPS' => 'on', - 'SERVER_PORT' => '443', - 'SERVER_NAME' => 'service.example.com', - ); - $keyDB = array('service_api_key' => 'service_secret'); - $this->createEscher('eu/service/ems_request')->authenticate($keyDB, $serverVars); - } - - /** - * @param $dateString - * @return string - * @throws Exception - */ - private function strtotime($dateString) - { - return Utils::parseLongDate($dateString)->format('U'); - } -} - diff --git a/test/unit/RequestCanonicalizerTest.php b/test/unit/RequestCanonicalizerTest.php deleted file mode 100644 index 4c1ed58..0000000 --- a/test/unit/RequestCanonicalizerTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertEquals($query, $result); - } - - /** - * @test - */ - public function urlEncodeQueryStringShouldReplacePlusSignWithSplace() - { - $query = "email=test%2Bbayxd%40gmail.com"; - $expected = "email=test%20bayxd%40gmail.com"; - $result = RequestCanonicalizer::urlEncodeQueryString($query, "Content-Type: application/x-www-form-urlencoded"); - $this->assertEquals($expected, $result); - } -} \ No newline at end of file