Skip to content

Commit

Permalink
Rename FixedStorage. Support for key prefix for RedisStorage.
Browse files Browse the repository at this point in the history
  • Loading branch information
lakiboy committed Jul 5, 2018
1 parent 2207c61 commit e66e76b
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 76 deletions.
3 changes: 3 additions & 0 deletions DependencyInjection/Configuration.php
Expand Up @@ -87,6 +87,9 @@ private function apiKeyNode(string $name): ArrayNodeDefinition
->cannotBeEmpty()
->defaultValue('snc_redis.default')
->end()
->scalarNode('key_prefix')
->cannotBeEmpty()
->end()
->scalarNode('doctrine_connection_id')
->cannotBeEmpty()
->defaultValue('database_connection')
Expand Down
23 changes: 16 additions & 7 deletions DependencyInjection/DamaxApiAuthExtension.php
Expand Up @@ -15,8 +15,11 @@
use Damax\Bundle\ApiAuthBundle\Jwt\TokenBuilder;
use Damax\Bundle\ApiAuthBundle\Key\Generator\Generator;
use Damax\Bundle\ApiAuthBundle\Key\Storage\ChainStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\DoctrineStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\DummyStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\InMemoryStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\Reader;
use Damax\Bundle\ApiAuthBundle\Key\Storage\RedisStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\Writer;
use Damax\Bundle\ApiAuthBundle\Listener\ExceptionListener;
use Damax\Bundle\ApiAuthBundle\Security\ApiKey\Authenticator as ApiKeyAuthenticator;
Expand Down Expand Up @@ -205,20 +208,26 @@ private function configureKeyStorage(array $config, ContainerBuilder $container)
$container->register(Writer::class, DummyStorage::class);

foreach ($config as $item) {
$className = sprintf('Damax\\Bundle\\ApiAuthBundle\\Key\\Storage\\%sStorage', ucfirst($item['type']));
$drivers[] = $driver = new Definition();

$drivers[] = $driver = new Definition($className);

if (Configuration::STORAGE_FIXED === $item['type']) {
$driver->addArgument($item['tokens']);
} elseif (Configuration::STORAGE_REDIS === $item['type']) {
$driver->addArgument(new Reference($item['redis_client_id']));
if (Configuration::STORAGE_REDIS === $item['type']) {
$driver
->setClass(RedisStorage::class)
->addArgument(new Reference($item['redis_client_id']))
->addArgument($item['key_prefix'] ?? '')
;
} elseif (Configuration::STORAGE_DOCTRINE === $item['type']) {
$driver
->setClass(DoctrineStorage::class)
->addArgument(new Reference($item['doctrine_connection_id']))
->addArgument($item['table_name'])
->addArgument($item['fields'] ?? [])
;
} else {
$driver
->setClass(InMemoryStorage::class)
->addArgument($item['tokens'])
;
}

if ($item['writable']) {
Expand Down
Expand Up @@ -6,7 +6,7 @@

use Damax\Bundle\ApiAuthBundle\Key\Key;

final class FixedStorage implements Reader
final class InMemoryStorage implements Reader
{
private $data;
private $ttl;
Expand Down
27 changes: 21 additions & 6 deletions Key/Storage/RedisStorage.php
Expand Up @@ -10,33 +10,48 @@
final class RedisStorage implements Storage
{
private $client;
private $prefix;

public function __construct(ClientInterface $client)
public function __construct(ClientInterface $client, string $prefix = '')
{
$this->client = $client;
$this->prefix = $prefix;
}

public function has(string $key): bool
{
return (bool) $this->client->exists($key);
$storageKey = $this->storageKey($key);

return (bool) $this->client->exists($storageKey);
}

public function get(string $key): Key
{
if (null === $identity = $this->client->get($key)) {
$storageKey = $this->storageKey($key);

if (null === $identity = $this->client->get($storageKey)) {
throw new KeyNotFound();
}

return new Key($key, $identity, $this->client->ttl($key));
return new Key($key, $identity, $this->client->ttl($storageKey));
}

public function add(Key $key): void
{
$this->client->setex((string) $key, $key->ttl(), $key->identity());
$storageKey = $this->storageKey((string) $key);

$this->client->setex($storageKey, $key->ttl(), $key->identity());
}

public function remove(string $key): void
{
$this->client->del([$key]);
$storageKey = $this->storageKey($key);

$this->client->del([$storageKey]);
}

private function storageKey(string $key): string
{
return $this->prefix . $key;
}
}
2 changes: 2 additions & 0 deletions Tests/DependencyInjection/ConfigurationTest.php
Expand Up @@ -98,6 +98,7 @@ public function it_configures_api_key()
'type' => 'redis',
'writable' => true,
'redis_client_id' => 'redis_service_id',
'key_prefix' => 'api_',
],
[
'type' => 'doctrine',
Expand Down Expand Up @@ -133,6 +134,7 @@ public function it_configures_api_key()
'tokens' => [],
'writable' => true,
'redis_client_id' => 'redis_service_id',
'key_prefix' => 'api_',
'doctrine_connection_id' => 'database_connection',
'table_name' => 'api_key',
],
Expand Down
152 changes: 109 additions & 43 deletions Tests/DependencyInjection/DamaxApiAuthExtensionTest.php
Expand Up @@ -17,6 +17,11 @@
use Damax\Bundle\ApiAuthBundle\Jwt\TokenBuilder;
use Damax\Bundle\ApiAuthBundle\Key\Generator\Generator;
use Damax\Bundle\ApiAuthBundle\Key\Generator\RandomGenerator;
use Damax\Bundle\ApiAuthBundle\Key\Storage\ChainStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\DoctrineStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\InMemoryStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\Reader;
use Damax\Bundle\ApiAuthBundle\Key\Storage\RedisStorage;
use Damax\Bundle\ApiAuthBundle\Listener\ExceptionListener;
use Damax\Bundle\ApiAuthBundle\Security\ApiKey\Authenticator as ApiKeyAuthenticator;
use Damax\Bundle\ApiAuthBundle\Security\ApiKey\StorageUserProvider;
Expand All @@ -25,6 +30,7 @@
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\HttpKernel\KernelEvents;

Expand All @@ -42,39 +48,128 @@ public function it_registers_api_key_services()
['type' => 'query', 'name' => 'api_key'],
['type' => 'cookie', 'name' => 'api_key'],
],
'storage' => [
'foo' => 'bar',
'baz' => 'qux',
],
],
]);

$this->assertContainerBuilderHasService('damax.api_auth.api_key.user_provider', StorageUserProvider::class);
$this->assertContainerBuilderHasService(Generator::class, RandomGenerator::class);
$this->assertContainerBuilderHasService('damax.api_auth.api_key.user_provider', StorageUserProvider::class);
$this->assertContainerBuilderHasService('damax.api_auth.api_key.authenticator', ApiKeyAuthenticator::class);
$this->assertContainerBuilderHasService(Reader::class, ChainStorage::class);

/** @var Definition $extractors */
$extractors = $this->container
->getDefinition('damax.api_auth.api_key.authenticator')
->getArgument(0)
;
$this->assertEquals(ChainExtractor::class, $extractors->getClass());
}

/** @var Definition[] $definitions */
$definitions = $extractors->getArgument(0);
/**
* @test
*/
public function it_registers_key_extractors()
{
$this->load([
'api_key' => [
'extractors' => [
['type' => 'header', 'name' => 'X-Authorization', 'prefix' => 'KEY'],
['type' => 'query', 'name' => 'api_key'],
['type' => 'cookie', 'name' => 'api_key'],
],
],
]);

/** @var Definition[] $extractors */
$extractors = $this->container
->getDefinition('damax.api_auth.api_key.authenticator')
->getArgument(0)
->getArgument(0)
;

// Header
$this->assertEquals(HeaderExtractor::class, $definitions[0]->getClass());
$this->assertEquals('X-Authorization', $definitions[0]->getArgument(0));
$this->assertEquals('KEY', $definitions[0]->getArgument(1));
$this->assertEquals(HeaderExtractor::class, $extractors[0]->getClass());
$this->assertEquals('X-Authorization', $extractors[0]->getArgument(0));
$this->assertEquals('KEY', $extractors[0]->getArgument(1));

// Query
$this->assertEquals(QueryExtractor::class, $definitions[1]->getClass());
$this->assertEquals('api_key', $definitions[1]->getArgument(0));
$this->assertEquals(QueryExtractor::class, $extractors[1]->getClass());
$this->assertEquals('api_key', $extractors[1]->getArgument(0));

// Cookie
$this->assertEquals(CookieExtractor::class, $definitions[2]->getClass());
$this->assertEquals('api_key', $definitions[2]->getArgument(0));
$this->assertEquals(CookieExtractor::class, $extractors[2]->getClass());
$this->assertEquals('api_key', $extractors[2]->getArgument(0));
}

/**
* @test
*/
public function it_registers_key_storage_drivers()
{
$this->load([
'api_key' => [
'storage' => [
[
'type' => 'fixed',
'tokens' => ['john.doe' => 'ABC', 'jane.doe' => 'XYZ'],
],
[
'type' => 'redis',
'key_prefix' => 'api_',
],
[
'type' => 'doctrine',
'fields' => ['key' => 'id', 'identity' => 'user_id'],
],
],
],
]);

/** @var Definition[] $drivers */
$drivers = $this->container->getDefinition(Reader::class)->getArgument(0);

// In memory
$this->assertEquals(InMemoryStorage::class, $drivers[0]->getClass());
$this->assertEquals(['john.doe' => 'ABC', 'jane.doe' => 'XYZ'], $drivers[0]->getArgument(0));

// Redis
$this->assertEquals(RedisStorage::class, $drivers[1]->getClass());
$this->assertEquals(new Reference('snc_redis.default'), $drivers[1]->getArgument(0));
$this->assertEquals('api_', $drivers[1]->getArgument(1));

// Doctrine
$this->assertEquals(DoctrineStorage::class, $drivers[2]->getClass());
$this->assertEquals(new Reference('database_connection'), $drivers[2]->getArgument(0));
$this->assertEquals('api_key', $drivers[2]->getArgument(1));
$this->assertEquals(['key' => 'id', 'identity' => 'user_id'], $drivers[2]->getArgument(2));
}

/**
* @test
*/
public function it_skips_exception_listener_registration()
{
$this->load(['format_exceptions' => false]);

$this->assertContainerBuilderNotHasService(ExceptionListener::class);
}

/**
* @test
*/
public function it_registers_exception_listener()
{
$this->load(['format_exceptions' => '^/api/']);

$this->assertContainerBuilderHasServiceDefinitionWithTag(ExceptionListener::class, 'kernel.event_listener', [
'event' => KernelEvents::EXCEPTION,
'method' => 'onKernelException',
]);

/** @var Definition $matcher */
$matcher = $this->container->getDefinition(ExceptionListener::class)->getArgument(1);

$this->assertEquals(RequestMatcher::class, $matcher->getClass());
$this->assertEquals('^/api/', $matcher->getArgument(0));
}

/**
Expand Down Expand Up @@ -160,35 +255,6 @@ public function it_registers_jwt_services_with_symmetric_signer()
unlink($key);
}

/**
* @test
*/
public function it_skips_exception_listener_registration()
{
$this->load(['format_exceptions' => false]);

$this->assertContainerBuilderNotHasService(ExceptionListener::class);
}

/**
* @test
*/
public function it_registers_exception_listener()
{
$this->load(['format_exceptions' => '^/api/']);

$this->assertContainerBuilderHasServiceDefinitionWithTag(ExceptionListener::class, 'kernel.event_listener', [
'event' => KernelEvents::EXCEPTION,
'method' => 'onKernelException',
]);

/** @var Definition $matcher */
$matcher = $this->container->getDefinition(ExceptionListener::class)->getArgument(1);

$this->assertEquals(RequestMatcher::class, $matcher->getClass());
$this->assertEquals('^/api/', $matcher->getArgument(0));
}

protected function getContainerExtensions(): array
{
return [
Expand Down
6 changes: 3 additions & 3 deletions Tests/Key/Storage/ChainStorageTest.php
Expand Up @@ -5,7 +5,7 @@
namespace Damax\Bundle\ApiAuthBundle\Tests\Key\Storage;

use Damax\Bundle\ApiAuthBundle\Key\Storage\ChainStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\FixedStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\InMemoryStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\KeyNotFound;
use PHPUnit\Framework\TestCase;

Expand All @@ -19,8 +19,8 @@ class ChainStorageTest extends TestCase
protected function setUp()
{
$this->storage = new ChainStorage([
new FixedStorage(['john.doe' => 'ABC'], 60),
new FixedStorage(['jane.doe' => 'XYZ'], 90),
new InMemoryStorage(['john.doe' => 'ABC'], 60),
new InMemoryStorage(['jane.doe' => 'XYZ'], 90),
]);
}

Expand Down
Expand Up @@ -4,18 +4,18 @@

namespace Damax\Bundle\ApiAuthBundle\Tests\Key\Storage;

use Damax\Bundle\ApiAuthBundle\Key\Storage\FixedStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\InMemoryStorage;
use Damax\Bundle\ApiAuthBundle\Key\Storage\KeyNotFound;
use PHPUnit\Framework\TestCase;

class FixedStorageTest extends TestCase
class InMemoryStorageTest extends TestCase
{
/**
* @test
*/
public function it_checks_key_existence()
{
$storage = new FixedStorage(['john.doe' => 'ABC', 'jane.doe' => 'XYZ'], 600);
$storage = new InMemoryStorage(['john.doe' => 'ABC', 'jane.doe' => 'XYZ'], 600);

$this->assertTrue($storage->has('ABC'));
$this->assertTrue($storage->has('XYZ'));
Expand All @@ -29,7 +29,7 @@ public function it_checks_key_existence()
*
* @test
*/
public function it_retrieves_key(FixedStorage $storage)
public function it_retrieves_key(InMemoryStorage $storage)
{
$key = $storage->get('ABC');

Expand All @@ -49,7 +49,7 @@ public function it_retrieves_key(FixedStorage $storage)
*
* @test
*/
public function it_throws_exception_when_retrieving_missing_key(FixedStorage $storage)
public function it_throws_exception_when_retrieving_missing_key(InMemoryStorage $storage)
{
$this->expectException(KeyNotFound::class);

Expand Down

0 comments on commit e66e76b

Please sign in to comment.