From 6c7799c90e8cf0fb696f00d23ce4b416b7ac219e Mon Sep 17 00:00:00 2001 From: Arthur de Moulins Date: Mon, 16 Mar 2026 12:37:47 +0100 Subject: [PATCH 1/4] refactor AccessControlEntry and interfaces: update UUID handling, add metadata support, and improve permission constants --- src/Entity/AccessControlEntry.php | 40 ++++++++++++++++------- src/Model/AccessControlEntryInterface.php | 4 +++ src/Security/PermissionInterface.php | 18 +++++----- src/Security/PermissionManager.php | 5 ++- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/Entity/AccessControlEntry.php b/src/Entity/AccessControlEntry.php index f01c5f2..5630c0e 100644 --- a/src/Entity/AccessControlEntry.php +++ b/src/Entity/AccessControlEntry.php @@ -5,7 +5,10 @@ namespace Alchemy\AclBundle\Entity; use Alchemy\AclBundle\Model\AccessControlEntryInterface; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Doctrine\UuidGenerator; +use Ramsey\Uuid\Doctrine\UuidType; use Ramsey\Uuid\Uuid; use Symfony\Component\Validator\Constraints as Assert; @@ -23,43 +26,46 @@ class AccessControlEntry implements AccessControlEntryInterface * @var Uuid */ #[ORM\Id] - #[ORM\Column(type: 'uuid', unique: true)] + #[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\CustomIdGenerator(class: \Ramsey\Uuid\Doctrine\UuidGenerator::class)] + #[ORM\CustomIdGenerator(class: UuidGenerator::class)] protected $id; - #[ORM\Column(type: 'smallint')] + #[ORM\Column(type: Types::SMALLINT)] protected int $userType = self::TYPE_USER_VALUE; - #[ORM\Column(type: 'string', length: 36, nullable: true)] + #[ORM\Column(type: Types::STRING, length: 36, nullable: true)] protected ?string $userId = null; /** * The object type name (i.e. publication). */ #[Assert\NotNull] - #[ORM\Column(type: 'string', length: 20)] + #[ORM\Column(type: Types::STRING, length: 20)] protected ?string $objectType = null; - #[ORM\Column(type: 'uuid', nullable: true)] + #[ORM\Column(type: UuidType::NAME, nullable: true)] protected ?string $objectId = null; - #[ORM\Column(type: 'integer')] + #[ORM\Column(type: Types::INTEGER)] protected int $mask = 0; + #[ORM\Column(type: Types::JSON, nullable: true)] + protected ?array $metadata = null; + /** * i.e. "p:b9ccf60e-9f08-4388-b703-2953c40cb0a7". */ - #[ORM\Column(type: 'string', length: 39, nullable: true)] + #[ORM\Column(type: Types::STRING, length: 39, nullable: true)] protected ?string $parentId = null; - #[ORM\Column(type: 'datetime')] - private readonly \DateTime $createdAt; + #[ORM\Column(type: Types::DATE_IMMUTABLE)] + private readonly \DateTimeImmutable $createdAt; public function __construct() { $this->id = Uuid::uuid4(); - $this->createdAt = new \DateTime(); + $this->createdAt = new \DateTimeImmutable(); } public static function getUserTypeFromString(string $type): int @@ -169,7 +175,7 @@ public function resetPermissions(): void $this->mask = 0; } - public function getCreatedAt(): \DateTime + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } @@ -193,4 +199,14 @@ public function setParentId(?string $parentId): void { $this->parentId = $parentId; } + + public function getMetadata(): array + { + return $this->metadata ?? []; + } + + public function setMetadata(array $metadata): void + { + $this->metadata = $metadata; + } } diff --git a/src/Model/AccessControlEntryInterface.php b/src/Model/AccessControlEntryInterface.php index 2626147..15506f4 100644 --- a/src/Model/AccessControlEntryInterface.php +++ b/src/Model/AccessControlEntryInterface.php @@ -52,6 +52,10 @@ public function removePermission(int $permission): void; public function resetPermissions(): void; + public function getMetadata(): array; + + public function setMetadata(array $metadata): void; + public function getParentId(): ?string; public function setParentId(?string $parentId): void; diff --git a/src/Security/PermissionInterface.php b/src/Security/PermissionInterface.php index 0da3805..1b34f0c 100644 --- a/src/Security/PermissionInterface.php +++ b/src/Security/PermissionInterface.php @@ -15,14 +15,15 @@ interface PermissionInterface public const int MASTER = 64; public const int OWNER = 128; public const int SHARE = 256; - public const int CHILD_CREATE = 512; - public const int CHILD_EDIT = 1024; - public const int CHILD_DELETE = 2048; - public const int CHILD_UNDELETE = 4096; - public const int CHILD_OPERATOR = 8192; - public const int CHILD_MASTER = 16384; - public const int CHILD_OWNER = 32768; - public const int CHILD_SHARE = 65536; + public const int CHILD_VIEW = 512; + public const int CHILD_CREATE = 1024; + public const int CHILD_EDIT = 2048; + public const int CHILD_DELETE = 4096; + public const int CHILD_UNDELETE = 8192; + public const int CHILD_OPERATOR = 16384; + public const int CHILD_MASTER = 32768; + public const int CHILD_OWNER = 65536; + public const int CHILD_SHARE = 131072; public const array PERMISSIONS = [ 'VIEW' => self::VIEW, @@ -34,6 +35,7 @@ interface PermissionInterface 'MASTER' => self::MASTER, 'OWNER' => self::OWNER, 'SHARE' => self::SHARE, + 'CHILD_VIEW' => self::CHILD_VIEW, 'CHILD_CREATE' => self::CHILD_CREATE, 'CHILD_EDIT' => self::CHILD_EDIT, 'CHILD_DELETE' => self::CHILD_DELETE, diff --git a/src/Security/PermissionManager.php b/src/Security/PermissionManager.php index 953d963..222a91a 100644 --- a/src/Security/PermissionManager.php +++ b/src/Security/PermissionManager.php @@ -46,7 +46,10 @@ public function isGranted(AclUserInterface $user, AclObjectInterface $object, ar return false; } - private function getAces(AclUserInterface $user, AclObjectInterface $object): array + /** + * @return AccessControlEntryInterface[] + */ + public function getAces(AclUserInterface $user, AclObjectInterface $object): array { $objectKey = $this->objectMapper->getObjectKey($object); $key = $this->getCacheKey(AccessControlEntryInterface::TYPE_USER_VALUE, $user->getId(), $objectKey, $object->getId()); From 3668093f0017f6ce63dcb494808a6e780d5d2d33 Mon Sep 17 00:00:00 2001 From: Arthur de Moulins Date: Tue, 17 Mar 2026 18:46:11 +0100 Subject: [PATCH 2/4] add resetCache method and enhance isGranted to support ownership grants --- src/Security/PermissionManager.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Security/PermissionManager.php b/src/Security/PermissionManager.php index 222a91a..8101e39 100644 --- a/src/Security/PermissionManager.php +++ b/src/Security/PermissionManager.php @@ -26,9 +26,14 @@ public function __construct( ) { } - public function isGranted(AclUserInterface $user, AclObjectInterface $object, array|int $permission): bool + public function resetCache(): void { - if ($object->getAclOwnerId() === $user->getId()) { + $this->cache = []; + } + + public function isGranted(AclUserInterface $user, AclObjectInterface $object, array|int $permission, bool $ownershipGrants = true): bool + { + if ($ownershipGrants && $object->getAclOwnerId() === $user->getId()) { return true; } From 1f1a5f6cfbeb2a6eb2a4b5882d16b9da3f664292 Mon Sep 17 00:00:00 2001 From: Arthur de Moulins Date: Tue, 24 Mar 2026 12:03:09 +0100 Subject: [PATCH 3/4] add metadata support to permission handling and update related methods --- src/Controller/PermissionController.php | 3 ++- src/Repository/DoctrinePermissionRepository.php | 2 ++ src/Repository/PermissionRepositoryInterface.php | 1 + src/Security/PermissionManager.php | 6 ++++-- src/Security/Voter/SetPermissionVoter.php | 4 ++-- src/Serializer/AceSerializer.php | 1 + 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Controller/PermissionController.php b/src/Controller/PermissionController.php index 89b72bd..824ad27 100644 --- a/src/Controller/PermissionController.php +++ b/src/Controller/PermissionController.php @@ -62,12 +62,13 @@ public function setAce(Request $request, AceSerializer $aceSerializer): Response $userType = $data['userType'] ?? null; $userId = $data['userId'] ?? null; $mask = (int) ($data['mask'] ?? 0); + $metadata = (array) ($data['metadata'] ?? []); $objectId = !empty($objectId) ? $objectId : null; $userType = AccessControlEntry::getUserTypeFromString($userType); - $ace = $this->permissionManager->updateOrCreateAce($userType, $userId, $objectType, $objectId, $mask); + $ace = $this->permissionManager->updateOrCreateAce($userType, $userId, $objectType, $objectId, $mask, $metadata); return new JsonResponse($aceSerializer->serialize($ace)); } diff --git a/src/Repository/DoctrinePermissionRepository.php b/src/Repository/DoctrinePermissionRepository.php index e993023..0e23500 100644 --- a/src/Repository/DoctrinePermissionRepository.php +++ b/src/Repository/DoctrinePermissionRepository.php @@ -114,6 +114,7 @@ public function updateOrCreateAce( string $objectType, ?string $objectId, int $mask, + array $metadata = [], ?string $parentId = null, bool $append = false, ): AccessControlEntryInterface { @@ -134,6 +135,7 @@ public function updateOrCreateAce( } else { $ace->setMask($mask); } + $ace->setMetadata($metadata); $this->em->persist($ace); $this->em->flush(); diff --git a/src/Repository/PermissionRepositoryInterface.php b/src/Repository/PermissionRepositoryInterface.php index 113f784..bc6601d 100644 --- a/src/Repository/PermissionRepositoryInterface.php +++ b/src/Repository/PermissionRepositoryInterface.php @@ -47,6 +47,7 @@ public function updateOrCreateAce( string $objectType, ?string $objectId, int $mask, + array $metadata = [], ?string $parentId = null, bool $append = false, ): AccessControlEntryInterface; diff --git a/src/Security/PermissionManager.php b/src/Security/PermissionManager.php index 8101e39..1e5b345 100644 --- a/src/Security/PermissionManager.php +++ b/src/Security/PermissionManager.php @@ -132,7 +132,7 @@ public function grantUserOnObject( $objectKey, $object->getId(), $permissions, - $parentId + parentId: $parentId ); } @@ -150,7 +150,7 @@ public function grantGroupOnObject( $objectKey, $object->getId(), $permissions, - $parentId + parentId: $parentId ); } @@ -182,6 +182,7 @@ public function updateOrCreateAce( string $objectType, ?string $objectId, int $permissions, + array $metadata = [], ?string $parentId = null, bool $append = false, ): ?AccessControlEntryInterface { @@ -191,6 +192,7 @@ public function updateOrCreateAce( $objectType, $objectId, $permissions, + $metadata, $parentId, $append ); diff --git a/src/Security/Voter/SetPermissionVoter.php b/src/Security/Voter/SetPermissionVoter.php index 9c2ae60..008d2eb 100644 --- a/src/Security/Voter/SetPermissionVoter.php +++ b/src/Security/Voter/SetPermissionVoter.php @@ -14,8 +14,8 @@ #[AutoconfigureTag(name: 'security.voter')] class SetPermissionVoter extends Voter { - public const ACL_READ = 'ACL_READ'; - public const ACL_WRITE = 'ACL_WRITE'; + public const string ACL_READ = 'ACL_READ'; + public const string ACL_WRITE = 'ACL_WRITE'; public function __construct(private readonly Security $security) { diff --git a/src/Serializer/AceSerializer.php b/src/Serializer/AceSerializer.php index 6869f69..a4f4f83 100644 --- a/src/Serializer/AceSerializer.php +++ b/src/Serializer/AceSerializer.php @@ -28,6 +28,7 @@ public function serialize(AccessControlEntryInterface $ace): array 'objectId' => $ace->getObjectId(), 'mask' => $ace->getMask(), 'parentId' => $ace->getParentId(), + 'metadata' => $ace->getMetadata(), ]; if (null !== $userId) { From 3fd9f56402d5aae7e4020f479130ce2d9bc71bd8 Mon Sep 17 00:00:00 2001 From: Arthur de Moulins Date: Mon, 4 May 2026 20:12:37 +0200 Subject: [PATCH 4/4] change createdAt column type from DATE_IMMUTABLE to DATETIME_IMMUTABLE --- src/Entity/AccessControlEntry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/AccessControlEntry.php b/src/Entity/AccessControlEntry.php index 5630c0e..77a4110 100644 --- a/src/Entity/AccessControlEntry.php +++ b/src/Entity/AccessControlEntry.php @@ -59,7 +59,7 @@ class AccessControlEntry implements AccessControlEntryInterface #[ORM\Column(type: Types::STRING, length: 39, nullable: true)] protected ?string $parentId = null; - #[ORM\Column(type: Types::DATE_IMMUTABLE)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] private readonly \DateTimeImmutable $createdAt; public function __construct()