diff --git a/src/Authorization/AbstractAuthorizationService.php b/src/Authorization/AbstractAuthorizationService.php index 20dec23e44ca6c45c65eeca0b6bab3249dbfe497..9bc4be20ccc7784ed28ee4d34a50bd881c8f9ba7 100644 --- a/src/Authorization/AbstractAuthorizationService.php +++ b/src/Authorization/AbstractAuthorizationService.php @@ -7,19 +7,23 @@ namespace Dbp\Relay\CoreBundle\Authorization; use Dbp\Relay\CoreBundle\API\UserSessionInterface; use Dbp\Relay\CoreBundle\Exception\ApiError; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; -abstract class AbstractAuthorizationService implements ContextAwareNormalizerInterface, NormalizerAwareInterface +abstract class AbstractAuthorizationService implements ContextAwareNormalizerInterface, NormalizerAwareInterface, ContextAwareDenormalizerInterface, DenormalizerAwareInterface { use NormalizerAwareTrait; + use DenormalizerAwareTrait; /* internal array keys */ private const ENTITY_SHORT_NAME_KEY = 'short_name'; private const ENTITY_CLASS_NAME_KEY = 'class_name'; - private const ENTITY_READ_ACCESS_ATTRIBUTE_NAMES_KEY = 'read_attribute_names'; + private const ENTITY_ATTRIBUTE_NAMES_KEY = 'attribute_names'; private const ENTITY_OBJECT_ALIAS = 'entity'; private const CONTEXT_GROUPS_KEY = 'groups'; @@ -33,11 +37,15 @@ abstract class AbstractAuthorizationService implements ContextAwareNormalizerInt private $config; /** @var array */ - private $entityClassNameToAttributeNamesMapping; + private $entityClassNameToReadAttributeNamesMapping; + + /** @var array */ + private $entityClassNameToWriteAttributeNamesMapping; public function __construct() { - $this->entityClassNameToAttributeNamesMapping = []; + $this->entityClassNameToReadAttributeNamesMapping = []; + $this->entityClassNameToWriteAttributeNamesMapping = []; } /** @@ -96,43 +104,83 @@ abstract class AbstractAuthorizationService implements ContextAwareNormalizerInt } /** - * {@inheritDoc} + * {@inheritdoc} */ public function normalize($object, $format = null, array $context = []) { $entityClassName = get_class($object); - $mapEntry = $this->entityClassNameToAttributeNamesMapping[$entityClassName]; + $mapEntry = $this->entityClassNameToReadAttributeNamesMapping[$entityClassName]; $entityShortName = $mapEntry[self::ENTITY_SHORT_NAME_KEY]; - foreach ($mapEntry[self::ENTITY_READ_ACCESS_ATTRIBUTE_NAMES_KEY] as $attributeName) { - $attributeId = self::toAttributeId($entityShortName, $attributeName); + foreach ($mapEntry[self::ENTITY_ATTRIBUTE_NAMES_KEY] as $attributeName) { + $attributeId = self::toReadAttributeId($entityShortName, $attributeName); if ($this->isGranted($attributeId, $object, self::ENTITY_OBJECT_ALIAS)) { $context[self::CONTEXT_GROUPS_KEY][] = $attributeId; } } - $context[self::getUniqueAlreadyCalledKeyForEntity($entityClassName)] = true; + $context[self::getUniqueNormalizerAlreadyCalledKeyForEntity($entityClassName)] = true; return $this->normalizer->normalize($object, $format, $context); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function supportsNormalization($data, $format = null, array $context = []): bool { - if ($this->entityClassNameToAttributeNamesMapping === null || is_object($data) === false) { + if ($this->entityClassNameToReadAttributeNamesMapping === null || is_object($data) === false) { return false; } $entityClassName = get_class($data); // Make sure we're not called twice - if (isset($context[self::getUniqueAlreadyCalledKeyForEntity($entityClassName)])) { + if (isset($context[self::getUniqueNormalizerAlreadyCalledKeyForEntity($entityClassName)])) { return false; } - return array_key_exists($entityClassName, $this->entityClassNameToAttributeNamesMapping); + return array_key_exists($entityClassName, $this->entityClassNameToReadAttributeNamesMapping); + } + + /** + * @return mixed + */ + public function denormalize($data, string $type, string $format = null, array $context = []) + { + $entityClassName = $type; + $mapEntry = $this->entityClassNameToReadAttributeNamesMapping[$entityClassName]; + $entityShortName = $mapEntry[self::ENTITY_SHORT_NAME_KEY]; + + foreach ($mapEntry[self::ENTITY_ATTRIBUTE_NAMES_KEY] as $attributeName) { + $attributeId = self::toWriteAttributeId($entityShortName, $attributeName); + if ($this->isGranted($attributeId, $data, self::ENTITY_OBJECT_ALIAS)) { + $context[self::CONTEXT_GROUPS_KEY][] = $attributeId; + } + } + + $context[self::getUniqueDenormalizerAlreadyCalledKeyForEntity($entityClassName)] = true; + + return $this->denormalizer->denormalize($data, $type, $format, $context); + } + + /** + * {@inheritdoc} + */ + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + { + if ($this->entityClassNameToWriteAttributeNamesMapping === null) { + return false; + } + + $entityClassName = $type; + + // Make sure we're not called twice + if (isset($context[self::getUniqueDenormalizerAlreadyCalledKeyForEntity($entityClassName)])) { + return false; + } + + return array_key_exists($entityClassName, $this->entityClassNameToWriteAttributeNamesMapping); } private function loadConfig() @@ -156,13 +204,23 @@ abstract class AbstractAuthorizationService implements ContextAwareNormalizerInt foreach ($entitiesConfigNode as $entityShortName => $entityNode) { $entityClassName = $entityNode[AuthorizationConfigDefinition::ENTITY_CLASS_NAME_CONFIG_NODE]; $attributeNames = []; + foreach ($entityNode[AuthorizationConfigDefinition::ENTITY_READ_ACCESS_CONFIG_NODE] ?? [] as $attributeName => $attributeAuthorizationExpression) { - $roleExpressions[self::toAttributeId($entityShortName, $attributeName)] = $attributeAuthorizationExpression; + $roleExpressions[self::toReadAttributeId($entityShortName, $attributeName)] = $attributeAuthorizationExpression; $attributeNames[] = $attributeName; } - $this->entityClassNameToAttributeNamesMapping[$entityClassName] = [ + $this->entityClassNameToReadAttributeNamesMapping[$entityClassName] = [ self::ENTITY_SHORT_NAME_KEY => $entityShortName, - self::ENTITY_READ_ACCESS_ATTRIBUTE_NAMES_KEY => $attributeNames, + self::ENTITY_ATTRIBUTE_NAMES_KEY => $attributeNames, + ]; + + foreach ($entityNode[AuthorizationConfigDefinition::ENTITY_WRITE_ACCESS_CONFIG_NODE] ?? [] as $attributeName => $attributeAuthorizationExpression) { + $roleExpressions[self::toWriteAttributeId($entityShortName, $attributeName)] = $attributeAuthorizationExpression; + $attributeNames[] = $attributeName; + } + $this->entityClassNameToWriteAttributeNamesMapping[$entityClassName] = [ + self::ENTITY_SHORT_NAME_KEY => $entityShortName, + self::ENTITY_ATTRIBUTE_NAMES_KEY => $attributeNames, ]; } @@ -182,13 +240,23 @@ abstract class AbstractAuthorizationService implements ContextAwareNormalizerInt return $this->userAuthorizationChecker->isGranted($this->currentAuthorizationUser, $rightName, $object, $objectAlias); } - private static function toAttributeId(string $entityShortName, string $attributeName): string + private static function toReadAttributeId(string $entityShortName, string $attributeName): string + { + return $entityShortName.':output:'.$attributeName; + } + + private static function toWriteAttributeId(string $entityShortName, string $attributeName): string + { + return $entityShortName.':input:'.$attributeName; + } + + private static function getUniqueNormalizerAlreadyCalledKeyForEntity(string $entityClassName): string { - return $entityShortName.':'.$attributeName; + return self::class.'.normalize.'.$entityClassName; } - private static function getUniqueAlreadyCalledKeyForEntity(string $entityClassName): string + private static function getUniqueDenormalizerAlreadyCalledKeyForEntity(string $entityClassName): string { - return self::class.$entityClassName; + return self::class.'.denormalizer.'.$entityClassName; } } diff --git a/src/Authorization/AuthorizationConfigDefinition.php b/src/Authorization/AuthorizationConfigDefinition.php index 25599e466d33e8db306acb888a34c2008466449c..a101475919fa819f44b3668f52c83bca8a0016e7 100644 --- a/src/Authorization/AuthorizationConfigDefinition.php +++ b/src/Authorization/AuthorizationConfigDefinition.php @@ -99,6 +99,7 @@ class AuthorizationConfigDefinition ->defaultValue($entityDefinition[self::ENTITY_CLASS_NAME_KEY]) ->info('The entity class name. There is no need to change the default value.') ->end(); + $entityReadAccessChildBuilder = $entityChildBuilder->arrayNode(self::ENTITY_READ_ACCESS_CONFIG_NODE) ->children(); foreach ($entityDefinition[self::ENTITY_READ_ACCESS_KEY] ?? [] as $attributeName) { @@ -108,7 +109,7 @@ class AuthorizationConfigDefinition ->end(); } - $entityWriteAccessChildBuilder = $entitiesNodeChildBuilder->arrayNode(self::ENTITY_WRITE_ACCESS_CONFIG_NODE) + $entityWriteAccessChildBuilder = $entityChildBuilder->arrayNode(self::ENTITY_WRITE_ACCESS_CONFIG_NODE) ->children(); foreach ($entityDefinition[self::ENTITY_WRITE_ACCESS_KEY] ?? [] as $attributeName) { $entityWriteAccessChildBuilder->scalarNode($attributeName) diff --git a/src/ExpressionLanguage/ExpressionFunctionProviders/ArrayExpressionFunctionProvider.php b/src/ExpressionLanguage/ExpressionFunctionProviders/ArrayExpressionFunctionProvider.php index 6d4d8891e0c0ae576a1941296e26d649a1ac44d7..e519fedb1211a2392d5c0ff7f6bcc5a9c4e49a26 100644 --- a/src/ExpressionLanguage/ExpressionFunctionProviders/ArrayExpressionFunctionProvider.php +++ b/src/ExpressionLanguage/ExpressionFunctionProviders/ArrayExpressionFunctionProvider.php @@ -19,6 +19,13 @@ class ArrayExpressionFunctionProvider implements ExpressionFunctionProviderInter function ($arguments, $varName): bool { return empty($varName); }), + new ExpressionFunction('array_key_exists', + function (string $keyName, string $arrayName): string { + return sprintf('array_key_exists(%s, %s)', $keyName, $arrayName); + }, + function ($arguments, $keyName, $arrayName): bool { + return array_key_exists($keyName, $arrayName); + }), ]; } }