vendor/shopware/core/Framework/Api/Acl/AclWriteValidator.php line 28

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Api\Acl;
  3. use Shopware\Core\Framework\Api\Acl\Role\AclRoleDefinition;
  4. use Shopware\Core\Framework\Api\Context\AdminApiSource;
  5. use Shopware\Core\Framework\Context;
  6. use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  9. use Shopware\Core\Framework\Uuid\Uuid;
  10. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\Validator\ConstraintViolation;
  14. use Symfony\Component\Validator\ConstraintViolationInterface;
  15. use Symfony\Component\Validator\ConstraintViolationList;
  16. class AclWriteValidator implements EventSubscriberInterface
  17. {
  18.     public const VIOLATION_NO_PERMISSION 'no_permission_violation';
  19.     public static function getSubscribedEvents()
  20.     {
  21.         return [PreWriteValidationEvent::class => 'preValidate'];
  22.     }
  23.     public function preValidate(PreWriteValidationEvent $event): void
  24.     {
  25.         if ($event->getContext()->getScope() === Context::SYSTEM_SCOPE) {
  26.             return;
  27.         }
  28.         $commands $event->getCommands();
  29.         $source $event->getContext()->getSource();
  30.         if (!$source instanceof AdminApiSource || $source->isAdmin()) {
  31.             return;
  32.         }
  33.         $violationList = new ConstraintViolationList();
  34.         foreach ($commands as $command) {
  35.             $resource $command->getDefinition()->getEntityName();
  36.             $privilege $command->getPrivilege();
  37.             if ($privilege === null) {
  38.                 continue;
  39.             }
  40.             if (is_subclass_of($command->getDefinition(), EntityTranslationDefinition::class)) {
  41.                 $resource $command->getDefinition()->getParentDefinition()->getEntityName();
  42.                 if ($privilege !== AclRoleDefinition::PRIVILEGE_DELETE) {
  43.                     $privilege $this->getPrivilegeForParentWriteOperation($command$commands);
  44.                 }
  45.             }
  46.             if (!$source->isAllowed($resource ':' $privilege)) {
  47.                 $this->violates($privilege$resource$command$violationList);
  48.             }
  49.         }
  50.         $this->tryToThrow($violationList);
  51.     }
  52.     private function tryToThrow(ConstraintViolationList $violations): void
  53.     {
  54.         if ($violations->count() > 0) {
  55.             throw new WriteConstraintViolationException($violations''Response::HTTP_FORBIDDEN);
  56.         }
  57.     }
  58.     private function buildViolation(
  59.         string $messageTemplate,
  60.         array $parameters,
  61.         $root null,
  62.         ?string $propertyPath null,
  63.         $invalidValue null,
  64.         ?string $code null
  65.     ): ConstraintViolationInterface {
  66.         return new ConstraintViolation(
  67.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  68.             $messageTemplate,
  69.             $parameters,
  70.             $root,
  71.             $propertyPath,
  72.             $invalidValue,
  73.             null,
  74.             $code
  75.         );
  76.     }
  77.     private function violates(
  78.         string $privilege,
  79.         string $resource,
  80.         WriteCommand $command,
  81.         ConstraintViolationList $violationList
  82.     ): void {
  83.         $violationList->add(
  84.             $this->buildViolation(
  85.                 'No permissions to %privilege%".',
  86.                 ['%privilege%' => $resource ':' $privilege],
  87.                 null,
  88.                 '/' $command->getDefinition()->getEntityName(),
  89.                 null,
  90.                 self::VIOLATION_NO_PERMISSION
  91.             )
  92.         );
  93.     }
  94.     /**
  95.      * @param WriteCommand[] $commands
  96.      */
  97.     private function getPrivilegeForParentWriteOperation(WriteCommand $command, array $commands): string
  98.     {
  99.         $pathSuffix '/translations/' Uuid::fromBytesToHex($command->getPrimaryKey()['language_id']);
  100.         $parentCommandPath str_replace($pathSuffix''$command->getPath());
  101.         $parentCommand $this->findCommandByPath($parentCommandPath$commands);
  102.         // writes to translation need privilege from parent command
  103.         // if we update e.g. a product and add translations for a new language
  104.         // the writeCommand on the translation would be an insert
  105.         if ($parentCommand) {
  106.             return $parentCommand->getPrivilege();
  107.         }
  108.         // if we don't have a parentCommand it must be a update,
  109.         // because the parentEntity must already exist
  110.         return AclRoleDefinition::PRIVILEGE_UPDATE;
  111.     }
  112.     /**
  113.      * @param WriteCommand[] $commands
  114.      */
  115.     private function findCommandByPath(string $commandPath, array $commands): ?WriteCommand
  116.     {
  117.         foreach ($commands as $command) {
  118.             if ($command->getPath() === $commandPath) {
  119.                 return $command;
  120.             }
  121.         }
  122.         return null;
  123.     }
  124. }