Skip to content
Snippets Groups Projects
Commit 314a2c41 authored by Groß-Vogt, Tobias's avatar Groß-Vogt, Tobias
Browse files

support for custom denormalization authorization

parent c4024bf4
Branches
Tags
No related merge requests found
......@@ -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;
}
}
......@@ -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)
......
......@@ -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);
}),
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment