Merge remote-tracking branch 'origin/master' into signature-app-master

This commit is contained in:
2024-11-14 12:18:07 +01:00
6 changed files with 349 additions and 278 deletions

View File

@@ -11,29 +11,100 @@ declare(strict_types=1);
namespace Chill\MainBundle\Workflow\Helper;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Security\Core\Security;
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
{
public const FORCE_GRANT = 'FORCE_GRANT';
public const FORCE_DENIED = 'FORCE_DENIED';
public const ABSTAIN = 'ABSTAIN';
public function __construct(
private readonly Security $security,
private readonly EntityWorkflowManager $entityWorkflowManager,
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);
$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) {
// if the user is finalized, we have to check if the workflow is finalPositive, or not
if ($entityWorkflow->isFinal()) {
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$marking = $workflow->getMarkingStore()->getMarking($entityWorkflow);
@@ -41,12 +112,40 @@ class WorkflowRelatedEntityPermissionHelper
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
if (true === ($placeMetadata['isFinalPositive'] ?? false)) {
// 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) {
// so, the workflow is running... We return true if the current user is involved
foreach ($entityWorkflow->getSteps() as $step) {
@@ -58,58 +157,4 @@ class WorkflowRelatedEntityPermissionHelper
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;
}
}