Skip to content

Commit

Permalink
Make the cache store and cache key configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
pelmered committed Sep 16, 2024
1 parent 0c4a693 commit 065a1f8
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
coverage.xml
composer.lock
/reports

/tests/cache
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
cacheDirectory="tests/cache/.phpunit.cache"
executionOrder="depends,defects"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
Expand Down
20 changes: 17 additions & 3 deletions src/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ final class AccessToken

public const TYPE_CUSTOM = 'custom';

protected string $tokenName = 'token';

public function __construct(
protected string $accessToken,
protected Carbon $expiresAt,
protected string $tokenType = self::TYPE_BEARER,
protected ?Closure $customCallback = null
protected string $tokenName = 'token',
protected ?Closure $customCallback = null,
) {
if ($tokenType === self::TYPE_CUSTOM && is_null($customCallback)) {
throw new InvalidArgumentException('customCallback must be set when using AUTH_TYPE_CUSTOM');
Expand All @@ -43,6 +42,21 @@ public function getExpiresIn(): int
return (int) round(Carbon::now()->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) {
Expand Down
1 change: 1 addition & 0 deletions src/Credentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions src/RefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function __invoke(
//tokenType: $options['auth_type'],
tokenType: $options->tokenType,
customCallback: $options->tokenTypeCustomCallback,
tokenName: $options->tokenName,
);
}

Expand Down
9 changes: 5 additions & 4 deletions src/TokenStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down
57 changes: 54 additions & 3 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,17 +38,66 @@ 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);
}

return Http::response([], 200);
}
);

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
Expand Down
1 change: 0 additions & 1 deletion tests/Unit/CredentialsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
46 changes: 40 additions & 6 deletions tests/Unit/MacroTest.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<?php

uses(\Pelmered\LaravelHttpOAuthHelper\Tests\TestCase::class);

use Illuminate\Cache\FileStore;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Pelmered\LaravelHttpOAuthHelper\AccessToken;
use Pelmered\LaravelHttpOAuthHelper\Credentials;

beforeEach(function () {});

test('macro with shorthand refresh token', function () {
$response = Http::withRefreshToken(
'https://example.com/oauth/token',
Expand All @@ -22,7 +26,7 @@
return $request->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',
[
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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(
Expand All @@ -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');
});
2 changes: 1 addition & 1 deletion tests/Unit/OptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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],
);
});

Expand Down
46 changes: 46 additions & 0 deletions tests/Unit/TokenStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
});

0 comments on commit 065a1f8

Please sign in to comment.