From 041f231ff20757225ef03edd8c1786a7b98ed826 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 6 Jan 2021 10:59:02 +0100 Subject: [PATCH] Implemented mechanism to add/remove roles from API keys --- .../Shlinkio.Shlink.Rest.Entity.ApiKey.php | 3 ++ .../Rest/src/ApiKey/Model/RoleDefinition.php | 39 ++++++++++++++++++ module/Rest/src/Entity/ApiKey.php | 40 ++++++++++++++++--- module/Rest/src/Entity/ApiKeyRole.php | 5 +++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 module/Rest/src/ApiKey/Model/RoleDefinition.php 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 2cb2df2b..95f53b30 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 @@ -38,5 +38,8 @@ return static function (ClassMetadata $metadata, array $emConfig): void { $builder->createOneToMany('roles', ApiKeyRole::class) ->mappedBy('apiKey') + ->setIndexBy('roleName') + ->cascadePersist() + ->orphanRemoval() ->build(); }; diff --git a/module/Rest/src/ApiKey/Model/RoleDefinition.php b/module/Rest/src/ApiKey/Model/RoleDefinition.php new file mode 100644 index 00000000..bb9165e8 --- /dev/null +++ b/module/Rest/src/ApiKey/Model/RoleDefinition.php @@ -0,0 +1,39 @@ +roleName = $roleName; + $this->meta = $meta; + } + + public static function forAuthoredShortUrls(): self + { + return new self(Role::AUTHORED_SHORT_URLS, []); + } + + public static function forDomain(string $domainId): self + { + return new self(Role::DOMAIN_SPECIFIC, ['domain_id' => $domainId]); + } + + public function roleName(): string + { + return $this->roleName; + } + + public function meta(): array + { + return $this->meta; + } +} diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 3d600ce7..59ff502b 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -7,10 +7,12 @@ namespace Shlinkio\Shlink\Rest\Entity; use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Exception; use Happyr\DoctrineSpecification\Spec; use Happyr\DoctrineSpecification\Specification\Specification; use Ramsey\Uuid\Uuid; use Shlinkio\Shlink\Common\Entity\AbstractEntity; +use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; use Shlinkio\Shlink\Rest\ApiKey\Role; class ApiKey extends AbstractEntity @@ -21,12 +23,20 @@ class ApiKey extends AbstractEntity /** @var Collection|ApiKeyRole[] */ private Collection $roles; - public function __construct(?Chronos $expirationDate = null) + /** + * @param RoleDefinition[] $roleDefinitions + * @throws Exception + */ + public function __construct(?Chronos $expirationDate = null, array $roleDefinitions = []) { $this->key = Uuid::uuid4()->toString(); $this->expirationDate = $expirationDate; $this->enabled = true; $this->roles = new ArrayCollection(); + + foreach ($roleDefinitions as $roleDefinition) { + $this->registerRole($roleDefinition); + } } public function getExpirationDate(): ?Chronos @@ -81,13 +91,33 @@ class ApiKey extends AbstractEntity public function hasRole(string $roleName): bool { - return $this->roles->exists(fn ($key, ApiKeyRole $role) => $role->name() === $roleName); + return $this->roles->containsKey($roleName); } public function getRoleMeta(string $roleName): array { - /** @var ApiKeyRole|false $role */ - $role = $this->roles->filter(fn (ApiKeyRole $role) => $role->name() === $roleName)->first(); - return ! $role ? [] : $role->meta(); + /** @var ApiKeyRole|null $role */ + $role = $this->roles->get($roleName); + return $role === null ? [] : $role->meta(); + } + + public function registerRole(RoleDefinition $roleDefinition): void + { + $roleName = $roleDefinition->roleName(); + $meta = $roleDefinition->meta(); + + if ($this->hasRole($roleName)) { + /** @var ApiKeyRole $role */ + $role = $this->roles->get($roleName); + $role->updateMeta($meta); + } else { + $role = new ApiKeyRole($roleDefinition->roleName(), $roleDefinition->meta(), $this); + $this->roles[$roleName] = $role; + } + } + + public function removeRole(string $roleName): void + { + $this->roles->remove($roleName); } } diff --git a/module/Rest/src/Entity/ApiKeyRole.php b/module/Rest/src/Entity/ApiKeyRole.php index aefda970..99dbb627 100644 --- a/module/Rest/src/Entity/ApiKeyRole.php +++ b/module/Rest/src/Entity/ApiKeyRole.php @@ -29,6 +29,11 @@ class ApiKeyRole extends AbstractEntity return $this->meta; } + public function updateMeta(array $newMeta): void + { + $this->meta = $newMeta; + } + public function apiKey(): ApiKey { return $this->apiKey;