diff --git a/.editorconfig b/.editorconfig index cd8eb86..7d38cf2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,6 @@ trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 02f91de..47fafe7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,43 +1,48 @@ name: Run tests -on: [push, pull_request] +on: + push: + pull_request: jobs: - php-tests: - runs-on: ubuntu-latest - - strategy: - matrix: - include: - - php: 8.2 - illuminate: ^10.0 - - php: 8.1 - illuminate: ^9.0 - - php: 8.1 - illuminate: ^8.0 - - php: 8.0 - illuminate: ^8.0 - - php: 8.0 - illuminate: ^7.0 - - php: 7.4 - illuminate: ^7.0 - - name: PHP ${{ matrix.php }} - Illuminate ${{ matrix.illuminate }} - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - - - name: Update composer - run: composer self-update --2 - - - name: Install dependencies - run: composer require "illuminate/support:${{ matrix.illuminate }}" --no-interaction --no-progress --no-suggest - - - name: Execute tests - run: composer test + php-tests: + runs-on: ubuntu-latest + + strategy: + matrix: + php: [8.3, 8.2, 8.1, 8.0] + laravel: [11.*, 10.*, 9.*] + dependency-version: [prefer-lowest, prefer-stable] + exclude: + - laravel: 11.* + php: 8.1 + - laravel: 11.* + php: 8.0 + - laravel: 10.* + php: 8.0 + - laravel: 9.* + php: 8.3 + - laravel: 9.* + php: 8.2 + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: | + composer require "illuminate/support:${{ matrix.laravel }}" --no-interaction --no-progress --no-suggest + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: composer test diff --git a/.gitignore b/.gitignore index 282f790..3ff5d3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ composer.lock .DS_Store .phpunit.result.cache +.phpunit.cache phpunit-output diff --git a/composer.json b/composer.json index f5717cd..83f288e 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,13 @@ { "name": "olssonm/l5-very-basic-auth", "description": "Laravel stateless HTTP basic auth without the need for a database", + "license": "MIT", "keywords": [ "olssonm", "laravel", "authentication", "http basic auth" ], - "homepage": "https://github.com/olssonm/l5-very-basic-auth", - "license": "MIT", "authors": [ { "name": "Marcus Olsson", @@ -16,18 +15,21 @@ "homepage": "https://marcusolsson.me" } ], + "homepage": "https://github.com/olssonm/l5-very-basic-auth", "require": { - "illuminate/support": "^7.0|^8.0|^9.0|^10.0", - "php": "~7.4|^8.0", + "php": "^8.0 || ^8.1 || ^8.2", + "illuminate/support": "^9.0 || ^10.0 || ^11.0", "squizlabs/php_codesniffer": "^3.5" }, "require-dev": { - "phpunit/phpunit": "^9.0", - "orchestra/testbench": ">=5.0", "laravel/helpers": "^1.1", - "pestphp/pest": "^1.0", - "pestphp/pest-plugin-laravel": "^1.2" + "orchestra/testbench": "^7.0 || ^8.0 || ^9.0", + "pestphp/pest": "^1.0 || ^2.0", + "pestphp/pest-plugin-laravel": "^1.2 || ^2.0", + "phpunit/phpunit": "^9.0 || ^10.5" }, + "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Olssonm\\VeryBasicAuth\\": "src" @@ -38,14 +40,15 @@ "Olssonm\\VeryBasicAuth\\Tests\\": "tests" } }, - "scripts": { - "phpsniff": "vendor/bin/phpcs --standard=\"PSR12\" ./src --ignore=./src/resources/*", - "phpfix": "vendor/bin/phpcbf --standard=\"PSR12\" ./src --ignore=./src/resources/*", - "test": "vendor/bin/pest" + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + }, + "sort-packages": true }, "extra": { "branch-alias": { - "dev-master": "6.x-dev" + "dev-master": "11.x-dev" }, "laravel": { "providers": [ @@ -53,11 +56,9 @@ ] } }, - "minimum-stability": "dev", - "prefer-stable": true, - "config": { - "allow-plugins": { - "pestphp/pest-plugin": true - } + "scripts": { + "phpfix": "vendor/bin/phpcbf --standard=\"PSR12\" ./src --ignore=./src/resources/*", + "phpsniff": "vendor/bin/phpcs --standard=\"PSR12\" ./src --ignore=./src/resources/*", + "test": "vendor/bin/pest" } } diff --git a/phpunit.xml b/phpunit.xml index 04080f8..88b7d47 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,25 +1,26 @@ - - ./tests/VeryBasicAuthTests.php + tests - - + + src - - src - - - + + + src + + diff --git a/src/Handlers/DefaultResponseHandler.php b/src/Handlers/DefaultResponseHandler.php index dcaaba7..07afa66 100644 --- a/src/Handlers/DefaultResponseHandler.php +++ b/src/Handlers/DefaultResponseHandler.php @@ -13,7 +13,7 @@ public function __invoke(Request $request) 'WWW-Authenticate' => sprintf( 'Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm', 'Basic Auth') - ) + ), ]; // View @@ -22,7 +22,7 @@ public function __invoke(Request $request) // If the request want's JSON, else view if ($request->wantsJson()) { return response()->json([ - 'message' => config('very_basic_auth.error_message') + 'message' => config('very_basic_auth.error_message'), ], 401, $header); } elseif (isset($view)) { return response()->view($view, [], 401) diff --git a/src/Http/Middleware/VeryBasicAuth.php b/src/Http/Middleware/VeryBasicAuth.php index f944137..b008f09 100644 --- a/src/Http/Middleware/VeryBasicAuth.php +++ b/src/Http/Middleware/VeryBasicAuth.php @@ -2,11 +2,10 @@ namespace Olssonm\VeryBasicAuth\Http\Middleware; -use Illuminate\Http\Request; -use Symfony\Component\HttpFoundation\Response; use Closure; -use Olssonm\VeryBasicAuth\Handlers\DefaultResponseHandler; +use Illuminate\Http\Request; use Olssonm\VeryBasicAuth\Handlers\ResponseHandler; +use Symfony\Component\HttpFoundation\Response; class VeryBasicAuth { @@ -20,11 +19,8 @@ public function __construct(ResponseHandler $responseHandler) /** * Handle an incoming request * - * @param Request $request - * @param Closure $next - * @param mixed $username - * @param mixed $password - * @return \Symfony\Component\HttpFoundation\Response + * @param mixed $username + * @param mixed $password */ public function handle(Request $request, Closure $next, $username = null, $password = null): Response { @@ -38,7 +34,7 @@ public function handle(Request $request, Closure $next, $username = null, $passw $authUsername = $username ?? config('very_basic_auth.user'); $authPassword = $password ?? config('very_basic_auth.password'); - if (!$authUsername && !$authPassword) { + if (! $authUsername && ! $authPassword) { return $next($request); } elseif ($request->getUser() !== $authUsername || $request->getPassword() !== $authPassword) { return $this->deniedResponse($request); @@ -51,7 +47,6 @@ public function handle(Request $request, Closure $next, $username = null, $passw /** * Return a error response * - * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ private function deniedResponse(Request $request): Response diff --git a/src/VeryBasicAuthServiceProvider.php b/src/VeryBasicAuthServiceProvider.php index 486ecfa..f009cf3 100644 --- a/src/VeryBasicAuthServiceProvider.php +++ b/src/VeryBasicAuthServiceProvider.php @@ -3,7 +3,6 @@ namespace Olssonm\VeryBasicAuth; use Illuminate\Support\ServiceProvider; -use Illuminate\Support\Str; use Olssonm\VeryBasicAuth\Handlers\DefaultResponseHandler; use Olssonm\VeryBasicAuth\Handlers\ResponseHandler; diff --git a/src/config.php b/src/config.php index d027f1f..1e86b0f 100644 --- a/src/config.php +++ b/src/config.php @@ -1,31 +1,32 @@ env('BASIC_AUTH_USERNAME', ''), - - // Password - 'password' => env('BASIC_AUTH_PASSWORD', ''), - - // Environments where the middleware is active. Use "*" to protect all envs - 'envs' => [ - '*' - ], - - // Response handler for the error responses - 'response_handler' => \Olssonm\VeryBasicAuth\Handlers\DefaultResponseHandler::class, - - // Message to display if the user "opts out"/clicks "cancel" - 'error_message' => 'You have to supply your credentials to access this resource.', - - // Message to display in the auth dialiog in some browsers (mainly Internet Explorer). - // Realm is also used to define a "space" that should share credentials. - 'realm' => 'Basic Auth', - - // If you prefer to use a view with your error message you can uncomment "error_view". - // This will supersede your default response message - // 'error_view' => 'very_basic_auth::default' - ]; +/** + * Configuration for the "HTTP Very Basic Auth"-middleware + */ + +return [ + // Username + 'user' => env('BASIC_AUTH_USERNAME', ''), + + // Password + 'password' => env('BASIC_AUTH_PASSWORD', ''), + + // Environments where the middleware is active. Use "*" to protect all envs + 'envs' => [ + '*', + ], + + // Response handler for the error responses + 'response_handler' => \Olssonm\VeryBasicAuth\Handlers\DefaultResponseHandler::class, + + // Message to display if the user "opts out"/clicks "cancel" + 'error_message' => 'You have to supply your credentials to access this resource.', + + // Message to display in the auth dialiog in some browsers (mainly Internet Explorer). + // Realm is also used to define a "space" that should share credentials. + 'realm' => 'Basic Auth', + + // If you prefer to use a view with your error message you can uncomment "error_view". + // This will supersede your default response message + // 'error_view' => 'very_basic_auth::default' +]; diff --git a/tests/Pest.php b/tests/Pest.php index 7798d74..4f20cf8 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,19 @@ in(__DIR__); +uses(TestCase::class) + ->beforeEach(function () { + // Set default config for testing + config()->set('very_basic_auth.user', 'test'); + config()->set('very_basic_auth.password', 'test'); + + Route::get('/', fn () => 'ok')->middleware(VeryBasicAuth::class)->name('default'); + Route::get('/test', fn () => 'ok')->middleware(VeryBasicAuth::class); + Route::get('/inline', fn () => 'ok')->middleware( + sprintf('auth.very_basic:%s,%s', config('very_basic_auth.user'), config('very_basic_auth.password')) + )->name('inline'); + }) + ->in(__DIR__); diff --git a/tests/TestCase.php b/tests/TestCase.php index b349ede..40963b7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,7 +15,7 @@ protected function setUp(): void protected function getPackageProviders($app) { return [ - VeryBasicAuthServiceProvider::class + VeryBasicAuthServiceProvider::class, ]; } diff --git a/tests/VeryBasicAuthTest.php b/tests/VeryBasicAuthTest.php new file mode 100644 index 0000000..adb812d --- /dev/null +++ b/tests/VeryBasicAuthTest.php @@ -0,0 +1,147 @@ +app->router->getMiddleware()))->toBeTrue(); + expect(array_key_exists('auth.very_basic', $this->app->router->getMiddleware())); +}); + +test('config file is installed', function () { + expect(file_exists(__DIR__.'/../src/config.php'))->toBeTrue(); +}); + +test('request with no credentials and no config passes', function () { + config()->set('very_basic_auth.user', ''); + config()->set('very_basic_auth.password', ''); + + $response = get('/'); + + expect($response->getStatusCode())->toEqual(200); + expect($response->headers->get('WWW-Authenticate'))->toEqual(null); +}); + +test('request with no credentials fails', function () { + $response = get('/'); + + expect($response->getStatusCode())->toEqual(401); + expect($response->headers->get('WWW-Authenticate'))->toEqual(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm'))); + expect($response->getContent())->toEqual(config('very_basic_auth.error_message')); +}); + +test('request with incorrect credentials fails - text/html', function () { + $response = withHeaders([ + 'PHP_AUTH_USER' => str_random(20), + 'PHP_AUTH_PW' => str_random(20), + ])->get('/'); + + expect($response->getStatusCode())->toEqual(401); + expect($response->headers->get('content-type'))->toEqual('text/html; charset=UTF-8'); + expect($response->headers->get('WWW-Authenticate'))->toEqual(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm'))); + expect($response->getContent())->toEqual(config('very_basic_auth.error_message')); +}); + +test('request with incorrect credentials fails - json', function () { + $response = withHeaders([ + 'PHP_AUTH_USER' => str_random(20), + 'PHP_AUTH_PW' => str_random(20), + 'Accept' => 'application/json', + ])->get('/'); + + $content = json_decode($response->getContent()); + + expect($response->getStatusCode())->toEqual(401); + expect($response->headers->get('content-type'))->toEqual('application/json'); + expect(json_last_error())->toEqual(JSON_ERROR_NONE); + expect($content->message)->toEqual(config('very_basic_auth.error_message')); + expect($response->headers->get('WWW-Authenticate'))->toEqual(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm'))); +}); + +test('request with incorrect credentials fails - view', function () { + config()->set('very_basic_auth.error_view', 'very_basic_auth::default'); + + $response = withHeaders([ + 'PHP_AUTH_USER' => str_random(20), + 'PHP_AUTH_PW' => str_random(20), + ])->get('/'); + + expect($response->getStatusCode())->toEqual(401); + expect($response->headers->get('content-type'))->toEqual('text/html; charset=UTF-8'); + expect($response->headers->get('WWW-Authenticate'))->toEqual(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm'))); + + $this->assertStringContainsStringIgnoringCase('This is the default view for the olssonm/l5-very-basic-auth-package', $response->getContent()); +}); + +test('request with correct credentials passes', function () { + $response = withHeaders([ + 'PHP_AUTH_USER' => config('very_basic_auth.user'), + 'PHP_AUTH_PW' => config('very_basic_auth.password'), + ])->get('/'); + + expect($response->getStatusCode())->toEqual(200); + expect($response->getContent())->toEqual('ok'); +}); + +test('environments', function () { + config()->set('very_basic_auth.envs', ['production']); + get('/')->assertStatus(200); + + config()->set('very_basic_auth.envs', ['local']); + get('/')->assertStatus(200); + + config()->set('very_basic_auth.envs', ['*']); + get('/')->assertStatus(401); + + config()->set('very_basic_auth.envs', ['testing']); + get('/')->assertStatus(401); +}); + +test('request with incorrect inline credentials fails', function () { + $response = withHeaders([ + 'PHP_AUTH_USER' => str_random(20), + 'PHP_AUTH_PW' => str_random(20), + ])->get('/inline'); + + expect($response->getStatusCode())->toEqual(401); + expect($response->getContent())->toEqual(config('very_basic_auth.error_message')); +}); + +test('request with correct inline credentials passes', function () { + $response = withHeaders([ + 'PHP_AUTH_USER' => config('very_basic_auth.user'), + 'PHP_AUTH_PW' => config('very_basic_auth.password'), + ])->get('/inline'); + + expect($response->getStatusCode())->toEqual(200); + expect($response->getContent())->toEqual('ok'); +}); + +test('test response handlers', function () { + // Custom response handler + app()->bind( + ResponseHandler::class, + CustomResponseHandler::class + ); + + $response = get('/test'); + + expect($response->getStatusCode())->toEqual(401); + expect($response->getContent())->toEqual('Custom response'); + + // Default response handler + app()->bind( + ResponseHandler::class, + DefaultResponseHandler::class + ); + + $response = get('/test'); + + expect($response->getStatusCode())->toEqual(401); + expect($response->getContent())->toEqual(config('very_basic_auth.error_message')); +}); diff --git a/tests/VeryBasicAuthTests.php b/tests/VeryBasicAuthTests.php deleted file mode 100644 index 08b6796..0000000 --- a/tests/VeryBasicAuthTests.php +++ /dev/null @@ -1,161 +0,0 @@ -set('very_basic_auth.user', 'test'); - config()->set('very_basic_auth.password', 'test'); - - Route::get('/', fn () => 'ok')->middleware(VeryBasicAuth::class)->name('default'); - Route::get('/test', fn () => 'ok')->middleware(VeryBasicAuth::class); - Route::get('/inline', fn () => 'ok')->middleware( - sprintf('auth.very_basic:%s,%s', config('very_basic_auth.user'), config('very_basic_auth.password')) - )->name('inline'); -}); - -test('basic auth filter is set', function () { - $this->assertTrue(in_array(VeryBasicAuth::class, $this->app->router->getMiddleware())); - $this->assertTrue(array_key_exists('auth.very_basic', $this->app->router->getMiddleware())); -}); - -test('config file is installed', function() { - $this->assertTrue(file_exists(__DIR__ . '/../src/config.php')); -}); - -test('request with no credentials and no config passes', function () { - - config()->set('very_basic_auth.user', ''); - config()->set('very_basic_auth.password', ''); - - $response = get('/'); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(null, $response->headers->get('WWW-Authenticate')); -}); - -test('request with no credentials fails', function() { - $response = get('/'); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm')), $response->headers->get('WWW-Authenticate')); - $this->assertEquals(config('very_basic_auth.error_message'), $response->getContent()); -}); - -test('request with incorrect credentials fails - text/html', function () { - $response = $this->withHeaders([ - 'PHP_AUTH_USER' => str_random(20), - 'PHP_AUTH_PW' => str_random(20) - ])->get('/'); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type')); - $this->assertEquals(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm')), $response->headers->get('WWW-Authenticate')); - $this->assertEquals(config('very_basic_auth.error_message'), $response->getContent()); -}); - -test('request with incorrect credentials fails - json', function () { - $response = $this->withHeaders([ - 'PHP_AUTH_USER' => str_random(20), - 'PHP_AUTH_PW' => str_random(20), - 'Accept' => 'application/json' - ])->get('/'); - - $content = json_decode($response->getContent()); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals('application/json', $response->headers->get('content-type')); - $this->assertEquals(json_last_error(), JSON_ERROR_NONE); - $this->assertEquals(config('very_basic_auth.error_message'), $content->message); - $this->assertEquals(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm')), $response->headers->get('WWW-Authenticate')); -}); - -test('request with incorrect credentials fails - view', function () { - - config()->set('very_basic_auth.error_view', 'very_basic_auth::default'); - - $response = $this->withHeaders([ - 'PHP_AUTH_USER' => str_random(20), - 'PHP_AUTH_PW' => str_random(20) - ])->get('/'); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type')); - $this->assertEquals(sprintf('Basic realm="%s", charset="UTF-8"', config('very_basic_auth.realm')), $response->headers->get('WWW-Authenticate')); - $this->assertStringContainsStringIgnoringCase('This is the default view for the olssonm/l5-very-basic-auth-package', $response->getContent()); -}); - -test('request with correct credentials passes', function () { - $response = $this->withHeaders([ - 'PHP_AUTH_USER' => config('very_basic_auth.user'), - 'PHP_AUTH_PW' => config('very_basic_auth.password') - ])->get('/'); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('ok', $response->getContent()); -}); - -test('environments', function () { - config()->set('very_basic_auth.envs', ['production']); - $this->get('/')->assertStatus(200); - - config()->set('very_basic_auth.envs', ['local']); - $this->get('/')->assertStatus(200); - - config()->set('very_basic_auth.envs', ['*']); - $this->get('/')->assertStatus(401); - - config()->set('very_basic_auth.envs', ['testing']); - $this->get('/')->assertStatus(401); -}); - -test('request with incorrect inline credentials fails', function () { - $response = $this->withHeaders([ - 'PHP_AUTH_USER' => str_random(20), - 'PHP_AUTH_PW' => str_random(20) - ])->get('/inline'); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals(config('very_basic_auth.error_message'), $response->getContent()); -}); - -test('request with correct inline credentials passes', function () { - $response = $this->withHeaders([ - 'PHP_AUTH_USER' => config('very_basic_auth.user'), - 'PHP_AUTH_PW' => config('very_basic_auth.password') - ])->get('/inline'); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('ok', $response->getContent()); -}); - -test('test response handlers', function () { - // Custom response handler - app()->bind( - ResponseHandler::class, - CustomResponseHandler::class - ); - - $response = get('/test'); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals('Custom response', $response->getContent()); - - // Default response handler - app()->bind( - ResponseHandler::class, - DefaultResponseHandler::class - ); - - $response = get('/test'); - - $this->assertEquals(401, $response->getStatusCode()); - $this->assertEquals(config('very_basic_auth.error_message'), $response->getContent()); -});