mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Update Workflow Permission Handling
Refactor the `WorkflowRelatedEntityPermissionHelper` to enhance permission checks for workflow-related entities. This includes updating methods, improving test coverage, and incorporating `MockClock` for date-sensitive operations.
This commit is contained in:
parent
c99dda0126
commit
829fb669fe
@ -46,24 +46,27 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
|
|||||||
|
|
||||||
public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool
|
public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool
|
||||||
{
|
{
|
||||||
// Retrieve the related accompanying course document
|
// Retrieve the related entity
|
||||||
$entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);
|
$entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);
|
||||||
|
|
||||||
// Determine the attribute to pass to AccompanyingCourseDocumentVoter
|
// Determine the attribute to pass to the voter for argument
|
||||||
$voterAttribute = $this->attributeToRole($attribute);
|
$voterAttribute = $this->attributeToRole($attribute);
|
||||||
|
|
||||||
if (false === $this->security->isGranted($voterAttribute, $entity)) {
|
$regularPermission = $this->security->isGranted($voterAttribute, $entity);
|
||||||
return false;
|
|
||||||
|
if (!$this->canBeAssociatedWithWorkflow()) {
|
||||||
|
return $regularPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StoredObjectRoleEnum::SEE !== $attribute && $this->canBeAssociatedWithWorkflow()) {
|
$workflowPermission = match ($attribute) {
|
||||||
if (null === $this->workflowDocumentService) {
|
StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($entity),
|
||||||
throw new \LogicException('Provide a workflow document service');
|
StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($entity),
|
||||||
}
|
};
|
||||||
|
|
||||||
return $this->workflowDocumentService->notBlockedByWorkflow($entity);
|
return match ($workflowPermission) {
|
||||||
}
|
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true,
|
||||||
|
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false,
|
||||||
return true;
|
WorkflowRelatedEntityPermissionHelper::ABSTAIN => $regularPermission,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,11 @@ use Chill\DocStoreBundle\Entity\StoredObject;
|
|||||||
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
|
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
|
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter;
|
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter;
|
||||||
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
|
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,19 +29,14 @@ use Symfony\Component\Security\Core\Security;
|
|||||||
*/
|
*/
|
||||||
class AbstractStoredObjectVoterTest extends TestCase
|
class AbstractStoredObjectVoterTest extends TestCase
|
||||||
{
|
{
|
||||||
private AssociatedEntityToStoredObjectInterface $repository;
|
use ProphecyTrait;
|
||||||
private Security $security;
|
|
||||||
private WorkflowRelatedEntityPermissionHelper $workflowDocumentService;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
private function buildStoredObjectVoter(
|
||||||
{
|
bool $canBeAssociatedWithWorkflow,
|
||||||
$this->repository = $this->createMock(AssociatedEntityToStoredObjectInterface::class);
|
AssociatedEntityToStoredObjectInterface $repository,
|
||||||
$this->security = $this->createMock(Security::class);
|
Security $security,
|
||||||
$this->workflowDocumentService = $this->createMock(WorkflowRelatedEntityPermissionHelper::class);
|
?WorkflowRelatedEntityPermissionHelper $workflowDocumentService = null,
|
||||||
}
|
): AbstractStoredObjectVoter {
|
||||||
|
|
||||||
private function buildStoredObjectVoter(bool $canBeAssociatedWithWorkflow, AssociatedEntityToStoredObjectInterface $repository, Security $security, ?WorkflowRelatedEntityPermissionHelper $workflowDocumentService = null): AbstractStoredObjectVoter
|
|
||||||
{
|
|
||||||
// Anonymous class extending the abstract class
|
// Anonymous class extending the abstract class
|
||||||
return new class ($canBeAssociatedWithWorkflow, $repository, $security, $workflowDocumentService) extends AbstractStoredObjectVoter {
|
return new class ($canBeAssociatedWithWorkflow, $repository, $security, $workflowDocumentService) extends AbstractStoredObjectVoter {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@ -74,95 +70,89 @@ class AbstractStoredObjectVoterTest extends TestCase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setupMockObjects(): array
|
|
||||||
{
|
|
||||||
$user = new User();
|
|
||||||
$token = $this->createMock(TokenInterface::class);
|
|
||||||
$subject = new StoredObject();
|
|
||||||
$entity = new \stdClass();
|
|
||||||
|
|
||||||
return [$user, $token, $subject, $entity];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setupMocksForVoteOnAttribute(User $user, TokenInterface $token, bool $isGrantedForEntity, object $entity, bool $workflowAllowed): void
|
|
||||||
{
|
|
||||||
// Set up token to return user
|
|
||||||
$token->method('getUser')->willReturn($user);
|
|
||||||
|
|
||||||
// Mock the return of an AccompanyingCourseDocument by the repository
|
|
||||||
$this->repository->method('findAssociatedEntityToStoredObject')->willReturn($entity);
|
|
||||||
|
|
||||||
// Mock scenario where user is allowed to see_details of the AccompanyingCourseDocument
|
|
||||||
$this->security->method('isGranted')->willReturn($isGrantedForEntity);
|
|
||||||
|
|
||||||
// Mock case where user is blocked or not by workflow
|
|
||||||
$this->workflowDocumentService->method('notBlockedByWorkflow')->willReturn($workflowAllowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSupportsOnAttribute(): void
|
public function testSupportsOnAttribute(): void
|
||||||
{
|
{
|
||||||
[$user, $token, $subject, $entity] = $this->setupMockObjects();
|
$voter = $this->buildStoredObjectVoter(false, new DummyRepository(new \stdClass()), $this->prophesize(Security::class)->reveal(), null);
|
||||||
|
|
||||||
// Setup mocks for voteOnAttribute method
|
self::assertTrue($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject()));
|
||||||
$this->setupMocksForVoteOnAttribute($user, $token, true, $entity, true);
|
|
||||||
$voter = $this->buildStoredObjectVoter(true, $this->repository, $this->security, $this->workflowDocumentService);
|
|
||||||
|
|
||||||
self::assertTrue($voter->supports(StoredObjectRoleEnum::SEE, $subject));
|
$voter = $this->buildStoredObjectVoter(false, new DummyRepository(new User()), $this->prophesize(Security::class)->reveal(), null);
|
||||||
|
|
||||||
|
self::assertFalse($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject()));
|
||||||
|
|
||||||
|
$voter = $this->buildStoredObjectVoter(false, new DummyRepository(null), $this->prophesize(Security::class)->reveal(), null);
|
||||||
|
|
||||||
|
self::assertFalse($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testVoteOnAttributeAllowedAndWorkflowAllowed(): void
|
/**
|
||||||
{
|
* @dataProvider dataProviderVoteOnAttribute
|
||||||
[$user, $token, $subject, $entity] = $this->setupMockObjects();
|
*/
|
||||||
|
public function testVoteOnAttribute(
|
||||||
|
StoredObjectRoleEnum $attribute,
|
||||||
|
bool $expected,
|
||||||
|
bool $canBeAssociatedWithWorkflow,
|
||||||
|
bool $isGrantedRegularPermission,
|
||||||
|
?string $isGrantedWorkflowPermissionRead,
|
||||||
|
?string $isGrantedWorkflowPermissionWrite,
|
||||||
|
string $message,
|
||||||
|
): void {
|
||||||
|
$storedObject = new StoredObject();
|
||||||
|
$dummyRepository = new DummyRepository($related = new \stdClass());
|
||||||
|
$token = new UsernamePasswordToken(new User(), 'dummy');
|
||||||
|
|
||||||
// Setup mocks for voteOnAttribute method
|
$security = $this->prophesize(Security::class);
|
||||||
$this->setupMocksForVoteOnAttribute($user, $token, true, $entity, true);
|
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
|
||||||
$voter = $this->buildStoredObjectVoter(true, $this->repository, $this->security, $this->workflowDocumentService);
|
|
||||||
|
|
||||||
// The voteOnAttribute method should return True when workflow is allowed
|
$workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class);
|
||||||
self::assertTrue($voter->voteOnAttribute(StoredObjectRoleEnum::SEE, $subject, $token));
|
if (null !== $isGrantedWorkflowPermissionRead) {
|
||||||
|
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)
|
||||||
|
->willReturn($isGrantedWorkflowPermissionRead)->shouldBeCalled();
|
||||||
|
} else {
|
||||||
|
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)->shouldNotBeCalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testVoteOnAttributeNotAllowed(): void
|
if (null !== $isGrantedWorkflowPermissionWrite) {
|
||||||
{
|
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($related)
|
||||||
[$user, $token, $subject, $entity] = $this->setupMockObjects();
|
->willReturn($isGrantedWorkflowPermissionWrite)->shouldBeCalled();
|
||||||
|
} else {
|
||||||
// Setup mocks for voteOnAttribute method where isGranted() returns false
|
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($related)->shouldNotBeCalled();
|
||||||
$this->setupMocksForVoteOnAttribute($user, $token, false, $entity, true);
|
|
||||||
$voter = $this->buildStoredObjectVoter(true, $this->repository, $this->security, $this->workflowDocumentService);
|
|
||||||
|
|
||||||
// The voteOnAttribute method should return True when workflow is allowed
|
|
||||||
self::assertFalse($voter->voteOnAttribute(StoredObjectRoleEnum::SEE, $subject, $token));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testVoteOnAttributeAllowedWorkflowNotAllowed(): void
|
$voter = $this->buildStoredObjectVoter($canBeAssociatedWithWorkflow, $dummyRepository, $security->reveal(), $workflowRelatedEntityPermissionHelper->reveal());
|
||||||
{
|
self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message);
|
||||||
[$user, $token, $subject, $entity] = $this->setupMockObjects();
|
|
||||||
|
|
||||||
// Setup mocks for voteOnAttribute method
|
|
||||||
$this->setupMocksForVoteOnAttribute($user, $token, true, $entity, false);
|
|
||||||
$voter = $this->buildStoredObjectVoter(true, $this->repository, $this->security, $this->workflowDocumentService);
|
|
||||||
|
|
||||||
// Test voteOnAttribute method
|
|
||||||
$attribute = StoredObjectRoleEnum::EDIT;
|
|
||||||
$result = $voter->voteOnAttribute($attribute, $subject, $token);
|
|
||||||
|
|
||||||
// Assert that access is denied when workflow is not allowed
|
|
||||||
$this->assertFalse($result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testVoteOnAttributeAllowedWorkflowAllowedToSeeDocument(): void
|
public static function dataProviderVoteOnAttribute(): iterable
|
||||||
{
|
{
|
||||||
[$user, $token, $subject, $entity] = $this->setupMockObjects();
|
// not associated on a workflow
|
||||||
|
yield [StoredObjectRoleEnum::SEE, true, false, true, null, null, 'not associated on a workflow, granted by regular access, must not rely on helper'];
|
||||||
|
yield [StoredObjectRoleEnum::SEE, false, false, false, null, null, 'not associated on a workflow, denied by regular access, must not rely on helper'];
|
||||||
|
|
||||||
// Setup mocks for voteOnAttribute method
|
// associated on a workflow, read operation
|
||||||
$this->setupMocksForVoteOnAttribute($user, $token, true, $entity, false);
|
yield [StoredObjectRoleEnum::SEE, true, true, true, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, null, 'associated on a workflow, read by regular, force grant by workflow, ask for read, should be granted'];
|
||||||
$voter = $this->buildStoredObjectVoter(true, $this->repository, $this->security, $this->workflowDocumentService);
|
yield [StoredObjectRoleEnum::SEE, true, true, true, WorkflowRelatedEntityPermissionHelper::ABSTAIN, null, 'associated on a workflow, read by regular, abstain by workflow, ask for read, should be granted'];
|
||||||
|
yield [StoredObjectRoleEnum::SEE, false, true, true, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, null, 'associated on a workflow, read by regular, force grant by workflow, ask for read, should be denied'];
|
||||||
|
yield [StoredObjectRoleEnum::SEE, true, true, false, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, null, 'associated on a workflow, denied read by regular, force grant by workflow, ask for read, should be granted'];
|
||||||
|
yield [StoredObjectRoleEnum::SEE, false, true, false, WorkflowRelatedEntityPermissionHelper::ABSTAIN, null, 'associated on a workflow, denied read by regular, abstain by workflow, ask for read, should be granted'];
|
||||||
|
yield [StoredObjectRoleEnum::SEE, false, true, false, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, null, 'associated on a workflow, denied read by regular, force grant by workflow, ask for read, should be denied'];
|
||||||
|
|
||||||
// Test voteOnAttribute method
|
// association on a workflow, write operation
|
||||||
$attribute = StoredObjectRoleEnum::SEE;
|
yield [StoredObjectRoleEnum::EDIT, true, true, true, null, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, 'associated on a workflow, write by regular, force grant by workflow, ask for write, should be granted'];
|
||||||
$result = $voter->voteOnAttribute($attribute, $subject, $token);
|
yield [StoredObjectRoleEnum::EDIT, true, true, true, null, WorkflowRelatedEntityPermissionHelper::ABSTAIN, 'associated on a workflow, write by regular, abstain by workflow, ask for write, should be granted'];
|
||||||
|
yield [StoredObjectRoleEnum::EDIT, false, true, true, null, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, 'associated on a workflow, write by regular, force grant by workflow, ask for write, should be denied'];
|
||||||
// Assert that access is denied when workflow is not allowed
|
yield [StoredObjectRoleEnum::EDIT, true, true, false, null, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, 'associated on a workflow, denied write by regular, force grant by workflow, ask for write, should be granted'];
|
||||||
$this->assertTrue($result);
|
yield [StoredObjectRoleEnum::EDIT, false, true, false, null, WorkflowRelatedEntityPermissionHelper::ABSTAIN, 'associated on a workflow, denied write by regular, abstain by workflow, ask for write, should be granted'];
|
||||||
|
yield [StoredObjectRoleEnum::EDIT, false, true, false, null, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, 'associated on a workflow, denied write by regular, force grant by workflow, ask for write, should be denied'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyRepository implements AssociatedEntityToStoredObjectInterface
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ?object $relatedEntity) {}
|
||||||
|
|
||||||
|
public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object
|
||||||
|
{
|
||||||
|
return $this->relatedEntity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
|
|||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
|
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
|
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
|
||||||
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||||
@ -23,6 +22,7 @@ use Chill\PersonBundle\Entity\Person;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\Clock\MockClock;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Workflow\DefinitionBuilder;
|
use Symfony\Component\Workflow\DefinitionBuilder;
|
||||||
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
|
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
|
||||||
@ -41,67 +41,85 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
|
|||||||
use ProphecyTrait;
|
use ProphecyTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideDataNotBlockByWorkflow
|
* @dataProvider provideDataAllowedByWorkflowReadOperation
|
||||||
|
*
|
||||||
|
* @param list<EntityWorkflow> $entityWorkflows
|
||||||
*/
|
*/
|
||||||
public function testNotBlockByWorkflow(EntityWorkflow $entityWorkflow, User $user, bool $expected, string $message): void
|
public function testAllowedByWorkflowRead(
|
||||||
{
|
array $entityWorkflows,
|
||||||
|
User $user,
|
||||||
|
string $expected,
|
||||||
|
?\DateTimeImmutable $atDate,
|
||||||
|
string $message,
|
||||||
|
): void {
|
||||||
// all entities must have this workflow name, so we are ok to set it here
|
// all entities must have this workflow name, so we are ok to set it here
|
||||||
|
foreach ($entityWorkflows as $entityWorkflow) {
|
||||||
$entityWorkflow->setWorkflowName('dummy');
|
$entityWorkflow->setWorkflowName('dummy');
|
||||||
$object = new \stdClass();
|
}
|
||||||
$helper = $this->buildHelper($object, $entityWorkflow, $user);
|
$helper = $this->buildHelper($entityWorkflows, $user, $atDate);
|
||||||
|
|
||||||
self::assertEquals($expected, $helper->notBlockedByWorkflow($entityWorkflow), $message);
|
self::assertEquals($expected, $helper->isAllowedByWorkflowForReadOperation(new \stdClass()), $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideDataAllowedByWorkflow
|
* @dataProvider provideDataAllowedByWorkflowWriteOperation
|
||||||
|
*
|
||||||
|
* @param list<EntityWorkflow> $entityWorkflows
|
||||||
*/
|
*/
|
||||||
public function testAllowedByWorkflow(EntityWorkflow $entityWorkflow, User $user, bool $expected, string $message): void
|
public function testAllowedByWorkflowWrite(
|
||||||
{
|
array $entityWorkflows,
|
||||||
|
User $user,
|
||||||
|
string $expected,
|
||||||
|
?\DateTimeImmutable $atDate,
|
||||||
|
string $message,
|
||||||
|
): void {
|
||||||
// all entities must have this workflow name, so we are ok to set it here
|
// all entities must have this workflow name, so we are ok to set it here
|
||||||
|
foreach ($entityWorkflows as $entityWorkflow) {
|
||||||
$entityWorkflow->setWorkflowName('dummy');
|
$entityWorkflow->setWorkflowName('dummy');
|
||||||
$object = new \stdClass();
|
}
|
||||||
$helper = $this->buildHelper($object, $entityWorkflow, $user);
|
$helper = $this->buildHelper($entityWorkflows, $user, $atDate);
|
||||||
|
|
||||||
self::assertEquals($expected, $helper->isAllowedByWorkflow($entityWorkflow), $message);
|
self::assertEquals($expected, $helper->isAllowedByWorkflowForWriteOperation(new \stdClass()), $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNoWorkflow(): void
|
public function testNoWorkflow(): void
|
||||||
{
|
{
|
||||||
$object = new \stdClass();
|
$helper = $this->buildHelper([], new User(), null);
|
||||||
$helper = $this->buildHelper($object, null, $user = new User());
|
|
||||||
self::assertTrue($helper->notBlockedByWorkflow($object), "the user is not blocked by the user, as there aren't any user inside");
|
self::assertEquals(WorkflowRelatedEntityPermissionHelper::ABSTAIN, $helper->isAllowedByWorkflowForWriteOperation(new \stdClass()));
|
||||||
|
self::assertEquals(WorkflowRelatedEntityPermissionHelper::ABSTAIN, $helper->isAllowedByWorkflowForReadOperation(new \stdClass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildHelper(object $relatedEntity, ?EntityWorkflow $entityWorkflow, User $user): WorkflowRelatedEntityPermissionHelper
|
/**
|
||||||
|
* @param list<EntityWorkflow> $entityWorkflows
|
||||||
|
*/
|
||||||
|
private function buildHelper(array $entityWorkflows, User $user, ?\DateTimeImmutable $atDateTime): WorkflowRelatedEntityPermissionHelper
|
||||||
{
|
{
|
||||||
$security = $this->prophesize(Security::class);
|
$security = $this->prophesize(Security::class);
|
||||||
$security->getUser()->willReturn($user);
|
$security->getUser()->willReturn($user);
|
||||||
|
|
||||||
$entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class);
|
$entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class);
|
||||||
if (null !== $entityWorkflow) {
|
$entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn($entityWorkflows);
|
||||||
$entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn([$entityWorkflow]);
|
|
||||||
} else {
|
return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $this->buildRegistry(), new MockClock($atDateTime ?? new \DateTimeImmutable()));
|
||||||
$entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $this->buildRegistry());
|
public static function provideDataAllowedByWorkflowReadOperation(): iterable
|
||||||
}
|
|
||||||
|
|
||||||
public static function provideDataAllowedByWorkflow(): iterable
|
|
||||||
{
|
{
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
yield [$entityWorkflow, new User(), false, 'not allowed because the user is not present as a dest user'];
|
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
|
||||||
|
'abstain because the user is not present as a dest user'];
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
$dto->futureDestUsers[] = $user = new User();
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allowed because the user is a current user'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
|
||||||
|
'force grant because the user is a current user'];
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
@ -112,7 +130,68 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
|
|||||||
$dto->futureDestUsers[] = new User();
|
$dto->futureDestUsers[] = new User();
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allowed because the user was a previous user'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
|
||||||
|
'force grant because the user was a previous user'];
|
||||||
|
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures[] = new Person();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
|
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
|
||||||
|
'force grant because there is a signature for person'];
|
||||||
|
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures[] = new Person();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
$signature = $entityWorkflow->getCurrentStep()->getSignatures()->first();
|
||||||
|
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate(new \DateTimeImmutable('2024-01-01T12:00:00'));
|
||||||
|
|
||||||
|
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable('2024-01-10T12:00:00'),
|
||||||
|
'abstain because there is a signature for person, already signed, and for a long time ago'];
|
||||||
|
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures[] = new Person();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
$signature = $entityWorkflow->getCurrentStep()->getSignatures()->first();
|
||||||
|
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)
|
||||||
|
->setStateDate(new \DateTimeImmutable('2024-01-01T12:00:00'));
|
||||||
|
|
||||||
|
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
|
||||||
|
new \DateTimeImmutable('2024-01-01T12:30:00'),
|
||||||
|
'force grant because there is a signature for person, already signed, a short time ago'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function provideDataAllowedByWorkflowWriteOperation(): iterable
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
|
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
|
||||||
|
'abstain because the user is not present as a dest user'];
|
||||||
|
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futureDestUsers[] = $user = new User();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
|
||||||
|
'force grant because the user is a current user'];
|
||||||
|
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futureDestUsers[] = $user = new User();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futureDestUsers[] = new User();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
|
||||||
|
'force grant because the user was a previous user'];
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
@ -122,7 +201,8 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
|
|||||||
$entityWorkflow->setStep('final_positive', $dto, 'to_final_positive', new \DateTimeImmutable(), new User());
|
$entityWorkflow->setStep('final_positive', $dto, 'to_final_positive', new \DateTimeImmutable(), new User());
|
||||||
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, false, 'not allowed because: user was a previous user, but it is finalized positive'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(),
|
||||||
|
'force denied: user was a previous user, but it is finalized positive'];
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
@ -132,80 +212,48 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
|
|||||||
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable());
|
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable());
|
||||||
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allowed: user was a previous user, it is finalized, but finalized negative'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
|
||||||
|
'abstain: user was a previous user, it is finalized, but finalized negative'];
|
||||||
}
|
|
||||||
|
|
||||||
public static function provideDataNotBlockByWorkflow(): iterable
|
|
||||||
{
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable());
|
|
||||||
|
|
||||||
yield [$entityWorkflow, new User(), false, 'blocked because the user is not present as a dest user'];
|
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
$dto->futureDestUsers[] = $user = new User();
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures[] = new Person();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
$signature = $entityWorkflow->getCurrentStep()->getSignatures()->first();
|
||||||
|
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate(new \DateTimeImmutable());
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allowed because the user is present as a dest user'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(),
|
||||||
|
'force denied because there is a signature'];
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
$dto->futureDestUsers[] = $user = new User();
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
$dto->futureDestUsers[] = new User();
|
$dto->futurePersonSignatures[] = new Person();
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allowed because the user is present as a **previous** dest user'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
|
||||||
|
'force grant: there is a signature, but still pending'];
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
$entityWorkflow = new EntityWorkflow();
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
$dto->futureDestUsers[] = $user = new User();
|
||||||
$entityWorkflow->setStep('final_positive', $dto, 'to_final_positive', new \DateTimeImmutable(), $user);
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures[] = new Person();
|
||||||
|
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
|
||||||
|
$signature = $entityWorkflow->getCurrentStep()->getSignatures()->first();
|
||||||
|
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate(new \DateTimeImmutable());
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable(), new User());
|
||||||
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, false, 'blocked because the step is final, and final positive'];
|
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
|
||||||
|
'abstain: there is a signature on a canceled workflow'];
|
||||||
$entityWorkflow = new EntityWorkflow();
|
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
|
||||||
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable(), $user);
|
|
||||||
$entityWorkflow->getCurrentStep()->setIsFinal(true);
|
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allowed because the step is final, and final negative'];
|
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
|
|
||||||
$step = $entityWorkflow->getCurrentStep();
|
|
||||||
new EntityWorkflowStepSignature($step, new Person());
|
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, true, 'allow, a signature is present but still pending'];
|
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
|
||||||
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
|
|
||||||
$step = $entityWorkflow->getCurrentStep();
|
|
||||||
$signature = new EntityWorkflowStepSignature($step, new Person());
|
|
||||||
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED);
|
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, false, 'blocked, a signature is present and signed'];
|
|
||||||
|
|
||||||
$entityWorkflow = new EntityWorkflow();
|
|
||||||
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
|
||||||
$dto->futureDestUsers[] = $user = new User();
|
|
||||||
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable(), $user);
|
|
||||||
$step = $entityWorkflow->getCurrentStep();
|
|
||||||
$signature = new EntityWorkflowStepSignature($step, new Person());
|
|
||||||
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED);
|
|
||||||
|
|
||||||
yield [$entityWorkflow, $user, false, 'blocked, a signature is present and signed, although the workflow is final negative'];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function buildRegistry(): Registry
|
private static function buildRegistry(): Registry
|
||||||
|
@ -11,29 +11,100 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Workflow\Helper;
|
namespace Chill\MainBundle\Workflow\Helper;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Workflow\Registry;
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an object, associated with a workflow, is blocked, or not, by this workflow.
|
* Helper to give supplementary permissions to a related entity.
|
||||||
|
*
|
||||||
|
* If a related entity is associated within a workflow, the logic of the workflow can give more permissions, or
|
||||||
|
* remove some permissions.
|
||||||
|
*
|
||||||
|
* The methods of this helper return either:
|
||||||
|
*
|
||||||
|
* - FORCE_GRANT, which means that a permission can be given, even if it would be denied when the related
|
||||||
|
* entity is not associated with a workflow;
|
||||||
|
* - FORCE_DENIED, which means that a permission should be denied, even if it would be granted when the related entity
|
||||||
|
* is not associated with a workflow
|
||||||
|
* - ABSTAIN, if there is no workflow logic to add or remove permission
|
||||||
|
*
|
||||||
|
* For read operations:
|
||||||
|
*
|
||||||
|
* - if the user is involved in the workflow (is part of the current step, of a step before), the user is granted read
|
||||||
|
* operation;
|
||||||
|
* - if there is a pending signature for a person, the workflow grant access to the related entity;
|
||||||
|
* - if there a signature applyied in less than 12 hours, the workflow grant access to the related entity. This allow to
|
||||||
|
* show the related entity to the person during this time frame.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* For write operation:
|
||||||
|
*
|
||||||
|
* - if the workflow is finalized "positive" (means "not canceled"), the workflow denys write operations;
|
||||||
|
* - if there isn't any finalized "positive" workflow, and if there is a signature appliyed for a running workflow (not finalized nor canceled),
|
||||||
|
* the workflow denys write operations;
|
||||||
|
* - if there is no case above and the user is involved in the workflow (is part of the current step, of a step before), the user is granted;
|
||||||
*/
|
*/
|
||||||
class WorkflowRelatedEntityPermissionHelper
|
class WorkflowRelatedEntityPermissionHelper
|
||||||
{
|
{
|
||||||
|
public const FORCE_GRANT = 'FORCE_GRANT';
|
||||||
|
public const FORCE_DENIED = 'FORCE_DENIED';
|
||||||
|
public const ABSTAIN = 'ABSTAIN';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Security $security,
|
private readonly Security $security,
|
||||||
private readonly EntityWorkflowManager $entityWorkflowManager,
|
private readonly EntityWorkflowManager $entityWorkflowManager,
|
||||||
private readonly Registry $registry,
|
private readonly Registry $registry,
|
||||||
|
private readonly ClockInterface $clock,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function isAllowedByWorkflow(object $entity): bool
|
/**
|
||||||
|
* @return 'FORCE_GRANT'|'FORCE_DENIED'|'ABSTAIN'
|
||||||
|
*/
|
||||||
|
public function isAllowedByWorkflowForReadOperation(object $entity): string
|
||||||
{
|
{
|
||||||
$entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
|
$entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
|
||||||
$currentUser = $this->security->getUser();
|
|
||||||
|
if ($this->isUserInvolvedInAWorkflow($entityWorkflows)) {
|
||||||
|
return self::FORCE_GRANT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// give a view permission if there is a Person signature pending, or in the 12 hours following
|
||||||
|
// the signature last state
|
||||||
|
foreach ($entityWorkflows as $workflow) {
|
||||||
|
foreach ($workflow->getCurrentStep()->getSignatures() as $signature) {
|
||||||
|
if ('person' === $signature->getSignerKind()) {
|
||||||
|
if (EntityWorkflowSignatureStateEnum::PENDING === $signature->getState()) {
|
||||||
|
return self::FORCE_GRANT;
|
||||||
|
}
|
||||||
|
$signatureDate = $signature->getStateDate();
|
||||||
|
$visibleUntil = $signatureDate->add(new \DateInterval('PT12H'));
|
||||||
|
|
||||||
|
if ($visibleUntil > $this->clock->now()) {
|
||||||
|
return self::FORCE_GRANT;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::ABSTAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 'FORCE_GRANT'|'FORCE_DENIED'|'ABSTAIN'
|
||||||
|
*/
|
||||||
|
public function isAllowedByWorkflowForWriteOperation(object $entity): string
|
||||||
|
{
|
||||||
|
$entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
|
||||||
|
$runningWorkflows = [];
|
||||||
|
|
||||||
|
// if a workflow is finalized positive, we are not allowed to edit to document any more
|
||||||
|
|
||||||
foreach ($entityWorkflows as $entityWorkflow) {
|
foreach ($entityWorkflows as $entityWorkflow) {
|
||||||
// if the user is finalized, we have to check if the workflow is finalPositive, or not
|
|
||||||
if ($entityWorkflow->isFinal()) {
|
if ($entityWorkflow->isFinal()) {
|
||||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||||
$marking = $workflow->getMarkingStore()->getMarking($entityWorkflow);
|
$marking = $workflow->getMarkingStore()->getMarking($entityWorkflow);
|
||||||
@ -41,12 +112,40 @@ class WorkflowRelatedEntityPermissionHelper
|
|||||||
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
|
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
|
||||||
if (true === ($placeMetadata['isFinalPositive'] ?? false)) {
|
if (true === ($placeMetadata['isFinalPositive'] ?? false)) {
|
||||||
// the workflow is final, and final positive, so we stop here.
|
// the workflow is final, and final positive, so we stop here.
|
||||||
return false;
|
return self::FORCE_DENIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$runningWorkflows[] = $entityWorkflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is a signature on a **running workflow**, no one can edit the workflow any more
|
||||||
|
foreach ($runningWorkflows as $entityWorkflow) {
|
||||||
|
foreach ($entityWorkflow->getSteps() as $step) {
|
||||||
|
foreach ($step->getSignatures() as $signature) {
|
||||||
|
if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) {
|
||||||
|
return self::FORCE_DENIED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allow only the users involved
|
||||||
|
if ($this->isUserInvolvedInAWorkflow($runningWorkflows)) {
|
||||||
|
return self::FORCE_GRANT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::ABSTAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<EntityWorkflow> $entityWorkflows
|
||||||
|
*/
|
||||||
|
private function isUserInvolvedInAWorkflow(array $entityWorkflows): bool
|
||||||
|
{
|
||||||
|
$currentUser = $this->security->getUser();
|
||||||
|
|
||||||
foreach ($entityWorkflows as $entityWorkflow) {
|
foreach ($entityWorkflows as $entityWorkflow) {
|
||||||
// so, the workflow is running... We return true if the current user is involved
|
// so, the workflow is running... We return true if the current user is involved
|
||||||
foreach ($entityWorkflow->getSteps() as $step) {
|
foreach ($entityWorkflow->getSteps() as $step) {
|
||||||
@ -58,58 +157,4 @@ class WorkflowRelatedEntityPermissionHelper
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the user is allowed to update the given object.
|
|
||||||
*
|
|
||||||
* Return false if some workflow block the edition of the object.
|
|
||||||
*/
|
|
||||||
public function notBlockedByWorkflow(object $entity): bool
|
|
||||||
{
|
|
||||||
$entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
|
|
||||||
$currentUser = $this->security->getUser();
|
|
||||||
|
|
||||||
$usersInvolved = [];
|
|
||||||
$entityWorkflowsNotFinalizedPositive = [];
|
|
||||||
foreach ($entityWorkflows as $entityWorkflow) {
|
|
||||||
// as soon as there is one signatured applyied, we are not able to
|
|
||||||
// edit the document any more
|
|
||||||
foreach ($entityWorkflow->getSteps() as $step) {
|
|
||||||
foreach ($step->getSignatures() as $signature) {
|
|
||||||
if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($entityWorkflow->isFinal()) {
|
|
||||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
|
||||||
$marking = $workflow->getMarkingStore()->getMarking($entityWorkflow);
|
|
||||||
foreach ($marking->getPlaces() as $place => $active) {
|
|
||||||
$metadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
|
|
||||||
if ($metadata['isFinalPositive'] ?? true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$entityWorkflowsNotFinalizedPositive[] = $entityWorkflow;
|
|
||||||
foreach ($entityWorkflow->getSteps() as $step) {
|
|
||||||
foreach ($step->getAllDestUser()->toArray() as $user) {
|
|
||||||
$usersInvolved[] = $user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there isn't any user, but a workflow, blocked
|
|
||||||
if ([] !== $entityWorkflowsNotFinalizedPositive) {
|
|
||||||
if ([] === $usersInvolved) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return in_array($currentUser, $usersInvolved, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user