Skip to content
Snippets Groups Projects
Commit fa743c09 authored by Tobias Gross-Vogt's avatar Tobias Gross-Vogt
Browse files

local data mapping: allow list of source attributes ordered by preferred usage

parent 93718322
No related branches found
No related tags found
No related merge requests found
Pipeline #227970 passed
...@@ -21,13 +21,13 @@ use Symfony\Component\HttpFoundation\Response; ...@@ -21,13 +21,13 @@ use Symfony\Component\HttpFoundation\Response;
abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizationService implements EventSubscriberInterface abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizationService implements EventSubscriberInterface
{ {
protected const ROOT_CONFIG_NODE = 'local_data_mapping'; protected const ROOT_CONFIG_NODE = 'local_data_mapping';
protected const SOURCE_ATTRIBUTE_CONFIG_NODE = 'source_attribute'; protected const SOURCE_ATTRIBUTES_CONFIG_NODE = 'source_attributes';
protected const LOCAL_DATA_ATTRIBUTE_CONFIG_NODE = 'local_data_attribute'; protected const LOCAL_DATA_ATTRIBUTE_CONFIG_NODE = 'local_data_attribute';
protected const AUTHORIZATION_EXPRESSION_CONFIG_NODE = 'authorization_expression'; protected const AUTHORIZATION_EXPRESSION_CONFIG_NODE = 'authorization_expression';
protected const DEFAULT_VALUE_ATTRIBUTE_CONFIG_NODE = 'default_value'; protected const DEFAULT_VALUE_ATTRIBUTE_CONFIG_NODE = 'default_value';
protected const DEFAULT_VALUES_ATTRIBUTE_CONFIG_NODE = 'default_values'; protected const DEFAULT_VALUES_ATTRIBUTE_CONFIG_NODE = 'default_values';
private const SOURCE_ATTRIBUTE_KEY = 'source'; private const SOURCE_ATTRIBUTES_KEY = 'source';
private const DEFAULT_VALUE_KEY = 'default'; private const DEFAULT_VALUE_KEY = 'default';
/* /*
...@@ -57,10 +57,16 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio ...@@ -57,10 +57,16 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio
} }
$attributeMapEntry = []; $attributeMapEntry = [];
$attributeMapEntry[self::SOURCE_ATTRIBUTE_KEY] = $configMappingEntry[self::SOURCE_ATTRIBUTE_CONFIG_NODE]; $attributeMapEntry[self::SOURCE_ATTRIBUTES_KEY] = $configMappingEntry[self::SOURCE_ATTRIBUTES_CONFIG_NODE];
$defaultValue = $configMappingEntry[self::DEFAULT_VALUE_ATTRIBUTE_CONFIG_NODE] ?? null;
if ($defaultValue === null) {
$defaultArray = $configMappingEntry[self::DEFAULT_VALUES_ATTRIBUTE_CONFIG_NODE] ?? null;
if ($defaultArray !== null && $defaultArray !== self::ARRAY_VALUE_NOT_SPECIFIED) {
$defaultValue = $defaultArray;
}
}
$defaultValue = $configMappingEntry[self::DEFAULT_VALUE_ATTRIBUTE_CONFIG_NODE] ??
((($defaultArray = $configMappingEntry[self::DEFAULT_VALUES_ATTRIBUTE_CONFIG_NODE]) !== self::ARRAY_VALUE_NOT_SPECIFIED) ? $defaultArray : null);
if ($defaultValue !== null) { if ($defaultValue !== null) {
$attributeMapEntry[self::DEFAULT_VALUE_KEY] = $defaultValue; $attributeMapEntry[self::DEFAULT_VALUE_KEY] = $defaultValue;
} }
...@@ -97,12 +103,19 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio ...@@ -97,12 +103,19 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio
throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, sprintf('access to local data attribute \'%s\' denied', $localDataAttributeName)); throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, sprintf('access to local data attribute \'%s\' denied', $localDataAttributeName));
} }
$sourceAttributeName = $attributeMapEntry[self::SOURCE_ATTRIBUTE_KEY]; $attributeValue = null;
$attributeValue = $sourceData[$sourceAttributeName] ?? $attributeMapEntry[self::DEFAULT_VALUE_KEY] ?? null; foreach ($attributeMapEntry[self::SOURCE_ATTRIBUTES_KEY] as $sourceAttributeName) {
if (($value = $sourceData[$sourceAttributeName] ?? null) !== null) {
$attributeValue = $value;
break;
}
}
$attributeValue = $attributeValue ?? $attributeMapEntry[self::DEFAULT_VALUE_KEY] ?? null;
if ($attributeValue !== null) { if ($attributeValue !== null) {
$postEvent->setLocalDataAttribute($localDataAttributeName, $attributeValue); $postEvent->setLocalDataAttribute($localDataAttributeName, $attributeValue);
} else { } else {
throw ApiError::withDetails(Response::HTTP_INTERNAL_SERVER_ERROR, sprintf('attribute \'%s\' not available in source data', $sourceAttributeName)); throw ApiError::withDetails(Response::HTTP_INTERNAL_SERVER_ERROR, sprintf('none of the source attributes available for local data attribute \'%s\'', $localDataAttributeName));
} }
} }
} }
...@@ -123,17 +136,23 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio ...@@ -123,17 +136,23 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio
return $treeBuilder->getRootNode() return $treeBuilder->getRootNode()
->arrayPrototype() ->arrayPrototype()
->children() ->children()
->scalarNode(self::SOURCE_ATTRIBUTE_CONFIG_NODE)->end() ->scalarNode(self::LOCAL_DATA_ATTRIBUTE_CONFIG_NODE)
->scalarNode(self::LOCAL_DATA_ATTRIBUTE_CONFIG_NODE)->end() ->info('The name of the local data attribute.')
->end()
->arrayNode(self::SOURCE_ATTRIBUTES_CONFIG_NODE)
->info('The list of source attributes to map to the local data attribute ordered by preferred usage. If an attribute is not found, the next attribute in the list is used.')
->scalarPrototype()->end()
->end()
->scalarNode(self::AUTHORIZATION_EXPRESSION_CONFIG_NODE) ->scalarNode(self::AUTHORIZATION_EXPRESSION_CONFIG_NODE)
->defaultValue('false') ->defaultValue('false')
->info('A boolean expression evaluable by the Symfony Expression Language determining whether the current user may request read the local data attribute.')
->end() ->end()
->scalarNode(self::DEFAULT_VALUE_ATTRIBUTE_CONFIG_NODE) ->scalarNode(self::DEFAULT_VALUE_ATTRIBUTE_CONFIG_NODE)
->info('The default value for scalar (non-array) attributes. If none is specified, an exception is thrown in the case the source attribute is not found.') ->info('The default value for scalar (i.e. non-array) attributes. If none is specified, an exception is thrown in case none of the source attributes is found.')
->end() ->end()
->arrayNode(self::DEFAULT_VALUES_ATTRIBUTE_CONFIG_NODE) ->arrayNode(self::DEFAULT_VALUES_ATTRIBUTE_CONFIG_NODE)
->defaultValue(self::ARRAY_VALUE_NOT_SPECIFIED) ->defaultValue(self::ARRAY_VALUE_NOT_SPECIFIED)
->info('The default value for array type attributes. If none is specified, an exception is thrown in the case the source attribute is not found.') ->info('The default value for array type attributes. If none is specified, an exception is thrown in case none of the source attributes is found.')
->scalarPrototype()->end() ->scalarPrototype()->end()
->end() ->end()
->end() ->end()
......
...@@ -32,12 +32,13 @@ class LocalDataEventDispatcher ...@@ -32,12 +32,13 @@ class LocalDataEventDispatcher
/** /**
* @param string $resourceClass The class name of the entity (resource) this event dispatcher is responsible for * @param string $resourceClass The class name of the entity (resource) this event dispatcher is responsible for
* @param EventDispatcherInterface $eventDispatcher The inner event dispatcher that this event dispatcher decorates * @param EventDispatcherInterface $eventDispatcher The inner event dispatcher that this event dispatcher decorates
* @param string|null $uniqueEntityName The unique name of the entity. If not specified or empty, the 'shortName' attribute of the entities @ApiResource annotation is used.
*/ */
public function __construct(string $resourceClass, EventDispatcherInterface $eventDispatcher) public function __construct(string $resourceClass, EventDispatcherInterface $eventDispatcher, string $uniqueEntityName = null)
{ {
$this->queryParameters = []; $this->queryParameters = [];
$this->requestedAttributes = []; $this->requestedAttributes = [];
$this->uniqueEntityName = self::getUniqueEntityName($resourceClass); $this->uniqueEntityName = !Tools::isNullOrEmpty($uniqueEntityName) ? $uniqueEntityName : self::getUniqueEntityName($resourceClass);
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
} }
...@@ -113,7 +114,7 @@ class LocalDataEventDispatcher ...@@ -113,7 +114,7 @@ class LocalDataEventDispatcher
} }
$uniqueName = $resourceMetadata->getShortName() ?? ''; $uniqueName = $resourceMetadata->getShortName() ?? '';
if (empty($uniqueName)) { if (Tools::isNullOrEmpty($uniqueName)) {
throw new ApiError(500, sprintf("'shortName' attribute missing in ApiResource annotation of resource class '%s'", $resourceClass)); throw new ApiError(500, sprintf("'shortName' attribute missing in ApiResource annotation of resource class '%s'", $resourceClass));
} elseif (str_contains($uniqueName, '.') || str_contains($uniqueName, ',')) { } elseif (str_contains($uniqueName, '.') || str_contains($uniqueName, ',')) {
throw new ApiError(500, sprintf("'shortName' attribute of resource class '%s' must not contain '.' or ',' characters: '%s'", $resourceClass, $uniqueName)); throw new ApiError(500, sprintf("'shortName' attribute of resource class '%s' must not contain '.' or ',' characters: '%s'", $resourceClass, $uniqueName));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment