diff --git a/config/autoload/common.global.php b/config/autoload/common.global.php index b35807fd..19404d8c 100644 --- a/config/autoload/common.global.php +++ b/config/autoload/common.global.php @@ -8,8 +8,8 @@ return [ 'debug' => false, - // Disabling config cache for cli, ensures it's never used for openswoole and also that console commands don't - // generate a cache file that's then used by non-openswoole web executions + // Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console + // commands don't generate a cache file that's then used by php-fpm web executions ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli', ]; diff --git a/module/Core/src/Config/EnvVars.php b/module/Core/src/Config/EnvVars.php index 7cbd0af4..228a5921 100644 --- a/module/Core/src/Config/EnvVars.php +++ b/module/Core/src/Config/EnvVars.php @@ -47,6 +47,7 @@ enum EnvVars: string case PORT = 'PORT'; case TASK_WORKER_NUM = 'TASK_WORKER_NUM'; case WEB_WORKER_NUM = 'WEB_WORKER_NUM'; + case INITIAL_API_KEY = 'INITIAL_API_KEY'; case ANONYMIZE_REMOTE_ADDR = 'ANONYMIZE_REMOTE_ADDR'; case TRACK_ORPHAN_VISITS = 'TRACK_ORPHAN_VISITS'; case DISABLE_TRACK_PARAM = 'DISABLE_TRACK_PARAM'; diff --git a/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php b/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php index 63716e74..1e0b041b 100644 --- a/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php +++ b/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php @@ -15,7 +15,8 @@ use function Shlinkio\Shlink\Core\determineTableName; return static function (ClassMetadata $metadata, array $emConfig): void { $builder = new ClassMetadataBuilder($metadata); - $builder->setTable(determineTableName('api_keys', $emConfig)); + $builder->setTable(determineTableName('api_keys', $emConfig)) + ->setCustomRepositoryClass(ApiKey\Repository\ApiKeyRepository::class); $builder->createField('id', Types::BIGINT) ->makePrimaryKey() diff --git a/module/Rest/config/initial-api-key.config.php b/module/Rest/config/initial-api-key.config.php new file mode 100644 index 00000000..57114479 --- /dev/null +++ b/module/Rest/config/initial-api-key.config.php @@ -0,0 +1,24 @@ + PHP_SAPI !== 'cli' ? null : EnvVars::INITIAL_API_KEY->loadFromEnv(), + + 'dependencies' => [ + 'delegators' => [ + Application::class => [ + ApiKey\InitialApiKeyDelegator::class, + ], + ], + ], + +]; diff --git a/module/Rest/src/ApiKey/InitialApiKeyDelegator.php b/module/Rest/src/ApiKey/InitialApiKeyDelegator.php new file mode 100644 index 00000000..9129d7d3 --- /dev/null +++ b/module/Rest/src/ApiKey/InitialApiKeyDelegator.php @@ -0,0 +1,31 @@ +get('config')['initial_api_key'] ?? null; + if ($initialApiKey !== null) { + $this->createInitialApiKey($initialApiKey, $container); + } + + return $callback(); + } + + private function createInitialApiKey(string $initialApiKey, ContainerInterface $container): void + { + /** @var ApiKeyRepositoryInterface $repo */ + $repo = $container->get(EntityManager::class)->getRepository(ApiKey::class); + $repo->createInitialApiKey($initialApiKey); + } +} diff --git a/module/Rest/src/ApiKey/Repository/ApiKeyRepository.php b/module/Rest/src/ApiKey/Repository/ApiKeyRepository.php new file mode 100644 index 00000000..098dda6a --- /dev/null +++ b/module/Rest/src/ApiKey/Repository/ApiKeyRepository.php @@ -0,0 +1,29 @@ +getEntityManager()->wrapInTransaction(function () use ($apiKey): void { + $qb = $this->getEntityManager()->createQueryBuilder(); + $amountOfApiKeys = $qb->select('COUNT(a.id)') + ->from(ApiKey::class, 'a') + ->getQuery() + ->setLockMode(LockMode::PESSIMISTIC_WRITE) + ->getSingleScalarResult(); + + if ($amountOfApiKeys === 0) { + $this->getEntityManager()->persist(ApiKey::fromKey($apiKey)); + $this->getEntityManager()->flush(); + } + }); + } +} diff --git a/module/Rest/src/ApiKey/Repository/ApiKeyRepositoryInterface.php b/module/Rest/src/ApiKey/Repository/ApiKeyRepositoryInterface.php new file mode 100644 index 00000000..f5beb3e9 --- /dev/null +++ b/module/Rest/src/ApiKey/Repository/ApiKeyRepositoryInterface.php @@ -0,0 +1,16 @@ +key = Uuid::uuid4()->toString(); - $this->expirationDate = $expirationDate; - $this->name = $name; + $this->key = $key ?? Uuid::uuid4()->toString(); $this->enabled = true; $this->roles = new ArrayCollection(); } @@ -44,7 +42,10 @@ class ApiKey extends AbstractEntity public static function fromMeta(ApiKeyMeta $meta): self { - $apiKey = new self($meta->name, $meta->expirationDate); + $apiKey = self::create(); + $apiKey->name = $meta->name; + $apiKey->expirationDate = $meta->expirationDate; + foreach ($meta->roleDefinitions as $roleDefinition) { $apiKey->registerRole($roleDefinition); } @@ -52,6 +53,11 @@ class ApiKey extends AbstractEntity return $apiKey; } + public static function fromKey(string $key): self + { + return new self($key); + } + public function getExpirationDate(): ?Chronos { return $this->expirationDate; diff --git a/module/Rest/test/ConfigProviderTest.php b/module/Rest/test/ConfigProviderTest.php index d3288151..06bf179f 100644 --- a/module/Rest/test/ConfigProviderTest.php +++ b/module/Rest/test/ConfigProviderTest.php @@ -22,10 +22,11 @@ class ConfigProviderTest extends TestCase { $config = ($this->configProvider)(); - self::assertCount(4, $config); + self::assertCount(5, $config); self::assertArrayHasKey('dependencies', $config); self::assertArrayHasKey('auth', $config); self::assertArrayHasKey('entity_manager', $config); + self::assertArrayHasKey('initial_api_key', $config); self::assertArrayHasKey(ConfigAbstractFactory::class, $config); }