From 7e6882960e69aaf3c1bf8ded90c907226d0334ff Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 2 Jan 2021 19:35:16 +0100 Subject: [PATCH] Added a system to set roles to API keys --- composer.json | 1 - data/migrations/Version20210102174433.php | 52 +++++++++++++++++++ .../Shlinkio.Shlink.Rest.Entity.ApiKey.php | 5 ++ ...Shlinkio.Shlink.Rest.Entity.ApiKeyRole.php | 42 +++++++++++++++ module/Rest/src/Entity/ApiKey.php | 7 ++- module/Rest/src/Entity/ApiKeyRole.php | 31 +++++++++++ 6 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 data/migrations/Version20210102174433.php create mode 100644 module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKeyRole.php create mode 100644 module/Rest/src/Entity/ApiKeyRole.php diff --git a/composer.json b/composer.json index e0333d85..09b26c42 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,6 @@ "cakephp/chronos": "^2.0", "cocur/slugify": "^4.0", "doctrine/cache": "^1.9", - "doctrine/dbal": "^2.10", "doctrine/migrations": "^3.0.2", "doctrine/orm": "^2.8", "endroid/qr-code": "^3.6", diff --git a/data/migrations/Version20210102174433.php b/data/migrations/Version20210102174433.php new file mode 100644 index 00000000..95ee62fe --- /dev/null +++ b/data/migrations/Version20210102174433.php @@ -0,0 +1,52 @@ +skipIf($schema->hasTable(self::TABLE_NAME)); + + $table = $schema->createTable(self::TABLE_NAME); + $table->addColumn('id', Types::BIGINT, [ + 'unsigned' => true, + 'autoincrement' => true, + 'notnull' => true, + ]); + $table->setPrimaryKey(['id']); + + $table->addColumn('role_name', Types::STRING, [ + 'length' => 256, + 'notnull' => true, + ]); + $table->addColumn('meta', Types::JSON, [ + 'notnull' => true, + ]); + + $table->addColumn('api_key_id', Types::BIGINT, [ + 'unsigned' => true, + 'notnull' => true, + ]); + $table->addForeignKeyConstraint('api_keys', ['api_key_id'], ['id'], [ + 'onDelete' => 'CASCADE', + 'onUpdate' => 'RESTRICT', + ]); + $table->addUniqueIndex(['role_name', 'api_key_id'], 'UQ_role_plus_api_key'); + } + + public function down(Schema $schema): void + { + $this->skipIf(! $schema->hasTable(self::TABLE_NAME)); + $schema->getTable(self::TABLE_NAME)->dropIndex('UQ_role_plus_api_key'); + $schema->dropTable(self::TABLE_NAME); + } +} 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 a5084cee..2cb2df2b 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 @@ -8,6 +8,7 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; +use Shlinkio\Shlink\Rest\Entity\ApiKeyRole; use function Shlinkio\Shlink\Core\determineTableName; @@ -34,4 +35,8 @@ return static function (ClassMetadata $metadata, array $emConfig): void { $builder->createField('enabled', Types::BOOLEAN) ->build(); + + $builder->createOneToMany('roles', ApiKeyRole::class) + ->mappedBy('apiKey') + ->build(); }; diff --git a/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKeyRole.php b/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKeyRole.php new file mode 100644 index 00000000..9c6355e3 --- /dev/null +++ b/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKeyRole.php @@ -0,0 +1,42 @@ +setTable(determineTableName('api_key_roles', $emConfig)); + + $builder->createField('id', Types::BIGINT) + ->makePrimaryKey() + ->generatedValue('IDENTITY') + ->option('unsigned', true) + ->build(); + + $builder->createField('roleName', Types::STRING) + ->columnName('role_name') + ->length(256) + ->nullable(false) + ->build(); + + $builder->createField('meta', Types::JSON) + ->columnName('meta') + ->nullable(false) + ->build(); + + $builder->createManyToOne('apiKey', ApiKey::class) + ->addJoinColumn('api_key_id', 'id', false, false, 'CASCADE') + ->cascadePersist() + ->build(); + + $builder->addUniqueConstraint(['role_name', 'api_key_id'], 'UQ_role_plus_api_key'); +}; diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 210c5b3a..bf1baccf 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Entity; use Cake\Chronos\Chronos; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Happyr\DoctrineSpecification\Spec; use Happyr\DoctrineSpecification\Specification\Specification; use Ramsey\Uuid\Uuid; @@ -15,12 +17,15 @@ class ApiKey extends AbstractEntity private string $key; private ?Chronos $expirationDate = null; private bool $enabled; + /** @var Collection|ApiKeyRole[] */ + private Collection $roles; public function __construct(?Chronos $expirationDate = null) { $this->key = Uuid::uuid4()->toString(); $this->expirationDate = $expirationDate; $this->enabled = true; + $this->roles = new ArrayCollection(); } public function getExpirationDate(): ?Chronos @@ -62,8 +67,6 @@ class ApiKey extends AbstractEntity return $this->key; } - /** - */ public function spec(): Specification { return Spec::andX(); diff --git a/module/Rest/src/Entity/ApiKeyRole.php b/module/Rest/src/Entity/ApiKeyRole.php new file mode 100644 index 00000000..6af3d328 --- /dev/null +++ b/module/Rest/src/Entity/ApiKeyRole.php @@ -0,0 +1,31 @@ +roleName = $roleName; + $this->meta = $meta; + $this->apiKey = $apiKey; + } + + public function name(): string + { + return $this->roleName; + } + + public function meta(): array + { + return $this->meta; + } +}