Fixed: add scope to generated document ref #11

This commit is contained in:
Julien Fastré 2022-10-08 00:21:37 +02:00
parent 877535ca4d
commit a8a206557b
2 changed files with 342 additions and 43 deletions

View File

@ -20,52 +20,72 @@ use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository; use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Form\ScopeType; use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime; use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use LogicException;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists; use function array_key_exists;
use function count;
class PersonContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface class PersonContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface
{ {
private AuthorizationHelperInterface $authorizationHelper;
private BaseContextData $baseContextData; private BaseContextData $baseContextData;
private CenterResolverManagerInterface $centerResolverManager;
private DocumentCategoryRepository $documentCategoryRepository; private DocumentCategoryRepository $documentCategoryRepository;
private EntityManagerInterface $em; private EntityManagerInterface $em;
private NormalizerInterface $normalizer; private NormalizerInterface $normalizer;
private Security $security;
private bool $showScopes;
private TranslatableStringHelperInterface $translatableStringHelper; private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator; private TranslatorInterface $translator;
private AuthorizationHelper $authorizationHelper;
public function __construct( public function __construct(
DocumentCategoryRepository $documentCategoryRepository, AuthorizationHelperInterface $authorizationHelper,
NormalizerInterface $normalizer,
TranslatableStringHelperInterface $translatableStringHelper,
EntityManagerInterface $em,
TranslatorInterface $translator,
BaseContextData $baseContextData, BaseContextData $baseContextData,
AuthorizationHelper $authorizationHelper CenterResolverManagerInterface $centerResolverManager,
DocumentCategoryRepository $documentCategoryRepository,
EntityManagerInterface $em,
NormalizerInterface $normalizer,
ParameterBagInterface $parameterBag,
Security $security,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper
) { ) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
$this->em = $em;
$this->baseContextData = $baseContextData;
$this->translator = $translator;
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->centerResolverManager = $centerResolverManager;
$this->baseContextData = $baseContextData;
$this->documentCategoryRepository = $documentCategoryRepository;
$this->em = $em;
$this->normalizer = $normalizer;
$this->security = $security;
$this->showScopes = $parameterBag->get('chill_main')['acl']['form_show_scopes'];
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function adminFormReverseTransform(array $data): array public function adminFormReverseTransform(array $data): array
@ -112,6 +132,18 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface, DocGen
]); ]);
} }
/**
* @param Person $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$builder->add('scope', ScopePickerType::class, [
'center' => $this->centerResolverManager->resolveCenters($entity),
'role' => PersonDocumentVoter::CREATE,
'label' => 'Scope',
]);
}
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{ {
if (!$entity instanceof Person) { if (!$entity instanceof Person) {
@ -163,6 +195,14 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface, DocGen
return true; return true;
} }
/**
* @param Person $entity
*/
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
return $this->isScopeNecessary($entity);
}
/** /**
* @param Person $entity * @param Person $entity
*/ */
@ -185,42 +225,36 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface, DocGen
); );
} }
if ($this->showScopes()) { if ($this->isScopeNecessary($entity)) {
$doc->setScope($contextGenerationData['scope']);
} elseif ($this->showScopes) {
// in this case, it should have only one scope possible, we get it through AuthorizationHelper::getReachableScopes
$scopes = $this->authorizationHelper->getReachableScopes(
$this->security->getUser(),
PersonDocumentVoter::CREATE,
$this->centerResolverManager->resolveCenters($entity)
);
$scopes = $this->authorizationHelper->getReachableScopes(); if (1 !== count($scopes)) {
throw new LogicException('at this step, it should have only one scope');
if (count($scopes > 1)) }
{
$scope = $this->getFormData();
$doc->setScope($scope);
} else {
$doc->setScope($scopes[0]); $doc->setScope($scopes[0]);
}
} }
$this->em->persist($doc); $this->em->persist($doc);
} }
public function showScopes(){ private function isScopeNecessary(Person $person): bool
return $this->parameterBag->get('chill_main')['acl']['form_show_scopes'];
}
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{ {
$builder->add('Center', ScopeType::class, [ if ($this->showScopes && 1 < $this->authorizationHelper->getReachableScopes(
'required' => true $this->security->getUser(),
]); PersonDocumentVoter::CREATE,
} $this->centerResolverManager->resolveCenters($person)
)) {
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
if ($this->showScopes()) {
return true; return true;
} else { }
return false; return false;
} }
} }
}

View File

@ -0,0 +1,265 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Service\DocGenerator;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Service\DocGenerator\PersonContext;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Exception\Prediction\FailedPredictionException;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count;
/**
* @internal
* @coversNothing
*/
final class PersonContextTest extends TestCase
{
use ProphecyTrait;
/**
* Test that the build person context works in the case when 'form_show_scope' is false.
*/
public function testScopeDoNotShowScopeInForms()
{
$person = new Person();
$docGen = (new DocGeneratorTemplate())
->setName(['fr' => 'template']);
$parameter = new ParameterBag(['chill_main' => ['acl' => ['form_show_scopes' => false]]]);
$em = $this->prophesize(EntityManagerInterface::class);
$em->persist(Argument::type(PersonDocument::class))
->should(static function ($calls, $object, $method) {
if (1 !== count($calls)) {
throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls)));
}
/** @var PersonDocument $personDocument */
$personDocument = $calls[0]->getArguments()[0];
if (null !== $personDocument->getScope()) {
throw new FailedPredictionException('the person document should not have any scope');
}
});
$personContext = $this->buildPersonContext(
null,
null,
null,
null,
$em->reveal(),
null,
$parameter
);
$this->assertFalse($personContext->hasPublicForm($docGen, $person));
$personContext->storeGenerated(
$docGen,
new StoredObject(),
$person,
[]
);
}
public function testScopeScopeMustBeShownInFormsAndUserAccessMultipleScope()
{
$person = new Person();
$docGen = (new DocGeneratorTemplate())
->setName(['fr' => 'template']);
$scope = new Scope();
$em = $this->prophesize(EntityManagerInterface::class);
$em->persist(Argument::type(PersonDocument::class))
->should(static function ($calls, $object, $method) use ($scope) {
if (1 !== count($calls)) {
throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls)));
}
/** @var PersonDocument $personDocument */
$personDocument = $calls[0]->getArguments()[0];
if ($personDocument->getScope() !== $scope) {
throw new FailedPredictionException('the person document should show the exactly prepared scope');
}
});
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableScopes(Argument::type(UserInterface::class), PersonDocumentVoter::CREATE, Argument::type('array'))
->willReturn([$scope, new Scope()]);
$personContext = $this->buildPersonContext(
$authorizationHelper->reveal(),
null,
null,
null,
$em->reveal(),
);
$this->assertTrue($personContext->hasPublicForm($docGen, $person));
$personContext->storeGenerated(
$docGen,
new StoredObject(),
$person,
['scope' => $scope]
);
}
public function testScopeScopeMustBeShownInFormsAndUserAccessOneScope()
{
$person = new Person();
$docGen = (new DocGeneratorTemplate())
->setName(['fr' => 'template']);
$scope = new Scope();
$em = $this->prophesize(EntityManagerInterface::class);
$em->persist(Argument::type(PersonDocument::class))
->should(static function ($calls, $object, $method) use ($scope) {
if (1 !== count($calls)) {
throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls)));
}
/** @var PersonDocument $personDocument */
$personDocument = $calls[0]->getArguments()[0];
if ($personDocument->getScope() !== $scope) {
throw new FailedPredictionException('the person document should show the exactly prepared scope');
}
});
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableScopes(Argument::type(UserInterface::class), PersonDocumentVoter::CREATE, Argument::type('array'))
->willReturn([$scope]);
$personContext = $this->buildPersonContext(
$authorizationHelper->reveal(),
null,
null,
null,
$em->reveal(),
);
$this->assertTrue($personContext->hasPublicForm($docGen, $person));
$personContext->storeGenerated(
$docGen,
new StoredObject(),
$person,
['scope' => $scope]
);
}
private function buildPersonContext(
?AuthorizationHelperInterface $authorizationHelper = null,
?BaseContextData $baseContextData = null,
?CenterResolverManagerInterface $centerResolverManager = null,
?DocumentCategoryRepository $documentCategoryRepository = null,
?EntityManagerInterface $em = null,
?NormalizerInterface $normalizer = null,
?ParameterBagInterface $parameterBag = null,
?Security $security = null,
?TranslatorInterface $translator = null,
?TranslatableStringHelperInterface $translatableStringHelper = null
): PersonContext {
if (null === $authorizationHelper) {
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class)->reveal();
}
if (null === $baseContextData) {
$baseContextData = $this->prophesize(BaseContextData::class)->reveal();
}
if (null === $centerResolverManager) {
$centerResolverManager = $this->prophesize(CenterResolverManagerInterface::class);
$centerResolverManager->resolveCenters(Argument::any(), Argument::any())
->willReturn([new Center()]);
$centerResolverManager = $centerResolverManager->reveal();
}
if (null === $documentCategoryRepository) {
$documentCategoryRepository = $this->prophesize(DocumentCategoryRepository::class);
$documentCategoryRepository->find(Argument::type('integer'))->willReturn(
new DocumentCategory(PersonDocument::class, 1)
);
$documentCategoryRepository = $documentCategoryRepository->reveal();
}
if (null === $em) {
$em = $this->prophesize(EntityManagerInterface::class)->reveal();
}
if (null === $normalizer) {
$normalizer = $this->prophesize(NormalizerInterface::class);
$normalizer->normalize(Argument::type(Person::class), 'docgen', Argument::any())
->willReturn(['type' => 'person']);
$normalizer = $normalizer->reveal();
}
if (null === $parameterBag) {
$parameterBag = new ParameterBag(['chill_main' => ['acl' => ['form_show_scopes' => true]]]);
}
if (null === $security) {
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn(new User());
$security = $security->reveal();
}
if (null === $translator) {
$translator = $this->prophesize(TranslatorInterface::class)->reveal();
}
if (null === $translatableStringHelper) {
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
// return only the 'fr' key
$translatableStringHelper->localize(Argument::type('array'))->will(static function ($args) {
return $args[0]['fr'];
});
$translatableStringHelper = $translatableStringHelper->reveal();
}
return new PersonContext(
$authorizationHelper,
$baseContextData,
$centerResolverManager,
$documentCategoryRepository,
$em,
$normalizer,
$parameterBag,
$security,
$translator,
$translatableStringHelper
);
}
}