src/Core/Framework/DataAbstractionLayer/EntityProtection/EntityProtectionValidator.php line 79

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\DataAbstractionLayer\EntityProtection;
  3. use Shopware\Core\Framework\Context;
  4. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  5. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntitySearchedEvent;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  10. use Shopware\Core\Framework\Log\Package;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  13. /**
  14.  * @internal
  15.  */
  16. #[Package('core')]
  17. class EntityProtectionValidator implements EventSubscriberInterface
  18. {
  19.     /**
  20.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  21.      */
  22.     public static function getSubscribedEvents(): array
  23.     {
  24.         return [
  25.             PreWriteValidationEvent::class => 'validateWriteCommands',
  26.             EntitySearchedEvent::class => 'validateEntitySearch',
  27.         ];
  28.     }
  29.     /**
  30.      * @param list<array{entity: string, value: string|null, definition: EntityDefinition, field: Field|null}> $pathSegments
  31.      * @param array<string> $protections FQCN of the protections that need to be validated
  32.      */
  33.     public function validateEntityPath(array $pathSegments, array $protectionsContext $context): void
  34.     {
  35.         foreach ($pathSegments as $pathSegment) {
  36.             /** @var EntityDefinition $definition */
  37.             $definition $pathSegment['definition'];
  38.             foreach ($protections as $protection) {
  39.                 $protectionInstance $definition->getProtections()->get($protection);
  40.                 if (!$protectionInstance || $protectionInstance->isAllowed($context->getScope())) {
  41.                     continue;
  42.                 }
  43.                 throw new AccessDeniedHttpException(
  44.                     sprintf('API access for entity "%s" not allowed.'$pathSegment['entity'])
  45.                 );
  46.             }
  47.         }
  48.     }
  49.     public function validateEntitySearch(EntitySearchedEvent $event): void
  50.     {
  51.         $definition $event->getDefinition();
  52.         $readProtection $definition->getProtections()->get(ReadProtection::class);
  53.         $context $event->getContext();
  54.         if ($readProtection && !$readProtection->isAllowed($context->getScope())) {
  55.             throw new AccessDeniedHttpException(
  56.                 sprintf(
  57.                     'Read access to entity "%s" not allowed for scope "%s".',
  58.                     $definition->getEntityName(),
  59.                     $context->getScope()
  60.                 )
  61.             );
  62.         }
  63.         $this->validateCriteriaAssociation(
  64.             $definition,
  65.             $event->getCriteria()->getAssociations(),
  66.             $context
  67.         );
  68.     }
  69.     public function validateWriteCommands(PreWriteValidationEvent $event): void
  70.     {
  71.         foreach ($event->getCommands() as $command) {
  72.             // Don't validate commands that fake operations on DB level, e.g. cascade deletes
  73.             if (!$command->isValid()) {
  74.                 continue;
  75.             }
  76.             $writeProtection $command->getDefinition()->getProtections()->get(WriteProtection::class);
  77.             if ($writeProtection && !$writeProtection->isAllowed($event->getContext()->getScope())) {
  78.                 throw new AccessDeniedHttpException(
  79.                     sprintf(
  80.                         'Write access to entity "%s" are not allowed in scope "%s".',
  81.                         $command->getDefinition()->getEntityName(),
  82.                         $event->getContext()->getScope()
  83.                     )
  84.                 );
  85.             }
  86.         }
  87.     }
  88.     /**
  89.      * @param array<string, Criteria> $associations
  90.      */
  91.     private function validateCriteriaAssociation(EntityDefinition $definition, array $associationsContext $context): void
  92.     {
  93.         /** @var Criteria $criteria */
  94.         foreach ($associations as $associationName => $criteria) {
  95.             $field $definition->getField($associationName);
  96.             if (!$field instanceof AssociationField) {
  97.                 continue;
  98.             }
  99.             $associationDefinition $field->getReferenceDefinition();
  100.             $readProtection $associationDefinition->getProtections()->get(ReadProtection::class);
  101.             if ($readProtection && !$readProtection->isAllowed($context->getScope())) {
  102.                 throw new AccessDeniedHttpException(
  103.                     sprintf(
  104.                         'Read access to nested association "%s" on entity "%s" not allowed for scope "%s".',
  105.                         $associationName,
  106.                         $definition->getEntityName(),
  107.                         $context->getScope()
  108.                     )
  109.                 );
  110.             }
  111.             $this->validateCriteriaAssociation($associationDefinition$criteria->getAssociations(), $context);
  112.         }
  113.     }
  114. }