Restrict export filters and aggregators for limited users

Added restrictions on export filters and aggregators based on user permissions. Introduced `ExportConfigProcessor` to handle allowed configurations and updated form components to respect these restrictions. Enhanced validation to enforce access control for unauthorized filter editing.
This commit is contained in:
Julien Fastré 2025-04-24 14:21:51 +02:00
parent a6e523ee0a
commit 8c5a7ac3e1
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
5 changed files with 51 additions and 6 deletions

View File

@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\SavedExport;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\DirectExportInterface; use Chill\MainBundle\Export\DirectExportInterface;
use Chill\MainBundle\Export\ExportConfigNormalizer; use Chill\MainBundle\Export\ExportConfigNormalizer;
use Chill\MainBundle\Export\ExportConfigProcessor;
use Chill\MainBundle\Export\ExportFormHelper; use Chill\MainBundle\Export\ExportFormHelper;
use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\ExportManager;
@ -24,6 +25,7 @@ use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\FormatterType; use Chill\MainBundle\Form\Type\Export\FormatterType;
use Chill\MainBundle\Form\Type\Export\PickCenterType; use Chill\MainBundle\Form\Type\Export\PickCenterType;
use Chill\MainBundle\Repository\SavedExportOrExportGenerationRepository; use Chill\MainBundle\Repository\SavedExportOrExportGenerationRepository;
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\MainBundle\Security\Authorization\SavedExportVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -64,6 +66,7 @@ class ExportController extends AbstractController
private readonly ClockInterface $clock, private readonly ClockInterface $clock,
private readonly ExportConfigNormalizer $exportConfigNormalizer, private readonly ExportConfigNormalizer $exportConfigNormalizer,
private readonly SavedExportOrExportGenerationRepository $savedExportOrExportGenerationRepository, private readonly SavedExportOrExportGenerationRepository $savedExportOrExportGenerationRepository,
private readonly ExportConfigProcessor $exportConfigProcessor,
) { ) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
} }
@ -114,11 +117,19 @@ class ExportController extends AbstractController
/** @var ExportManager $exportManager */ /** @var ExportManager $exportManager */
$exportManager = $this->exportManager; $exportManager = $this->exportManager;
$isGenerate = str_starts_with($step, 'generate_'); $isGenerate = str_starts_with($step, 'generate_');
$canEditFull = $this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT);
if (!$canEditFull && null === $savedExport) {
throw new AccessDeniedHttpException('The user is not allowed to edit all filter, it should edit only SavedExport');
}
$options = match ($step) { $options = match ($step) {
'export', 'generate_export' => [ 'export', 'generate_export' => [
'export_alias' => $alias, 'export_alias' => $alias,
'picked_centers' => $this->exportFormHelper->getPickedCenters($data), 'picked_centers' => $this->exportFormHelper->getPickedCenters($data),
'can_edit_full' => $canEditFull,
'allowed_filters' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedFilters($savedExport->getOptions()['filters']),
'allowed_aggregators' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedAggregators($savedExport->getOptions()['aggregators']),
], ],
'formatter', 'generate_formatter' => [ 'formatter', 'generate_formatter' => [
'export_alias' => $alias, 'export_alias' => $alias,

View File

@ -32,6 +32,7 @@ class AggregatorType extends AbstractType
->add(self::ENABLED_FIELD, CheckboxType::class, [ ->add(self::ENABLED_FIELD, CheckboxType::class, [
'value' => true, 'value' => true,
'required' => false, 'required' => false,
'disabled' => $options['disable_enable_field'],
]); ]);
$aggregatorFormBuilder = $builder->create('form', FormType::class, [ $aggregatorFormBuilder = $builder->create('form', FormType::class, [
@ -55,6 +56,7 @@ class AggregatorType extends AbstractType
{ {
$resolver->setRequired('aggregator_alias') $resolver->setRequired('aggregator_alias')
->setRequired('export_manager') ->setRequired('export_manager')
->setDefault('disable_enable_field', false)
->setDefault('compound', true) ->setDefault('compound', true)
->setDefault('error_bubbling', false); ->setDefault('error_bubbling', false);
} }

View File

@ -35,7 +35,7 @@ class ExportType extends AbstractType
public function __construct( public function __construct(
private readonly ExportManager $exportManager, private readonly ExportManager $exportManager,
private readonly SortExportElement $sortExportElement, private readonly SortExportElement $sortExportElement,
protected ParameterBagInterface $parameterBag, ParameterBagInterface $parameterBag,
) { ) {
$this->personFieldsConfig = $parameterBag->get('chill_person.person_fields'); $this->personFieldsConfig = $parameterBag->get('chill_person.person_fields');
} }
@ -43,6 +43,8 @@ class ExportType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$export = $this->exportManager->getExport($options['export_alias']); $export = $this->exportManager->getExport($options['export_alias']);
/** @var bool $canEditFull */
$canEditFull = $options['can_edit_full'];
$exportOptions = [ $exportOptions = [
'compound' => true, 'compound' => true,
@ -59,8 +61,18 @@ class ExportType extends AbstractType
if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { if ($export instanceof \Chill\MainBundle\Export\ExportInterface) {
// add filters // add filters
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); $filterAliases = $options['allowed_filters'];
$filters = [];
if (is_iterable($filterAliases)) {
foreach ($filterAliases as $alias => $filter) {
$filters[$alias] = $filter;
}
} else {
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']);
}
$this->sortExportElement->sortFilters($filters); $this->sortExportElement->sortFilters($filters);
$filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]);
foreach ($filters as $alias => $filter) { foreach ($filters as $alias => $filter) {
@ -70,15 +82,26 @@ class ExportType extends AbstractType
'constraints' => [ 'constraints' => [
new ExportElementConstraint(['element' => $filter]), new ExportElementConstraint(['element' => $filter]),
], ],
'disable_enable_field' => !$canEditFull,
]); ]);
} }
$builder->add($filterBuilder); $builder->add($filterBuilder);
// add aggregators // add aggregators
$aggregators = $this->exportManager $aggregatorsAliases = $options['allowed_aggregators'];
->getAggregatorsApplyingOn($export, $options['picked_centers']); $aggregators = [];
if (is_iterable($aggregatorsAliases)) {
foreach ($aggregatorsAliases as $alias => $aggregator) {
$aggregators[$alias] = $aggregator;
}
} else {
$aggregators = $this->exportManager
->getAggregatorsApplyingOn($export, $options['picked_centers']);
}
$this->sortExportElement->sortAggregators($aggregators); $this->sortExportElement->sortAggregators($aggregators);
$aggregatorBuilder = $builder->create( $aggregatorBuilder = $builder->create(
self::AGGREGATOR_KEY, self::AGGREGATOR_KEY,
FormType::class, FormType::class,
@ -96,11 +119,11 @@ class ExportType extends AbstractType
} }
} }
$aggregatorBuilder->add($alias, AggregatorType::class, [ $aggregatorBuilder->add($alias, AggregatorType::class, [
'aggregator_alias' => $alias, 'aggregator_alias' => $alias,
'export_manager' => $this->exportManager, 'export_manager' => $this->exportManager,
'label' => $aggregator->getTitle(), 'label' => $aggregator->getTitle(),
'disable_enable_field' => !$canEditFull,
'constraints' => [ 'constraints' => [
new ExportElementConstraint(['element' => $aggregator]), new ExportElementConstraint(['element' => $aggregator]),
], ],
@ -125,8 +148,13 @@ class ExportType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver->setRequired(['export_alias', 'picked_centers']) $resolver->setRequired(['export_alias', 'picked_centers', 'can_edit_full'])
->setAllowedTypes('export_alias', ['string']) ->setAllowedTypes('export_alias', ['string'])
->setAllowedValues('can_edit_full', [true, false])
->setDefault('allowed_filters', null)
->setAllowedTypes('allowed_filters', ['iterable', 'null'])
->setDefault('allowed_aggregators', null)
->setAllowedTypes('allowed_aggregators', ['iterable', 'null'])
->setDefault('compound', true) ->setDefault('compound', true)
->setDefault('constraints', [ ->setDefault('constraints', [
// new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint() // new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint()

View File

@ -34,6 +34,7 @@ class FilterType extends AbstractType
->add(self::ENABLED_FIELD, CheckboxType::class, [ ->add(self::ENABLED_FIELD, CheckboxType::class, [
'value' => true, 'value' => true,
'required' => false, 'required' => false,
'disabled' => $options['disable_enable_field'],
]); ]);
$filterFormBuilder = $builder->create('form', FormType::class, [ $filterFormBuilder = $builder->create('form', FormType::class, [
@ -58,6 +59,7 @@ class FilterType extends AbstractType
$resolver $resolver
->setRequired('filter') ->setRequired('filter')
->setAllowedTypes('filter', [FilterInterface::class]) ->setAllowedTypes('filter', [FilterInterface::class])
->setDefault('disable_enable_field', false)
->setDefault('compound', true) ->setDefault('compound', true)
->setDefault('error_bubbling', false); ->setDefault('error_bubbling', false);
} }

View File

@ -16,6 +16,8 @@ services:
Chill\MainBundle\Export\ExportConfigNormalizer: ~ Chill\MainBundle\Export\ExportConfigNormalizer: ~
Chill\MainBundle\Export\ExportConfigProcessor: ~
chill.main.export_element_validator: chill.main.export_element_validator:
class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator
tags: tags: