From 747236b5ae1f191576292b6d96d19a6707e27e37 Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Fri, 21 Mar 2025 20:10:19 +0100 Subject: [PATCH] php8.4 support among other improvements --- .gitattributes | 16 +++++------ .github/workflows/main.yaml | 45 ++++++++++++++---------------- .gitignore | 2 ++ .php-cs-fixer.php | 10 +++++++ .phpstan.neon | 6 ++++ CHANGELOG.md | 5 ++++ LICENSE | 2 +- composer.json | 16 +++++------ phpcs.xml | 16 +++++++++++ phpunit.xml | 33 ++++++++++++++++++++++ src/BasicAuthentication.php | 19 +++++++++---- src/DigestAuthentication.php | 5 ++++ src/HttpAuthentication.php | 8 +++--- tests/BasicAuthenticationTest.php | 39 ++++++++++++++++++++++---- tests/DigestAuthenticationTest.php | 11 +++++--- 15 files changed, 173 insertions(+), 60 deletions(-) create mode 100644 .php-cs-fixer.php create mode 100644 .phpstan.neon create mode 100644 phpcs.xml create mode 100644 phpunit.xml diff --git a/.gitattributes b/.gitattributes index c540470..b67c7ca 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,10 @@ * text=auto eol=lf -/tests export-ignore -.editorconfig export-ignore -.gitattributes export-ignore -.gitignore export-ignore -.php_cs export-ignore -.travis.yml export-ignore -phpcs.xml.dist export-ignore -phpunit.xml.dist export-ignore +/tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.php-cs-fixer.php export-ignore +phpcs.xml.dist export-ignore +phpunit.xml.dist export-ignore +.phpstan.neon export-ignore diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3f37f93..5d14a59 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,10 +1,10 @@ name: "testing" on: - push: - branches: [ master ] - pull_request: - branches: [ master ] + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: qa: @@ -13,19 +13,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Validate composer.json and composer.lock run: composer validate - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' @@ -40,19 +40,19 @@ jobs: strategy: matrix: - php: - - 7.2 - - 7.3 - - 7.4 - - 8.0 - - 8.1 - - 8.2 - - 8.3 - fail-fast: false + php: + - 7.2 + - 7.3 + - 7.4 + - 8.0 + - 8.1 + - 8.2 + - 8.3 + - 8.4 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 @@ -60,7 +60,7 @@ jobs: php-version: ${{ matrix.php }} - name: Cache PHP dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} @@ -71,6 +71,3 @@ jobs: - name: Tests run: composer test - - - name: Tests coverage - run: composer coverage diff --git a/.gitignore b/.gitignore index 364d1a4..5ae8693 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ vendor composer.lock coverage *.cache +.idea +kit diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..90b12a3 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,10 @@ +setFinder( + PhpCsFixer\Finder::create() + ->files() + ->name('*.php') + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ); \ No newline at end of file diff --git a/.phpstan.neon b/.phpstan.neon new file mode 100644 index 0000000..a961aee --- /dev/null +++ b/.phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 8 + paths: + - src + - tests + treatPhpDocTypesAsCertain: false diff --git a/CHANGELOG.md b/CHANGELOG.md index dba983d..c091927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2.2.0] - 2025-03-21 +### Added +- Support for PHP 8.4 + ## [2.1.2] - 2024-01-12 ### Fixed - Updated dependencies. @@ -72,6 +76,7 @@ First version [#2]: https://github.com/middlewares/http-authentication/issues/2 +[2.2.0]: https://github.com/middlewares/http-authentication/compare/v2.1.2...v2.2.0 [2.1.2]: https://github.com/middlewares/http-authentication/compare/v2.1.1...v2.1.2 [2.1.1]: https://github.com/middlewares/http-authentication/compare/v2.1.0...v2.1.1 [2.1.0]: https://github.com/middlewares/http-authentication/compare/v2.0.0...v2.1.0 diff --git a/LICENSE b/LICENSE index 017c0cd..374fb13 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 +Copyright (c) 2019-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/composer.json b/composer.json index a7243a9..5a1471a 100644 --- a/composer.json +++ b/composer.json @@ -19,16 +19,16 @@ }, "require": { "php": "^7.2 || ^8.0", - "middlewares/utils": "^3.0 || ^4.0", - "psr/http-server-middleware": "^1.0" + "middlewares/utils": "^2 || ^3 || ^4", + "psr/http-server-middleware": "^1" }, "require-dev": { "phpunit/phpunit": "^8 || ^9", - "laminas/laminas-diactoros": "^2.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^2.0", - "squizlabs/php_codesniffer": "^3.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12" + "laminas/laminas-diactoros": "^2 || ^3", + "friendsofphp/php-cs-fixer": "^3", + "squizlabs/php_codesniffer": "^3", + "oscarotero/php-cs-fixer-config": "^2", + "phpstan/phpstan": "^1 || ^2" }, "autoload": { "psr-4": { @@ -48,4 +48,4 @@ "coverage": "phpunit --coverage-text", "coverage-html": "phpunit --coverage-html=coverage" } -} +} \ No newline at end of file diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..7814331 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,16 @@ + + + Middlewares coding standard + + + + + + + + + + + src + tests + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..eb266b8 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,33 @@ + + + + + tests + + + + + + ./src + + ./tests + ./vendor + + + + diff --git a/src/BasicAuthentication.php b/src/BasicAuthentication.php index 00583e1..d5b26b1 100644 --- a/src/BasicAuthentication.php +++ b/src/BasicAuthentication.php @@ -10,6 +10,7 @@ class BasicAuthentication extends HttpAuthentication implements MiddlewareInterface { + /** @var bool */ private $verifyHash = false; /** @@ -31,7 +32,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } - public function verifyHash($verifyHash = true): self + public function verifyHash(bool $verifyHash = true): self { $this->verifyHash = $verifyHash; @@ -50,8 +51,8 @@ private function login(ServerRequestInterface $request): ?string return null; } - //Check the user - if (!isset($this->users[$authorization['username']])) { + //Check the user and password + if (!isset($this->users[$authorization['username']]) || !isset($authorization['password'])) { return null; } @@ -68,6 +69,8 @@ private function login(ServerRequestInterface $request): ?string /** * Parses the authorization header for a basic authentication. + * + * @return ?array */ private function parseHeader(string $header): ?array { @@ -75,7 +78,13 @@ private function parseHeader(string $header): ?array return null; } - $header = base64_decode(substr($header, 6)); + $userAndPassword = substr($header, 6); + if (!$userAndPassword) { + return null; + } + + /** @var string|false $header */ + $header = base64_decode($userAndPassword); if ($header === false) { return null; @@ -85,7 +94,7 @@ private function parseHeader(string $header): ?array return [ 'username' => $header[0], - 'password' => isset($header[1]) ? $header[1] : null, + 'password' => $header[1], ]; } } diff --git a/src/DigestAuthentication.php b/src/DigestAuthentication.php index 15b28c3..eb66771 100644 --- a/src/DigestAuthentication.php +++ b/src/DigestAuthentication.php @@ -78,6 +78,8 @@ private function login(ServerRequestInterface $request): ?string /** * Validates the authorization. + * + * @param array $authorization */ private function isValid(array $authorization, string $method, string $password): bool { @@ -96,6 +98,8 @@ private function isValid(array $authorization, string $method, string $password) /** * Parses the authorization header for a basic authentication. + * + * @return array|null $header */ private function parseHeader(string $header): ?array { @@ -120,6 +124,7 @@ private function parseHeader(string $header): ?array if ($matches) { foreach ($matches as $m) { + // @phpstan-ignore-next-line $data[$m[1]] = $m[3] ?: $m[4]; unset($needed_parts[$m[1]]); } diff --git a/src/HttpAuthentication.php b/src/HttpAuthentication.php index df6102e..dda4f1f 100644 --- a/src/HttpAuthentication.php +++ b/src/HttpAuthentication.php @@ -11,7 +11,7 @@ abstract class HttpAuthentication { /** - * @var array|ArrayAccess The available users + * @var array|ArrayAccess The available users */ protected $users; @@ -31,11 +31,11 @@ abstract class HttpAuthentication protected $responseFactory; /** - * @param array|ArrayAccess $users [username => password] + * @param array|ArrayAccess $users [username => password] */ - public function __construct($users, ResponseFactoryInterface $responseFactory = null) + public function __construct($users, ?ResponseFactoryInterface $responseFactory = null) { - if (!is_array($users) && !($users instanceof ArrayAccess)) { + if (!is_array($users) && !$users instanceof ArrayAccess) { throw new InvalidArgumentException( 'The users argument must be an array or implement the ArrayAccess interface' ); diff --git a/tests/BasicAuthenticationTest.php b/tests/BasicAuthenticationTest.php index f4d4f4c..e0ec5b5 100644 --- a/tests/BasicAuthenticationTest.php +++ b/tests/BasicAuthenticationTest.php @@ -11,16 +11,17 @@ class BasicAuthenticationTest extends TestCase { - public function testException() + public function testException(): void { $this->expectException(InvalidArgumentException::class); $response = Dispatcher::run([ + // @phpstan-ignore-next-line new BasicAuthentication('foo'), ]); } - public function testUserDoesNotExists() + public function testUserDoesNotExists(): void { $response = Dispatcher::run( [ @@ -33,7 +34,33 @@ public function testUserDoesNotExists() $this->assertSame(401, $response->getStatusCode()); } - public function testInvalidPassword() + public function testEmptyUserAndPassword(): void + { + $response = Dispatcher::run( + [ + (new BasicAuthentication(['user' => 'pass']))->realm('My realm'), + ], + Factory::createServerRequest('GET', '/') + ->withHeader('Authorization', 'Basic ') + ); + + $this->assertSame(401, $response->getStatusCode()); + } + + public function testPasswordNotProvided(): void + { + $response = Dispatcher::run( + [ + (new BasicAuthentication(['user' => 'pass']))->realm('My realm'), + ], + Factory::createServerRequest('GET', '/') + ->withHeader('Authorization', 'Basic '.base64_encode('invalid-user:')) + ); + + $this->assertSame(401, $response->getStatusCode()); + } + + public function testInvalidPassword(): void { $response = Dispatcher::run( [ @@ -46,7 +73,7 @@ public function testInvalidPassword() $this->assertSame(401, $response->getStatusCode()); } - public function testError() + public function testError(): void { $response = Dispatcher::run([ (new BasicAuthentication(['user' => 'pass']))->realm('My realm'), @@ -56,7 +83,7 @@ public function testError() $this->assertSame('Basic realm="My realm"', $response->getHeaderLine('WWW-Authenticate')); } - public function testSuccess() + public function testSuccess(): void { $request = Factory::createServerRequest('GET', '/') ->withHeader('Authorization', 'Basic '.base64_encode('user:pass')); @@ -75,7 +102,7 @@ function ($request) { $this->assertSame('user', (string) $response->getBody()); } - public function testHashSuccess() + public function testHashSuccess(): void { $request = Factory::createServerRequest('GET', '/') ->withHeader('Authorization', 'Basic '.base64_encode('user:rasmuslerdorf')); diff --git a/tests/DigestAuthenticationTest.php b/tests/DigestAuthenticationTest.php index 9834a55..ca0f817 100644 --- a/tests/DigestAuthenticationTest.php +++ b/tests/DigestAuthenticationTest.php @@ -10,9 +10,10 @@ class DigestAuthenticationTest extends TestCase { - public function testError() + public function testError(): void { $response = Dispatcher::run([ + // @phpstan-ignore-next-line (new DigestAuthentication(['user' => 'pass']))->realm('My realm')->nonce('xxx'), ]); @@ -23,10 +24,11 @@ public function testError() ); } - public function testUserDoesNotExists() + public function testUserDoesNotExists(): void { $response = Dispatcher::run( [ + // @phpstan-ignore-next-line (new DigestAuthentication(['user' => 'pass']))->realm('My realm')->nonce('xxx'), ], Factory::createServerRequest('GET', '/') @@ -36,10 +38,11 @@ public function testUserDoesNotExists() $this->assertSame(401, $response->getStatusCode()); } - public function testInvalidPassword() + public function testInvalidPassword(): void { $response = Dispatcher::run( [ + // @phpstan-ignore-next-line (new DigestAuthentication(['user' => 'pass']))->realm('My realm')->nonce('xxx'), ], Factory::createServerRequest('GET', '/') @@ -49,7 +52,7 @@ public function testInvalidPassword() $this->assertSame(401, $response->getStatusCode()); } - public function testSuccess() + public function testSuccess(): void { $nonce = uniqid(); $request = Factory::createServerRequest('GET', '/')