workflow: allow a user to get access to validation step by an access key

This commit is contained in:
Julien Fastré 2022-02-24 12:17:13 +01:00
parent 08f9819453
commit ff1ff8f5bb
7 changed files with 167 additions and 32 deletions

View File

@ -11,8 +11,10 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
use Chill\MainBundle\Form\EntityWorkflowCommentType;
use Chill\MainBundle\Form\WorkflowStepType;
use Chill\MainBundle\Pagination\PaginatorFactory;
@ -24,10 +26,12 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\TransitionBlocker;
@ -84,8 +88,7 @@ class WorkflowController extends AbstractController
->setRelatedEntityId($request->query->getInt('entityId'))
->setWorkflowName($request->query->get('workflow'))
->addSubscriberToStep($this->getUser())
->addSubscriberToFinal($this->getUser())
;
->addSubscriberToFinal($this->getUser());
$errors = $this->validator->validate($entityWorkflow, null, ['creation']);
@ -136,6 +139,37 @@ class WorkflowController extends AbstractController
]);
}
/**
* @Route("/{_locale}/main/workflow-step/{id}/access_key", name="chill_main_workflow_grant_access_by_key")
*/
public function getAccessByAccessKey(EntityWorkflowStep $entityWorkflowStep, Request $request): Response
{
if (null === $accessKey = $request->query->get('accessKey', null)) {
throw new BadRequestException('accessKey is missing');
}
if (!$this->getUser() instanceof User) {
throw new AccessDeniedException('Not a valid user');
}
dump($accessKey);
dump($entityWorkflowStep->getAccessKey());
if ($entityWorkflowStep->getAccessKey() !== $accessKey) {
throw new AccessDeniedException('Access key is invalid');
}
if (!$entityWorkflowStep->isWaitingForTransition()) {
$this->addFlash('error', $this->translator->trans('workflow.Steps is not waiting for transition. Maybe someone apply the transition before you ?'));
} else {
$entityWorkflowStep->addDestUserByAccessKey($this->getUser());
$this->entityManager->flush();
$this->addFlash('success', $this->translator->trans('workflow.You get access to this step'));
}
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflowStep->getEntityWorkflow()->getId()]);
}
/**
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
*/

View File

@ -27,6 +27,11 @@ use function in_array;
*/
class EntityWorkflowStep
{
/**
* @ORM\Column(type="text", nullable=false)
*/
private string $accessKey;
/**
* @ORM\Column(type="text", options={"default": ""})
*/
@ -48,6 +53,12 @@ class EntityWorkflowStep
*/
private Collection $destUser;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user_by_accesskey")
*/
private Collection $destUserByAccessKey;
/**
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
*/
@ -101,14 +112,10 @@ class EntityWorkflowStep
*/
private ?string $transitionByEmail = null;
/**
* @ORM\Column(type="text", nullable=false)
*/
private string $accessKey;
public function __construct()
{
$this->destUser = new ArrayCollection();
$this->destUserByAccessKey = new ArrayCollection();
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
}
@ -133,6 +140,37 @@ class EntityWorkflowStep
return $this;
}
public function addDestUserByAccessKey(User $user): self
{
if (!$this->destUserByAccessKey->contains($user)) {
$this->destUserByAccessKey[] = $user;
$this->getEntityWorkflow()
->addSubscriberToFinal($user)
->addSubscriberToStep($user);
}
return $this;
}
public function getAccessKey(): string
{
return $this->accessKey;
}
/**
* get all the users which are allowed to apply a transition: those added manually, and
* those added automatically bu using an access key.
*/
public function getAllDestUser(): Collection
{
return new ArrayCollection(
[
...$this->getDestUser()->toArray(),
...$this->getDestUserByAccessKey()->toArray(),
]
);
}
public function getComment(): string
{
return $this->comment;
@ -149,13 +187,21 @@ class EntityWorkflowStep
}
/**
* @return ArrayCollection|Collection
* get dest users added by the creator.
*
* You should **not** rely on this method to get all users which are able to
* apply a transition on this step. Use @see{EntityWorkflowStep::getAllDestUser} instead.
*/
public function getDestUser()
public function getDestUser(): collection
{
return $this->destUser;
}
public function getDestUserByAccessKey(): Collection
{
return $this->destUserByAccessKey;
}
public function getEntityWorkflow(): ?EntityWorkflow
{
return $this->entityWorkflow;
@ -206,6 +252,19 @@ class EntityWorkflowStep
return $this->freezeAfter;
}
public function isWaitingForTransition(): bool
{
if (null !== $this->transitionAfter) {
return false;
}
if ($this->isFinal()) {
return false;
}
return true;
}
public function removeDestEmail(string $email): self
{
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
@ -222,6 +281,13 @@ class EntityWorkflowStep
return $this;
}
public function removeDestUserByAccessKey(User $user): self
{
$this->destUserByAccessKey->removeElement($user);
return $this;
}
public function setComment(?string $comment): EntityWorkflowStep
{
$this->comment = (string) $comment;

View File

@ -82,13 +82,23 @@
<p>{{ 'workflow.This workflow is finalized'|trans }}</p>
{% else %}
<p>{{ 'workflow.You are not allowed to apply a transition on this workflow'|trans }}</p>
<p>{{ 'workflow.Only those users are allowed'|trans }}:</p>
{% if entity_workflow.currentStep.destUser|length > 0 %}
<p><b>{{ 'workflow.Only those users are allowed'|trans }}&nbsp;:</b></p>
<ul>
{% for u in entity_workflow.currentStep.destUser -%}
<li>{{ u|chill_entity_render_box }}</li>
{%- endfor %}
</ul>
{% endif %}
<ul>
{% for u in entity_workflow.currentStep.destUser -%}
<li>{{ u|chill_entity_render_box }}</li>
{%- endfor %}
</ul>
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }}&nbsp;:</b></p>
<ul>
{% for u in entity_workflow.currentStep.destUserByAccessKey %}
<li>{{ u|chill_entity_render_box }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</div>

View File

@ -69,15 +69,26 @@
</blockquote>
</div>
{% endif %}
{% if loop.last and step.destUser|length > 0 %}
{% if loop.last and step.allDestUser|length > 0 %}
<div class="item-row separator">
<div>
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }}&nbsp;: </b></p>
<ul>
{% for u in step.destUser %}
<li>{{ u|chill_entity_render_box }}</li>
{% endfor %}
</ul>
{% if step.destUser|length > 0 %}
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }}&nbsp;: </b></p>
<ul>
{% for u in step.destUser %}
<li>{{ u|chill_entity_render_box }}</li>
{% endfor %}
</ul>
{% endif %}
{% if step.destUserByAccessKey|length > 0 %}
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }}&nbsp;:</b></p>
<ul>
{% for u in step.destUserByAccessKey %}
<li>{{ u|chill_entity_render_box }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endif %}

View File

@ -72,7 +72,7 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
return;
}
if (!$entityWorkflow->getCurrentStep()->getDestUser()->contains($this->security->getUser())) {
if (!$entityWorkflow->getCurrentStep()->getAllDestUser()->contains($this->security->getUser())) {
if (!$event->getMarking()->has('initial')) {
$event->addTransitionBlocker(new TransitionBlocker(
'workflow.You are not allowed to apply a transition on this workflow. Only those users are allowed: %users%',
@ -80,7 +80,7 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
[
'%users%' => implode(
', ',
$entityWorkflow->getCurrentStep()->getDestUser()->map(function (User $u) {
$entityWorkflow->getCurrentStep()->getAllDestUser()->map(function (User $u) {
return $this->userRender->renderString($u, []);
})->toArray()
),

View File

@ -1,5 +1,12 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Main;
@ -7,14 +14,17 @@ namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220223171457 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP accessKey');
$this->addSql('DROP TABLE chill_main_workflow_entity_step_user_by_accesskey');
}
public function getDescription(): string
{
return '';
return 'Add access key to EntityWorkflowStep';
}
public function up(Schema $schema): void
@ -31,10 +41,11 @@ final class Version20220223171457 extends AbstractMigration
UPDATE chill_main_workflow_entity_step SET accessKey = randoms.random_word FROM randoms WHERE chill_main_workflow_entity_step.id = randoms.id');
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER accessKey DROP DEFAULT ');
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER accessKey SET NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP accessKey');
$this->addSql('CREATE TABLE chill_main_workflow_entity_step_user_by_accesskey (entityworkflowstep_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflowstep_id, user_id))');
$this->addSql('CREATE INDEX IDX_8296D8397E6AF9D4 ON chill_main_workflow_entity_step_user_by_accesskey (entityworkflowstep_id)');
$this->addSql('CREATE INDEX IDX_8296D839A76ED395 ON chill_main_workflow_entity_step_user_by_accesskey (user_id)');
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user_by_accesskey ADD CONSTRAINT FK_8296D8397E6AF9D4 FOREIGN KEY (entityworkflowstep_id) REFERENCES chill_main_workflow_entity_step (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user_by_accesskey ADD CONSTRAINT FK_8296D839A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
}

View File

@ -400,6 +400,9 @@ workflow:
Delete workflow ?: Supprimer le workflow ?
Are you sure you want to delete this workflow ?: Êtes-vous sûr·e de vouloir supprimer ce workflow ?
Delete workflow: Supprimer le workflow
Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ?
You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow.
Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès
Subscribe final: Recevoir une notification à l'étape finale
Subscribe all steps: Recevoir une notification à chaque étape