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

#25859 abstract normalizer that allows configuring access exrpressions for entity attributes

parent bb0d8a8a
Branches
Tags
No related merge requests found
Pipeline #231263 passed
<?php
declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Authorization\Serializer;
use Dbp\Relay\CoreBundle\Authorization\AbstractAuthorizationService;
use Dbp\Relay\CoreBundle\Exception\ApiError;
use Dbp\Relay\CoreBundle\Helpers\ApiPlatformHelperFunctions;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
abstract class AbstractEntityNormalizer extends AbstractAuthorizationService implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
private const ROOT_CONFIG_NODE = 'attribute_access';
private const ENTITY_SHORT_NAME_KEY = 'short_name';
private const ATTRIBUTE_NAMES_KEY = 'attribute_names';
private const ENTITY_OBJECT_ALIAS = 'entity';
/** @var Security */
private $security;
/** @var array */
private $entityClassNames;
/** @var array */
private $entityClassNameToAttributeNamesMapping;
public static function getAttributeAccessConfigNodeDefinition(array $entityShortNameToAttributeNamesMapping): NodeDefinition
{
$treeBuilder = new TreeBuilder(self::ROOT_CONFIG_NODE);
foreach ($entityShortNameToAttributeNamesMapping as $entityShortName => $attributeNames) {
$attributeNodeBuilder = $treeBuilder->getRootNode()->children()->arrayNode($entityShortName)
->addDefaultsIfNotSet()
->children();
foreach ($attributeNames as $attributeName) {
$attributeNodeBuilder->scalarNode($attributeName)
->defaultValue('false')
->info(sprintf('viewer role expression for attribute \'%s\' under entity \'%s\'', $attributeName, $entityShortName))
->end();
}
}
return $treeBuilder->getRootNode();
}
private static function toAttributeId(string $entityShortName, string $attributeName): string
{
return $entityShortName.':'.$attributeName;
}
private static function getUniqueAlreadyCalledKeyForEntity(string $entityClassName): string
{
return self::class.$entityClassName;
}
protected function __construct(array $entityClassNames)
{
$this->entityClassNames = $entityClassNames;
}
public function setConfig(array $config)
{
$configNode = $config[self::ROOT_CONFIG_NODE] ?? [];
$rightExpressions = [];
foreach ($this->entityClassNames as $entityClassName) {
$entityShortName = ApiPlatformHelperFunctions::getShortNameForResource($entityClassName);
$entityNode = $configNode[$entityShortName] ?? null;
if ($entityNode === null) {
throw ApiError::withDetails(Response::HTTP_INTERNAL_SERVER_ERROR, sprintf('attribute access not configured for entity \'%s\'', $entityShortName));
}
$attributeNames = [];
foreach ($entityNode as $attributeName => $attributeAuthorizationExpression) {
$rightExpressions[self::toAttributeId($entityShortName, $attributeName)] = $attributeAuthorizationExpression;
$attributeNames[] = $attributeName;
}
$this->entityClassNameToAttributeNamesMapping[$entityClassName] = [
self::ENTITY_SHORT_NAME_KEY => $entityShortName,
self::ATTRIBUTE_NAMES_KEY => $attributeNames,
];
}
parent::setConfig(parent::createConfig($rightExpressions));
}
/**
* @required
*/
public function __inject(Security $security)
{
$this->security = $security;
}
/**
* {@inheritDoc}
*/
public function normalize($object, $format = null, array $context = [])
{
$entityClassName = get_class($object);
$mapEntry = $this->entityClassNameToAttributeNamesMapping[$entityClassName];
$entityShortName = $mapEntry[self::ENTITY_SHORT_NAME_KEY];
foreach ($mapEntry[self::ATTRIBUTE_NAMES_KEY] as $attributeName) {
$attributeId = self::toAttributeId($entityShortName, $attributeName);
dump($attributeId);
if ($this->isGranted($attributeId, $object, self::ENTITY_OBJECT_ALIAS)) {
$context['groups'][] = $attributeId;
}
}
$context[self::getUniqueAlreadyCalledKeyForEntity($entityClassName)] = true;
return $this->normalizer->normalize($object, $format, $context);
}
/**
* {@inheritDoc}
*/
public function supportsNormalization($data, $format = null, array $context = []): bool
{
if (!is_object($data)) {
return false;
}
$entityClassName = get_class($data);
// Make sure we're not called twice
if (isset($context[self::getUniqueAlreadyCalledKeyForEntity($entityClassName)])) {
return false;
}
return array_key_exists($entityClassName, $this->entityClassNameToAttributeNamesMapping);
}
}
<?php
declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Helpers;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceMetadataFactory;
use Dbp\Relay\CoreBundle\Exception\ApiError;
use Doctrine\Common\Annotations\AnnotationReader;
class ApiPlatformHelperFunctions
{
/**
* Returns the 'shortName' attribute of the ApiResource annotation of the entity with the given class name.
*
* @throws ApiError if the ApiResource annotation of $resourceClass is not found or doesn't have a non-empty 'shortName' attribute
*/
public static function getShortNameForResource(string $resourceClass): string
{
$resourceMetadataFactory = new AnnotationResourceMetadataFactory(new AnnotationReader());
try {
$resourceMetadata = $resourceMetadataFactory->create($resourceClass);
} catch (ResourceClassNotFoundException $exc) {
throw new ApiError(500, $exc->getMessage());
}
$uniqueName = $resourceMetadata->getShortName() ?? '';
if (Tools::isNullOrEmpty($uniqueName)) {
throw new ApiError(500, sprintf("'shortName' attribute missing in ApiResource annotation of resource class '%s'", $resourceClass));
} 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));
}
return $uniqueName;
}
}
...@@ -4,11 +4,9 @@ declare(strict_types=1); ...@@ -4,11 +4,9 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\LocalData; namespace Dbp\Relay\CoreBundle\LocalData;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceMetadataFactory;
use Dbp\Relay\CoreBundle\Exception\ApiError; use Dbp\Relay\CoreBundle\Exception\ApiError;
use Dbp\Relay\CoreBundle\Helpers\ApiPlatformHelperFunctions;
use Dbp\Relay\CoreBundle\Helpers\Tools; use Dbp\Relay\CoreBundle\Helpers\Tools;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\EventDispatcher\Event; use Symfony\Contracts\EventDispatcher\Event;
...@@ -111,21 +109,7 @@ class LocalDataEventDispatcher ...@@ -111,21 +109,7 @@ class LocalDataEventDispatcher
*/ */
public static function getUniqueEntityName(string $resourceClass): string public static function getUniqueEntityName(string $resourceClass): string
{ {
$resourceMetadataFactory = new AnnotationResourceMetadataFactory(new AnnotationReader()); return ApiPlatformHelperFunctions::getShortNameForResource($resourceClass);
try {
$resourceMetadata = $resourceMetadataFactory->create($resourceClass);
} catch (ResourceClassNotFoundException $exc) {
throw new ApiError(500, $exc->getMessage());
}
$uniqueName = $resourceMetadata->getShortName() ?? '';
if (Tools::isNullOrEmpty($uniqueName)) {
throw new ApiError(500, sprintf("'shortName' attribute missing in ApiResource annotation of resource class '%s'", $resourceClass));
} 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));
}
return $uniqueName;
} }
/** /**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment