src/Core/Framework/App/Subscriber/CustomFieldProtectionSubscriber.php line 43

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\App\Subscriber;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Framework\Api\Context\AdminApiSource;
  5. use Shopware\Core\Framework\Api\Context\SystemSource;
  6. use Shopware\Core\Framework\Context;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  10. use Shopware\Core\Framework\Log\Package;
  11. use Shopware\Core\Framework\Uuid\Uuid;
  12. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  13. use Shopware\Core\System\CustomField\Aggregate\CustomFieldSet\CustomFieldSetDefinition;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Component\Validator\ConstraintViolation;
  16. use Symfony\Component\Validator\ConstraintViolationInterface;
  17. use Symfony\Component\Validator\ConstraintViolationList;
  18. /**
  19.  * @internal only for use by the app-system, will be considered internal from v6.4.0 onward
  20.  */
  21. #[Package('core')]
  22. class CustomFieldProtectionSubscriber implements EventSubscriberInterface
  23. {
  24.     final public const VIOLATION_NO_PERMISSION 'no_permission_violation';
  25.     public function __construct(private readonly Connection $connection)
  26.     {
  27.     }
  28.     /**
  29.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  30.      */
  31.     public static function getSubscribedEvents(): array
  32.     {
  33.         return [
  34.             PreWriteValidationEvent::class => 'checkWrite',
  35.         ];
  36.     }
  37.     public function checkWrite(PreWriteValidationEvent $event): void
  38.     {
  39.         $context $event->getContext();
  40.         if ($context->getSource() instanceof SystemSource || $context->getScope() === Context::SYSTEM_SCOPE) {
  41.             return;
  42.         }
  43.         $integrationId $this->getIntegrationId($context);
  44.         $violationList = new ConstraintViolationList();
  45.         foreach ($event->getCommands() as $command) {
  46.             if (
  47.                 !($command->getDefinition() instanceof CustomFieldSetDefinition)
  48.                 || $command instanceof InsertCommand
  49.             ) {
  50.                 continue;
  51.             }
  52.             $appIntegrationId $this->fetchIntegrationIdOfAssociatedApp($command);
  53.             if (!$appIntegrationId) {
  54.                 continue;
  55.             }
  56.             if ($integrationId !== $appIntegrationId) {
  57.                 $this->addViolation($violationList$command);
  58.             }
  59.         }
  60.         if ($violationList->count() > 0) {
  61.             $event->getExceptions()->add(new WriteConstraintViolationException($violationList));
  62.         }
  63.     }
  64.     private function getIntegrationId(Context $context): ?string
  65.     {
  66.         $source $context->getSource();
  67.         if (!($source instanceof AdminApiSource)) {
  68.             return null;
  69.         }
  70.         return $source->getIntegrationId();
  71.     }
  72.     private function fetchIntegrationIdOfAssociatedApp(WriteCommand $command): ?string
  73.     {
  74.         $id $command->getPrimaryKey()['id'];
  75.         $integrationId $this->connection->executeQuery('
  76.             SELECT `app`.`integration_id`
  77.             FROM `app`
  78.             INNER JOIN `custom_field_set` ON `custom_field_set`.`app_id` = `app`.`id`
  79.             WHERE `custom_field_set`.`id` = :customFieldSetId
  80.         ', ['customFieldSetId' => $id])->fetchOne();
  81.         if (!$integrationId) {
  82.             return null;
  83.         }
  84.         return Uuid::fromBytesToHex($integrationId);
  85.     }
  86.     private function addViolation(ConstraintViolationList $violationListWriteCommand $command): void
  87.     {
  88.         $violationList->add(
  89.             $this->buildViolation(
  90.                 'No permissions to %privilege%".',
  91.                 ['%privilege%' => 'write:custom_field_set'],
  92.                 '/' $command->getDefinition()->getEntityName(),
  93.                 self::VIOLATION_NO_PERMISSION
  94.             )
  95.         );
  96.     }
  97.     /**
  98.      * @param array<string, string> $parameters
  99.      */
  100.     private function buildViolation(
  101.         string $messageTemplate,
  102.         array $parameters,
  103.         ?string $propertyPath null,
  104.         ?string $code null
  105.     ): ConstraintViolationInterface {
  106.         return new ConstraintViolation(
  107.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  108.             $messageTemplate,
  109.             $parameters,
  110.             null,
  111.             $propertyPath,
  112.             null,
  113.             null,
  114.             $code
  115.         );
  116.     }
  117. }