diff --git a/src/Authorization/AbstractAuthorizationService.php b/src/Authorization/AbstractAuthorizationService.php index 9911ced147eb8cf86e580bb8cd677f4b505420be..fc69b1a6cf8120c5e1c7304bc1b4e0843bc158e5 100644 --- a/src/Authorization/AbstractAuthorizationService.php +++ b/src/Authorization/AbstractAuthorizationService.php @@ -10,6 +10,9 @@ use Symfony\Component\HttpFoundation\Response; abstract class AbstractAuthorizationService { + public const RIGHTS_CONFIG_ATTRIBUTE = AuthorizationExpressionChecker::RIGHTS_CONFIG_ATTRIBUTE; + public const ATTRIBUTES_CONFIG_ATTRIBUTE = AuthorizationExpressionChecker::ATTRIBUTES_CONFIG_ATTRIBUTE; + /** @var AuthorizationExpressionChecker */ private $userAuthorizationChecker; diff --git a/src/Entity/NamedEntityInterface.php b/src/Entity/NamedEntityInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6ef8159d432d121d5febe62809d7939469640f57 --- /dev/null +++ b/src/Entity/NamedEntityInterface.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Entity; + +/** + * Interface for entities with an identifier and a name. + */ +interface NamedEntityInterface +{ + public function getIdentifier(); + + public function getName(); +} diff --git a/src/LocalData/AbstractLocalDataPostEventSubscriber.php b/src/LocalData/AbstractLocalDataPostEventSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..9af529e18ef55dff5dfd21743c4936ea46560f8c --- /dev/null +++ b/src/LocalData/AbstractLocalDataPostEventSubscriber.php @@ -0,0 +1,82 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\LocalData; + +use Dbp\Relay\CoreBundle\Authorization\AbstractAuthorizationService; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizationService implements EventSubscriberInterface +{ + public const CONFIG_NODE = 'local_data_mapping'; + public const SOURCE_ATTRIBUTE_KEY = 'source_attribute'; + public const LOCAL_DATA_ATTRIBUTE_KEY = 'local_data_attribute'; + public const AUTHORIZATION_EXPRESSION_KEY = 'authorization_expression'; + + /** @var string[] */ + private $attributeMapping; + + public function setConfig(array $config) + { + $configNode = $config[self::CONFIG_NODE] ?? []; + + $rights = []; + foreach ($configNode as $configMappingEntry) { + if (isset($this->attributeMapping[$configMappingEntry[self::LOCAL_DATA_ATTRIBUTE_KEY]])) { + throw new \RuntimeException(sprintf('multiple mapping entries for local data attribute %s', $configMappingEntry[self::LOCAL_DATA_ATTRIBUTE_KEY])); + } + $this->attributeMapping[$configMappingEntry[self::LOCAL_DATA_ATTRIBUTE_KEY]] = $configMappingEntry[self::SOURCE_ATTRIBUTE_KEY]; + // the name of the local data attribute is used as name for the right to view that attribute + // the attribute is visible false by default + $rights[$configMappingEntry[self::LOCAL_DATA_ATTRIBUTE_KEY]] = $configMappingEntry[self::AUTHORIZATION_EXPRESSION_KEY] ?? 'false'; + } + + if (!empty($rights)) { + parent::setConfig([AbstractAuthorizationService::RIGHTS_CONFIG_ATTRIBUTE => $rights]); + } + } + + public static function getSubscribedEvents(): array + { + return [static::getSubscribedEventName() => 'onPost']; + } + + public static function getSubscribedEventName(): string + { + throw new \RuntimeException(sprintf('child classes must implement the \'%s\' method', __METHOD__)); + } + + public function onPost(LocalDataPostEvent $postEvent) + { + $sourceData = $postEvent->getSourceData(); + + foreach ($this->attributeMapping as $localDataAttributeName => $sourceAttributeName) { + if ($this->isGranted($localDataAttributeName)) { + if ($sourceAttributeValue = $sourceData[$sourceAttributeName] ?? null) { + $postEvent->trySetLocalDataAttribute($localDataAttributeName, $sourceAttributeValue); + } else { + throw new \RuntimeException(sprintf('attribute \'%s\' not available in source data', $sourceAttributeName)); + } + } + } + } + + public static function getConfigNode() + { + $treeBuilder = new TreeBuilder(self::CONFIG_NODE); + + return $treeBuilder->getRootNode() + ->arrayPrototype() + ->children() + ->scalarNode(self::SOURCE_ATTRIBUTE_KEY)->end() + ->scalarNode(self::LOCAL_DATA_ATTRIBUTE_KEY)->end() + ->scalarNode(self::AUTHORIZATION_EXPRESSION_KEY) + ->defaultValue('false') + ->end() + ->end() + ->end() + ; + } +} diff --git a/src/LocalData/LocalData.php b/src/LocalData/LocalData.php index 547d0e55a1fbc30836e206ec642ab493cedce7e8..14bc0bab8534359d6c84fd2b63e35e430cb55c25 100644 --- a/src/LocalData/LocalData.php +++ b/src/LocalData/LocalData.php @@ -35,4 +35,9 @@ class LocalData { return $options[self::QUERY_PARAMETER_NAME] ?? null; } + + public static function toIncludeLocalParameterValue(array $attributeNames): string + { + return implode(LocalDataEventDispatcher::SEPARATOR, $attributeNames); + } } diff --git a/src/LocalData/LocalDataAwareEventDispatcher.php b/src/LocalData/LocalDataEventDispatcher.php similarity index 96% rename from src/LocalData/LocalDataAwareEventDispatcher.php rename to src/LocalData/LocalDataEventDispatcher.php index d3d8e0bf97b4a9ce823bbaf1482addff16769b65..90085e4696e90dc08b66b64cc0acdfc2812d0350 100644 --- a/src/LocalData/LocalDataAwareEventDispatcher.php +++ b/src/LocalData/LocalDataEventDispatcher.php @@ -13,8 +13,10 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\EventDispatcher\Event; -class LocalDataAwareEventDispatcher +class LocalDataEventDispatcher { + public const SEPARATOR = ','; + /** @var array */ private $queryParameters; @@ -79,10 +81,10 @@ class LocalDataAwareEventDispatcher */ public function dispatch(Event $event, string $eventName): void { - if ($event instanceof LocalDataAwarePreEvent) { + if ($event instanceof LocalDataPreEvent) { $event->setQueryParameters($this->queryParameters); $this->eventDispatcher->dispatch($event, $eventName); - } elseif ($event instanceof LocalDataAwarePostEvent) { + } elseif ($event instanceof LocalDataPostEvent) { $event->setRequestedAttributes($this->requestedAttributes); $this->eventDispatcher->dispatch($event, $eventName); @@ -130,7 +132,7 @@ class LocalDataAwareEventDispatcher $this->requestedAttributes = []; if (!Tools::isNullOrEmpty($includeParameter)) { - $requestedAttributes = explode(',', $includeParameter); + $requestedAttributes = explode(self::SEPARATOR, $includeParameter); foreach ($requestedAttributes as $requestedAttribute) { $requestedAttribute = trim($requestedAttribute); @@ -155,7 +157,7 @@ class LocalDataAwareEventDispatcher $this->queryParameters = []; if (!Tools::isNullOrEmpty($queryParameter)) { - $localQueryParameters = explode(',', $queryParameter); + $localQueryParameters = explode(self::SEPARATOR, $queryParameter); foreach ($localQueryParameters as $localQueryParameter) { $localQueryParameter = trim($localQueryParameter); diff --git a/src/LocalData/LocalDataAwarePostEvent.php b/src/LocalData/LocalDataPostEvent.php similarity index 87% rename from src/LocalData/LocalDataAwarePostEvent.php rename to src/LocalData/LocalDataPostEvent.php index f49a42021ffd33251854365768d65454cbb318d9..a8b8c9dc1615b38f8296a3f5d61c0d6c2a27b131 100644 --- a/src/LocalData/LocalDataAwarePostEvent.php +++ b/src/LocalData/LocalDataPostEvent.php @@ -9,22 +9,31 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Contracts\EventDispatcher\Event; -class LocalDataAwarePostEvent extends Event implements LoggerAwareInterface +class LocalDataPostEvent extends Event implements LoggerAwareInterface { use LoggerAwareTrait; /** @var LocalDataAwareInterface */ private $entity; + /** @var array */ + private $sourceData; + /** @var array */ private $requestedAttributes; - protected function __construct(LocalDataAwareInterface $entity) + public function __construct(LocalDataAwareInterface $entity, array $sourceData) { $this->entity = $entity; + $this->sourceData = $sourceData; + } + + public function getSourceData(): array + { + return $this->sourceData; } - protected function getEntityInternal(): LocalDataAwareInterface + public function getEntity(): LocalDataAwareInterface { return $this->entity; } @@ -100,7 +109,7 @@ class LocalDataAwarePostEvent extends Event implements LoggerAwareInterface if ($arrayKey === false) { if ($warnfNotFound) { if ($this->logger !== null) { - $this->logger->warning(sprintf("trying to set local data attribute '%s', which was not requested for entity '%s'", $key, LocalDataAwareEventDispatcher::getUniqueEntityName(get_class($this->entity)))); + $this->logger->warning(sprintf("trying to set local data attribute '%s', which was not requested for entity '%s'", $key, LocalDataEventDispatcher::getUniqueEntityName(get_class($this->entity)))); } assert(false); } else { diff --git a/src/LocalData/LocalDataAwarePreEvent.php b/src/LocalData/LocalDataPreEvent.php similarity index 94% rename from src/LocalData/LocalDataAwarePreEvent.php rename to src/LocalData/LocalDataPreEvent.php index de1a7a63040effe749cde873972e10586e702437..fcc7428bfd286192b588d9e51743d5fc736cb806 100644 --- a/src/LocalData/LocalDataAwarePreEvent.php +++ b/src/LocalData/LocalDataPreEvent.php @@ -6,7 +6,7 @@ namespace Dbp\Relay\CoreBundle\LocalData; use Symfony\Contracts\EventDispatcher\Event; -class LocalDataAwarePreEvent extends Event +class LocalDataPreEvent extends Event { public const NAME = 'dbp.relay.relay_core.local_data_aware_event.pre';