From 080b7b89eb2d4bdf1ab3d13355673fda492048f5 Mon Sep 17 00:00:00 2001 From: rigonlucas Date: Wed, 21 Aug 2024 22:33:16 -0300 Subject: [PATCH] Bitwise access control --- .../Gateways/AccountRepositoryInterface.php | 2 + .../Application/User/Show/ShowUserUseCase.php | 7 +- .../Traits/Account/AccountEntityBuilder.php | 9 +++ .../User/Traits/HasUserEntityBuilder.php | 17 ++++- .../User/Traits/UserEntityAcessors.php | 2 +- core/Domain/Entities/User/UserEntity.php | 2 + .../Exceptions/InvalidRoleException.php | 8 ++ .../Permissions/Access/GeneralPermissions.php | 11 +++ core/Support/Permissions/Access/UserRoles.php | 10 +++ core/Support/Permissions/HasUserRoleTrait.php | 57 +++++++++++++++ .../0001_01_01_000000_create_users_table.php | 1 + .../Account/Repository/AccountRepository.php | 13 ++++ .../User/Repository/UserRepository.php | 10 ++- .../User/Create/CreateUserHandler.php | 4 +- .../User/Update/UpdateUserHandler.php | 12 +-- .../Unit/User/Entity/UserEntityRolesTest.php | 73 +++++++++++++++++++ 16 files changed, 224 insertions(+), 14 deletions(-) create mode 100644 core/Support/Exceptions/InvalidRoleException.php create mode 100644 core/Support/Permissions/Access/GeneralPermissions.php create mode 100644 core/Support/Permissions/Access/UserRoles.php create mode 100644 core/Support/Permissions/HasUserRoleTrait.php create mode 100644 tests/Unit/User/Entity/UserEntityRolesTest.php diff --git a/core/Application/Account/Commons/Gateways/AccountRepositoryInterface.php b/core/Application/Account/Commons/Gateways/AccountRepositoryInterface.php index be250b6..b0afc1d 100644 --- a/core/Application/Account/Commons/Gateways/AccountRepositoryInterface.php +++ b/core/Application/Account/Commons/Gateways/AccountRepositoryInterface.php @@ -6,6 +6,8 @@ interface AccountRepositoryInterface { + public function findByid(int $id): ?AccountEntity; + public function findByUuid(string $uuid): ?AccountEntity; public function findByAccessCode(string $code): ?AccountEntity; diff --git a/core/Application/User/Show/ShowUserUseCase.php b/core/Application/User/Show/ShowUserUseCase.php index f07f082..f04a252 100644 --- a/core/Application/User/Show/ShowUserUseCase.php +++ b/core/Application/User/Show/ShowUserUseCase.php @@ -2,6 +2,7 @@ namespace Core\Application\User\Show; +use Core\Application\Account\Commons\Gateways\AccountRepositoryInterface; use Core\Application\User\Commons\Exceptions\UserNotFountException; use Core\Application\User\Commons\Gateways\UserRepositoryInterface; use Core\Domain\Entities\User\UserEntity; @@ -12,7 +13,8 @@ class ShowUserUseCase { public function __construct( private readonly FrameworkContract $framework, - private readonly UserRepositoryInterface $userRepository + private readonly UserRepositoryInterface $userRepository, + private readonly AccountRepositoryInterface $accountRepository ) { } @@ -29,6 +31,9 @@ public function execute(string $uuid): UserEntity ); } + $accountEntity = $this->accountRepository->findByid($userEntity->getAccount()->getId()); + $userEntity->setAccount($accountEntity); + return $userEntity; } } diff --git a/core/Domain/Entities/Account/Traits/Account/AccountEntityBuilder.php b/core/Domain/Entities/Account/Traits/Account/AccountEntityBuilder.php index 1533364..697b250 100644 --- a/core/Domain/Entities/Account/Traits/Account/AccountEntityBuilder.php +++ b/core/Domain/Entities/Account/Traits/Account/AccountEntityBuilder.php @@ -34,4 +34,13 @@ public static function forDetail( return $account; } + + public static function forIdentify( + int $id + ): AccountEntity { + $account = new AccountEntity(); + $account->setId($id); + + return $account; + } } diff --git a/core/Domain/Entities/User/Traits/HasUserEntityBuilder.php b/core/Domain/Entities/User/Traits/HasUserEntityBuilder.php index 8c8c43f..4ba445a 100644 --- a/core/Domain/Entities/User/Traits/HasUserEntityBuilder.php +++ b/core/Domain/Entities/User/Traits/HasUserEntityBuilder.php @@ -23,6 +23,7 @@ public static function forCreate( ?AccountEntity $account, UuidInterface $uuid = null, ?DateTimeInterface $birthday = null, + int $role = 0 ): UserEntity { $userEntity = new UserEntity(); $userEntity->setBirthday($birthday); @@ -43,14 +44,18 @@ public static function forDetail( string $name, string $email, UuidInterface $uuid, - ?DateTimeInterface $birthday = null + AccountEntity $account, + ?DateTimeInterface $birthday = null, + int $role = 0 ): UserEntity { $userEntity = new UserEntity(); $userEntity->setId($id); $userEntity->setName($name); $userEntity->setEmail(new EmailValueObject($email, false)); $userEntity->setBirthday($birthday); + $userEntity->setAccount($account); $userEntity->setUuid($uuid); + $userEntity->setRole($role); return $userEntity; } @@ -64,7 +69,8 @@ public static function forUpdate( string $email, #[SensitiveParameter] string $password, - ?DateTimeInterface $birthday = null + ?DateTimeInterface $birthday = null, + int $role = 0 ): UserEntity { $userEntity = new UserEntity(); $userEntity->setBirthday($birthday); @@ -72,22 +78,25 @@ public static function forUpdate( $userEntity->setName($name); $userEntity->setEmail(new EmailValueObject($email, false)); $userEntity->setPassword($password); + $userEntity->setRole($role); return $userEntity; } - public static function forDelete(int $id): UserEntity + public static function forDelete(int $id, int $role = 0): UserEntity { $userEntity = new UserEntity(); $userEntity->setId($id); + $userEntity->setRole($role); return $userEntity; } - public static function forIdentify(int $id): UserEntity + public static function forIdentify(int $id, int $role = 0): UserEntity { $userEntity = new UserEntity(); $userEntity->setId($id); + $userEntity->setRole($role); return $userEntity; } diff --git a/core/Domain/Entities/User/Traits/UserEntityAcessors.php b/core/Domain/Entities/User/Traits/UserEntityAcessors.php index 7c1671d..7dc0db5 100644 --- a/core/Domain/Entities/User/Traits/UserEntityAcessors.php +++ b/core/Domain/Entities/User/Traits/UserEntityAcessors.php @@ -48,7 +48,7 @@ public function getBirthday(): ?DateTimeInterface return $this->birthday; } - public function setBirthday(DateTimeInterface $birthday): self + public function setBirthday(?DateTimeInterface $birthday): self { $this->birthday = $birthday; return $this; diff --git a/core/Domain/Entities/User/UserEntity.php b/core/Domain/Entities/User/UserEntity.php index 23786ce..a6115eb 100644 --- a/core/Domain/Entities/User/UserEntity.php +++ b/core/Domain/Entities/User/UserEntity.php @@ -6,6 +6,7 @@ use Core\Domain\Entities\User\Traits\HasUserEntityBuilder; use Core\Domain\Entities\User\Traits\UserEntityAcessors; use Core\Domain\ValueObjects\EmailValueObject; +use Core\Support\Permissions\HasUserRoleTrait; use DateTime; use DateTimeInterface; use Ramsey\Uuid\UuidInterface; @@ -14,6 +15,7 @@ class UserEntity { use HasUserEntityBuilder; use UserEntityAcessors; + use HasUserRoleTrait; private ?int $id = null; private string $name; diff --git a/core/Support/Exceptions/InvalidRoleException.php b/core/Support/Exceptions/InvalidRoleException.php new file mode 100644 index 0000000..f10f010 --- /dev/null +++ b/core/Support/Exceptions/InvalidRoleException.php @@ -0,0 +1,8 @@ +permissions & $permission) === $permission; + } + + public function getPermissions(): int + { + return $this->permissions; + } + + public function setPermissions(int $permissions): void + { + $this->permissions = $permissions; + } + + public function getRole(): int + { + return $this->role; + } + + public function setRole(int $role): void + { + $this->role = $role; + $this->permissions = $role; + } + + public function hasRolePermission(int $permission): bool + { + return ($this->role & $permission) === $permission; + } + + /** + * @throws InvalidRoleException + */ + public function getRoleName(): string + { + return match ($this->role) { + UserRoles::ADMIN => 'ADMIN', + UserRoles::EDITOR => 'EDITOR', + UserRoles::VIEWER => 'VIEWER', + default => throw new InvalidRoleException("Invalid role", ResponseStatusCodeEnum::FORBIDDEN->value), + }; + } +} \ No newline at end of file diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index e9314d7..8196145 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -17,6 +17,7 @@ public function up(): void ->default(null) ->constrained() ->onDelete('cascade'); + $table->integer('role')->default(0); $table->string('name'); $table->string('email')->unique(); $table->date('birthday')->nullable(); diff --git a/infra/Database/Account/Repository/AccountRepository.php b/infra/Database/Account/Repository/AccountRepository.php index 693276a..6281867 100644 --- a/infra/Database/Account/Repository/AccountRepository.php +++ b/infra/Database/Account/Repository/AccountRepository.php @@ -10,6 +10,19 @@ class AccountRepository implements AccountRepositoryInterface { + public function findByid(int $id): ?AccountEntity + { + $accountModel = Account::query()->where('id', $id)->first(); + if (!$accountModel) { + return null; + } + + return AccountEntity::forDetail( + id: $accountModel->id, + name: $accountModel->name, + uuid: $accountModel->uuid + ); + } public function findByUuid(string $uuid): ?AccountEntity { diff --git a/infra/Database/User/Repository/UserRepository.php b/infra/Database/User/Repository/UserRepository.php index 7d56260..01035b0 100644 --- a/infra/Database/User/Repository/UserRepository.php +++ b/infra/Database/User/Repository/UserRepository.php @@ -4,12 +4,17 @@ use App\Models\User; use Core\Application\User\Commons\Gateways\UserRepositoryInterface; +use Core\Domain\Entities\Account\AccountEntity; use Core\Domain\Entities\User\UserEntity; +use Core\Support\Exceptions\InvalidEmailException; use DateTime; use Infra\Services\Framework\FrameworkService; class UserRepository implements UserRepositoryInterface { + /** + * @throws InvalidEmailException + */ public function findById(int $id): ?UserEntity { $userModel = User::query() @@ -24,6 +29,7 @@ public function findById(int $id): ?UserEntity name: $userModel->name, email: $userModel->email, uuid: FrameworkService::getInstance()->uuid()->uuidFromString($userModel->uuid), + account: AccountEntity::forIdentify($userModel->account_id), birthday: new DateTime($userModel->birthday) ); } @@ -31,7 +37,7 @@ public function findById(int $id): ?UserEntity public function findByUuid(string $uuid): ?UserEntity { $userModel = User::query() - ->select(['id', 'name', 'email', 'birthday', 'uuid']) + ->select(['id', 'name', 'email', 'birthday', 'uuid', 'account_id']) ->where('uuid', '=', $uuid) ->first(); if (!$userModel) { @@ -43,6 +49,7 @@ public function findByUuid(string $uuid): ?UserEntity name: $userModel->name, email: $userModel->email, uuid: FrameworkService::getInstance()->uuid()->uuidFromString($userModel->uuid), + account: AccountEntity::forIdentify($userModel->account_id), birthday: new DateTime($userModel->birthday), ); } @@ -62,6 +69,7 @@ public function findByEmail(string $email): ?UserEntity name: $userModel->name, email: $userModel->email, uuid: FrameworkService::getInstance()->uuid()->uuidFromString($userModel->uuid), + account: AccountEntity::forIdentify($userModel->account_id), birthday: new DateTime($userModel->birthday) ); } diff --git a/infra/Handlers/UseCases/User/Create/CreateUserHandler.php b/infra/Handlers/UseCases/User/Create/CreateUserHandler.php index ecc591b..ca56212 100644 --- a/infra/Handlers/UseCases/User/Create/CreateUserHandler.php +++ b/infra/Handlers/UseCases/User/Create/CreateUserHandler.php @@ -42,8 +42,8 @@ public function handle(CreateUserInput $createUserInput, AccountInput $accountIn accountRepository: $this->accountRepositoryInterface ); - $accountInput->setUserNane($userEntity->getName()); - $accountInput->setUserId($userEntity->getId()); + $accountInput->setUserNane(userNane: $userEntity->getName()); + $accountInput->setUserId(userId: $userEntity->getId()); $accountEntity = $createAccountUseCase->execute(input: $accountInput); diff --git a/infra/Handlers/UseCases/User/Update/UpdateUserHandler.php b/infra/Handlers/UseCases/User/Update/UpdateUserHandler.php index efb9977..d5388c2 100644 --- a/infra/Handlers/UseCases/User/Update/UpdateUserHandler.php +++ b/infra/Handlers/UseCases/User/Update/UpdateUserHandler.php @@ -9,12 +9,12 @@ use Core\Services\Framework\FrameworkContract; use Core\Support\Exceptions\OutputErrorException; -class UpdateUserHandler +readonly class UpdateUserHandler { public function __construct( - private readonly UserCommandInterface $userCommand, - private readonly UserRepositoryInterface $userRepository, - private readonly FrameworkContract $frameworkService + private UserCommandInterface $userCommand, + private UserRepositoryInterface $userRepository, + private FrameworkContract $frameworkService ) { } @@ -29,6 +29,8 @@ public function handle(UpdateUserInput $input): UpdateUserOutput $this->userRepository, $this->userCommand ); - return new UpdateUserOutput($useCase->execute($input)); + $userEntity = $useCase->execute($input); + + return new UpdateUserOutput($userEntity); } } diff --git a/tests/Unit/User/Entity/UserEntityRolesTest.php b/tests/Unit/User/Entity/UserEntityRolesTest.php new file mode 100644 index 0000000..0bd6ffc --- /dev/null +++ b/tests/Unit/User/Entity/UserEntityRolesTest.php @@ -0,0 +1,73 @@ +assertTrue($user->hasRolePermission(UserRoles::ADMIN)); + $this->assertTrue($user->hasPermission(GeneralPermissions::READ)); + $this->assertTrue($user->hasPermission(GeneralPermissions::WRITE)); + $this->assertTrue($user->hasPermission(GeneralPermissions::EXECUTE)); + $this->assertTrue($user->hasPermission(GeneralPermissions::DELETE)); + } + + public function test_user_entity_roles_as_editor() + { + $user = UserEntity::forDetail( + id: 1, + name: 'John Doe', + email: 'email@email.com', + uuid: Uuid::uuid7(), + account: AccountEntity::forIdentify(1), + role: UserRoles::EDITOR + ); + + $this->assertTrue($user->hasRolePermission(UserRoles::EDITOR)); + $this->assertTrue($user->hasPermission(GeneralPermissions::READ)); + $this->assertTrue($user->hasPermission(GeneralPermissions::WRITE)); + + $this->assertFalse($user->hasPermission(GeneralPermissions::EXECUTE)); + $this->assertFalse($user->hasPermission(GeneralPermissions::DELETE)); + } + + + public function test_user_entity_roles_as_viewer() + { + $user = UserEntity::forDetail( + id: 1, + name: 'John Doe', + email: 'email@email.com', + uuid: Uuid::uuid7(), + account: AccountEntity::forIdentify(1), + role: UserRoles::VIEWER + ); + + $this->assertTrue($user->hasRolePermission(UserRoles::VIEWER)); + $this->assertTrue($user->hasPermission(GeneralPermissions::READ)); + + $this->assertFalse($user->hasPermission(GeneralPermissions::WRITE)); + $this->assertFalse($user->hasPermission(GeneralPermissions::EXECUTE)); + $this->assertFalse($user->hasPermission(GeneralPermissions::DELETE)); + } +}