<?php declare(strict_types=1);
namespace Shopware\Core\Framework\App\Subscriber;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Api\Context\AdminApiSource;
use Shopware\Core\Framework\Api\Context\SystemSource;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
use Shopware\Core\System\CustomField\Aggregate\CustomFieldSet\CustomFieldSetDefinition;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
/**
* @internal only for use by the app-system, will be considered internal from v6.4.0 onward
*/
class CustomFieldProtectionSubscriber implements EventSubscriberInterface
{
public const VIOLATION_NO_PERMISSION = 'no_permission_violation';
private Connection $connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
*/
public static function getSubscribedEvents()
{
return [
PreWriteValidationEvent::class => 'checkWrite',
];
}
public function checkWrite(PreWriteValidationEvent $event): void
{
$context = $event->getContext();
if ($context->getSource() instanceof SystemSource || $context->getScope() === Context::SYSTEM_SCOPE) {
return;
}
$integrationId = $this->getIntegrationId($context);
$violationList = new ConstraintViolationList();
foreach ($event->getCommands() as $command) {
if (
!($command->getDefinition() instanceof CustomFieldSetDefinition)
|| $command instanceof InsertCommand
) {
continue;
}
$appIntegrationId = $this->fetchIntegrationIdOfAssociatedApp($command);
if (!$appIntegrationId) {
continue;
}
if ($integrationId !== $appIntegrationId) {
$this->addViolation($violationList, $command);
}
}
if ($violationList->count() > 0) {
$event->getExceptions()->add(new WriteConstraintViolationException($violationList));
}
}
private function getIntegrationId(Context $context): ?string
{
$source = $context->getSource();
if (!($source instanceof AdminApiSource)) {
return null;
}
return $source->getIntegrationId();
}
private function fetchIntegrationIdOfAssociatedApp(WriteCommand $command): ?string
{
$id = $command->getPrimaryKey()['id'];
$integrationId = $this->connection->executeQuery('
SELECT `app`.`integration_id`
FROM `app`
INNER JOIN `custom_field_set` ON `custom_field_set`.`app_id` = `app`.`id`
WHERE `custom_field_set`.`id` = :customFieldSetId
', ['customFieldSetId' => $id])->fetchOne();
if (!$integrationId) {
return null;
}
return Uuid::fromBytesToHex($integrationId);
}
private function addViolation(ConstraintViolationList $violationList, WriteCommand $command): void
{
$violationList->add(
$this->buildViolation(
'No permissions to %privilege%".',
['%privilege%' => 'write:custom_field_set'],
'/' . $command->getDefinition()->getEntityName(),
self::VIOLATION_NO_PERMISSION
)
);
}
/**
* @param array<string, string> $parameters
*/
private function buildViolation(
string $messageTemplate,
array $parameters,
?string $propertyPath = null,
?string $code = null
): ConstraintViolationInterface {
return new ConstraintViolation(
str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
$messageTemplate,
$parameters,
null,
$propertyPath,
null,
null,
$code
);
}
}