diff --git a/.gitignore b/.gitignore index 024479b..63eca1c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ coverage.xml composer.lock /reports + +/tests/cache diff --git a/README.md b/README.md index 40a8dc4..4395f46 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This helper takes care of all the headaches and boilerplate code with a simple a - Access token key - ~~Improve data validation and error messages~~ - Write/update readme -- Make the cache store configurable +- ~~Make the cache store configurable~~ - Maybe: add more tests - Maybe: Add support for authorization_code and implicit grants diff --git a/phpunit.xml b/phpunit.xml index 2c67ce8..7ad5462 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,7 +2,7 @@ diffInSeconds($this->expiresAt)); } + public function getTokenType(): string + { + return $this->tokenType; + } + + public function getTokenName(): string + { + return $this->tokenName; + } + + public function getCustomCallback(): ?Closure + { + return $this->customCallback; + } + public function getHttpClient(PendingRequest $httpClient): PendingRequest { return match ($this->tokenType) { diff --git a/src/Credentials.php b/src/Credentials.php index b14d143..6917c98 100644 --- a/src/Credentials.php +++ b/src/Credentials.php @@ -120,6 +120,7 @@ public function addAuthToRequest(PendingRequest $httpClient, Options $options): $options->tokenName => $this->token, ]); } + return $httpClient->withToken($this->token, $options->authType); } if ($options->authType === self::AUTH_TYPE_BASIC) { diff --git a/src/Options.php b/src/Options.php index 66a90e8..f3804f1 100644 --- a/src/Options.php +++ b/src/Options.php @@ -20,6 +20,8 @@ final public function __construct( public int|string|Closure $expires = 3600, public string|Closure $accessToken = 'access_token', public ?Closure $tokenTypeCustomCallback = null, + public ?string $cacheKey = null, + public ?string $cacheDriver = null, ) { $this->validateOptions(); } diff --git a/src/RefreshToken.php b/src/RefreshToken.php index b38a6b5..885601c 100644 --- a/src/RefreshToken.php +++ b/src/RefreshToken.php @@ -43,6 +43,7 @@ public function __invoke( //tokenType: $options['auth_type'], tokenType: $options->tokenType, customCallback: $options->tokenTypeCustomCallback, + tokenName: $options->tokenName, ); } diff --git a/src/TokenStore.php b/src/TokenStore.php index 2e69d01..7c358d9 100644 --- a/src/TokenStore.php +++ b/src/TokenStore.php @@ -14,15 +14,16 @@ protected static function generateCacheKey(string $refreshUrl): string } /** - * @throws Exception + * @throws Exception|\Psr\SimpleCache\InvalidArgumentException */ public static function get( string $refreshUrl, Credentials $credentials, Options $options, ): AccessToken { - $cacheKey = static::generateCacheKey($refreshUrl); - $accessToken = Cache::get($cacheKey); + $cacheKey = $options->cacheKey ?? static::generateCacheKey($refreshUrl); + + $accessToken = Cache::store($options->cacheDriver)->get($cacheKey); if ($accessToken) { return $accessToken; @@ -31,7 +32,7 @@ public static function get( $accessToken = app(RefreshToken::class)(...func_get_args()); $ttl = $accessToken->getExpiresIn(); - Cache::put($cacheKey, $accessToken, $ttl); + Cache::store($options->cacheDriver)->put($cacheKey, $accessToken, $ttl); return $accessToken; } diff --git a/tests/TestCase.php b/tests/TestCase.php index 79118f5..b1c6926 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,11 @@ namespace Pelmered\LaravelHttpOAuthHelper\Tests; +use Illuminate\Contracts\Config\Repository; use Illuminate\Http\Client\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; use Pelmered\LaravelHttpOAuthHelper\LaravelHttpOAuthHelperServiceProvider; class TestCase extends \Orchestra\Testbench\TestCase @@ -36,9 +38,25 @@ static function (Request $request) { } } - if ($request->url() === 'https://example.com/api') { + if ( + $request->url() === 'https://example.com/api' + && $request->hasHeader('Authorization') + ) { return Http::response([ - 'data' => 'some data', + 'data' => 'some data with bearer token', + 'token' => $request->header('Authorization')[0], + ], 200); + } + + if (Str::of($request->url())->startsWith('https://example.com/api?token=')) { + + $url = parse_url($request->url(), PHP_URL_QUERY); + parse_str($url, $output); + $token = $output['token']; + + return Http::response([ + 'data' => 'some data with query string token', + 'token' => $token, ], 200); } @@ -46,7 +64,40 @@ static function (Request $request) { } ); - Cache::spy(); + //Cache::spy(); + } + + protected function defineEnvironment($app) + { + // Setup default database to use sqlite :memory: + tap($app['config'], function (Repository $config) { + + //dd($config->get('cache.stores')); + + // Setup queue database connections. + $configData = [ + 'database.default' => 'testbench', + 'database.connections.testbench' => [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ], + 'queue.batching.database' => 'testbench', + 'queue.failed.database' => 'testbench', + + //'cache.stores.array' + + ]; + + foreach ($configData as $key => $value) { + $config->set($key, $value); + } + }); + } + + protected function usesMySqlConnection($app) + { + $app['config']->set('database.default', 'mysql'); } protected function getPackageProviders($app): array diff --git a/tests/Unit/CredentialsTest.php b/tests/Unit/CredentialsTest.php index 5b04550..648681d 100644 --- a/tests/Unit/CredentialsTest.php +++ b/tests/Unit/CredentialsTest.php @@ -2,7 +2,6 @@ uses(\Pelmered\LaravelHttpOAuthHelper\Tests\TestCase::class); -use Illuminate\Validation\ValidationException; use Pelmered\LaravelHttpOAuthHelper\Credentials; it('validates credentials array when creating a credential object', function () { diff --git a/tests/Unit/MacroTest.php b/tests/Unit/MacroTest.php index 6cf1f1b..3478ecb 100644 --- a/tests/Unit/MacroTest.php +++ b/tests/Unit/MacroTest.php @@ -1,12 +1,16 @@ hasHeader('Authorization', 'Bearer this_is_my_access_token_from_body_refresh_token') && $request->url() === 'https://example.com/api'; }); }); -test('macro with shorthand client credentials', function () { +test('macro with shorthand client credentials', function () { $response = Http::withRefreshToken( 'https://example.com/oauth/token', [ @@ -31,7 +35,7 @@ ['scopes' => ['scope1', 'scope2']], )->get('https://example.com/api'); - expect($response->json()['data'])->toBe('some data'); + expect($response->json()['data'])->toBe('some data with bearer token'); Http::assertSentInOrder([ function (Request $request) { @@ -44,19 +48,19 @@ function (Request $request) { }, ]); }); -test('macro with refresh token in credentials object', function () { +test('macro with refresh token in credentials object', function () { $response = Http::withRefreshToken( 'https://example.com/oauth/token', new Credentials( token: 'this_is_my_refresh_token', ), [ - 'scopes' => ['scope1', 'scope2'], + 'scopes' => ['scope1', 'scope2'], 'authType' => Credentials::AUTH_TYPE_BEARER, ] )->get('https://example.com/api'); - expect($response->json()['data'])->toBe('some data'); + expect($response->json()['data'])->toBe('some data with bearer token'); Http::assertSentInOrder([ function (Request $request) { @@ -68,7 +72,7 @@ function (Request $request) { ]); }); -test('macro with client credentials in credentials object', function () { +test('macro with client credentials in credentials object', function () { $response = Http::withRefreshToken( 'https://example.com/oauth/token', new Credentials( @@ -92,3 +96,33 @@ function (Request $request) { }, ]); }); + +test('macro with custom cache store', function () { + + Cache::clear(); + Cache::spy(); + + Cache::shouldReceive('store') + ->with('file') + ->andReturn(new FileStore(app()['files'], 'tests/cache')); + + $response = Http::withRefreshToken( + 'https://example.com/oauth/token', + new Credentials( + clientId: 'this_is_my_client_id', + clientSecret: 'this_is_my_client_secret', + ), + [ + 'scopes' => ['scope1', 'scope2'], + 'tokenType' => AccessToken::TYPE_BEARER, + 'authType' => Credentials::AUTH_TYPE_BASIC, + 'cacheDriver' => 'file', + 'cacheKey' => 'my_cache_key', + ], + )->get('https://example.com/api'); + + $data = $response->json(); + + expect($data['data'])->toBe('some data with bearer token') + ->and($data['token'])->toBe('Bearer this_is_my_access_token_from_body_refresh_token'); +}); diff --git a/tests/Unit/OptionsTest.php b/tests/Unit/OptionsTest.php index 52273d4..a633ed4 100644 --- a/tests/Unit/OptionsTest.php +++ b/tests/Unit/OptionsTest.php @@ -50,7 +50,7 @@ $this->expectExceptionMessage('The scopes.2 field must be a string.'); new Options( - scopes: ['valid', 'also_valid', new stdClass()], + scopes: ['valid', 'also_valid', new stdClass], ); }); diff --git a/tests/Unit/TokenStoreTest.php b/tests/Unit/TokenStoreTest.php index 68248ea..f0d4510 100644 --- a/tests/Unit/TokenStoreTest.php +++ b/tests/Unit/TokenStoreTest.php @@ -2,11 +2,19 @@ uses(\Pelmered\LaravelHttpOAuthHelper\Tests\TestCase::class); +use Illuminate\Cache\FileStore; use Illuminate\Support\Facades\Cache; +use Orchestra\Testbench\Attributes\DefineEnvironment; use Pelmered\LaravelHttpOAuthHelper\Credentials; use Pelmered\LaravelHttpOAuthHelper\Options; use Pelmered\LaravelHttpOAuthHelper\TokenStore; +beforeEach(function () { + //DefineEnvironment:: + +}); + +/* it('reads and stores a token in cache', function () { Cache::clear(); Cache::spy(); @@ -28,3 +36,41 @@ // Does not work with composer update --prefer-lowest //Cache::shouldHaveReceived('put')->once()->with('oauth_token_example.comoauthtoken', $accessToken, 3540); }); +*/ + +it('reads and stores a token in cache with custom cache driver', function () { + + Cache::clear(); + //Cache::spy(); + + //FileStore:: + + //Cache::shouldReceive('get')->once()->with('oauth_token_example.comoauthtoken')->andReturn(null); + + $cacheStore = 'file'; + $cacheKey = 'custom_cache_key'; + $cacheStoreNotUsed = 'array'; + + $accessToken = TokenStore::get( + 'https://example.com/oauth/token', + new Credentials( + clientId: 'this_is_my_client_id', + clientSecret: 'this_is_my_client_secret', + ), + new Options( + scopes: ['scope1', 'scope2'], + authType: Credentials::AUTH_TYPE_BASIC, + cacheKey: $cacheKey, + cacheDriver: $cacheStore + ), + ); + + $accessToken2 = Cache::store($cacheStore)->get($cacheKey); + $accessToken3 = Cache::store($cacheStoreNotUsed)->get($cacheKey); + + expect($accessToken->getAccessToken())->toBe($accessToken2->getAccessToken()) + ->and($accessToken->getExpiresIn())->toBe($accessToken2->getExpiresIn()) + ->and($accessToken->getTokenType())->toBe($accessToken2->getTokenType()) + ->and($accessToken->getTokenName())->toBe($accessToken2->getTokenName()) + ->and($accessToken3)->toBeNull(); +});