Skip to content
Snippets Groups Projects
Commit 1de0ce92 authored by Reiter, Christoph's avatar Reiter, Christoph :snake:
Browse files

AuthorizationDataMuxer: add two events for mutating attributes

One event for changing the global attribute list and one for changing
the result for existing attributes and/or introducing new attributes.
parent 51966acf
No related branches found
No related tags found
No related merge requests found
...@@ -16,19 +16,32 @@ abstract class AbstractAuthorizationService ...@@ -16,19 +16,32 @@ abstract class AbstractAuthorizationService
/** @var AuthorizationExpressionChecker */ /** @var AuthorizationExpressionChecker */
private $userAuthorizationChecker; private $userAuthorizationChecker;
/** @var AuthorizationUser|null */ /** @var AuthorizationUser */
private $currentAuthorizationUser; private $currentAuthorizationUser;
public function __construct(UserSessionInterface $userSession, AuthorizationDataProviderProvider $authorizationDataProviderProvider) private $config;
/**
* @required
*/
public function _injectServices(UserSessionInterface $userSession, AuthorizationDataMuxer $mux)
{ {
$muxer = new AuthorizationDataMuxer($authorizationDataProviderProvider->getAuthorizationDataProviders()); $this->userAuthorizationChecker = new AuthorizationExpressionChecker($mux);
$this->userAuthorizationChecker = new AuthorizationExpressionChecker($muxer);
$this->currentAuthorizationUser = new AuthorizationUser($userSession->getUserIdentifier(), $this->userAuthorizationChecker); $this->currentAuthorizationUser = new AuthorizationUser($userSession->getUserIdentifier(), $this->userAuthorizationChecker);
$this->updateConfig();
} }
public function setConfig(array $config) public function setConfig(array $config)
{ {
$this->userAuthorizationChecker->setConfig($config); $this->config = $config;
$this->updateConfig();
}
private function updateConfig()
{
if ($this->userAuthorizationChecker !== null && $this->config !== null) {
$this->userAuthorizationChecker->setConfig($this->config);
}
} }
/** /**
......
...@@ -4,32 +4,43 @@ declare(strict_types=1); ...@@ -4,32 +4,43 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Authorization; namespace Dbp\Relay\CoreBundle\Authorization;
use Dbp\Relay\CoreBundle\Authorization\Event\GetAttributeEvent;
use Dbp\Relay\CoreBundle\Authorization\Event\GetAvailableAttributesEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @internal
*/
class AuthorizationDataMuxer class AuthorizationDataMuxer
{ {
/** @var iterable */ /** @var iterable<AuthorizationDataProviderInterface> */
private $authorizationDataProviders; private $authorizationDataProviders;
/** @var array */ /** @var array<string, array> */
private $attributes; private $providerCache;
/** @var array<string, string[]> */
private $availableCache;
/** @var EventDispatcherInterface */
private $eventDispatcher;
/** @var string[] */
private $attributeStack;
/** /**
* @param iterable<AuthorizationDataProviderInterface> $authorizationDataProviders * @var ?string[]
*/ */
public function __construct(iterable $authorizationDataProviders) private $availableCacheAll;
{
$this->authorizationDataProviders = $authorizationDataProviders;
$this->attributes = [];
}
private function loadUserAttributesFromAuthorizationProvider(?string $userIdentifier, AuthorizationDataProviderInterface $authorizationDataProvider): void public function __construct(AuthorizationDataProviderProvider $authorizationDataProviderProvider, EventDispatcherInterface $eventDispatcher)
{ {
$userAttributes = $authorizationDataProvider->getUserAttributes($userIdentifier); $this->authorizationDataProviders = $authorizationDataProviderProvider->getAuthorizationDataProviders();
$this->eventDispatcher = $eventDispatcher;
foreach ($authorizationDataProvider->getAvailableAttributes() as $availableAttribute) { $this->providerCache = [];
if (array_key_exists($availableAttribute, $userAttributes)) { $this->availableCache = [];
$this->attributes[$availableAttribute] = $userAttributes[$availableAttribute]; $this->availableCacheAll = null;
} $this->attributeStack = [];
}
} }
/** /**
...@@ -39,48 +50,97 @@ class AuthorizationDataMuxer ...@@ -39,48 +50,97 @@ class AuthorizationDataMuxer
*/ */
public function getAvailableAttributes(): array public function getAvailableAttributes(): array
{ {
if ($this->availableCacheAll === null) {
$res = []; $res = [];
foreach ($this->authorizationDataProviders as $authorizationDataProvider) { foreach ($this->authorizationDataProviders as $authorizationDataProvider) {
$availableAttributes = $authorizationDataProvider->getAvailableAttributes(); $availableAttributes = $this->getProviderAvailableAttributes($authorizationDataProvider);
$res = array_merge($res, $availableAttributes); $res = array_merge($res, $availableAttributes);
} }
return $res; $event = new GetAvailableAttributesEvent($res);
$this->eventDispatcher->dispatch($event);
$this->availableCacheAll = $event->getAttributes();
}
return $this->availableCacheAll;
} }
/** /**
* @param mixed|null $defaultValue * Returns a cached list for available attributes for the provider.
* *
* @return mixed|null * @return string[]
*/
private function getProviderAvailableAttributes(AuthorizationDataProviderInterface $prov): array
{
// Caches getAvailableAttributes for each provider
$provKey = get_class($prov);
if (!array_key_exists($provKey, $this->availableCache)) {
$this->availableCache[$provKey] = $prov->getAvailableAttributes();
}
return $this->availableCache[$provKey];
}
/**
* Returns a cached map of available user attributes.
* *
* @throws AuthorizationException * @return array<string, mixed>
*/ */
public function getAttribute(?string $userIdentifier, string $attributeName, $defaultValue = null) private function getProviderUserAttributes(AuthorizationDataProviderInterface $prov, ?string $userIdentifier): array
{ {
if (array_key_exists($attributeName, $this->attributes) === false) { // We cache the attributes for each provider, but only for the last userIdentifier
$this->loadAttribute($userIdentifier, $attributeName); $provKey = get_class($prov);
if (!array_key_exists($provKey, $this->providerCache) || $this->providerCache[$provKey][0] !== $userIdentifier) {
$this->providerCache[$provKey] = [$userIdentifier, $prov->getUserAttributes($userIdentifier)];
} }
$res = $this->providerCache[$provKey];
assert($res[0] === $userIdentifier);
return $this->attributes[$attributeName] ?? $defaultValue; return $res[1];
} }
/** /**
* @param mixed $defaultValue
*
* @return mixed
*
* @throws AuthorizationException * @throws AuthorizationException
*/ */
private function loadAttribute(?string $userIdentifier, string $attributeName): void public function getAttribute(?string $userIdentifier, string $attributeName, $defaultValue = null)
{ {
if (!in_array($attributeName, $this->getAvailableAttributes(), true)) {
throw new AuthorizationException(sprintf('attribute \'%s\' undefined', $attributeName), AuthorizationException::ATTRIBUTE_UNDEFINED);
}
$wasFound = false; $wasFound = false;
$value = null;
foreach ($this->authorizationDataProviders as $authorizationDataProvider) { foreach ($this->authorizationDataProviders as $authorizationDataProvider) {
$availableAttributes = $authorizationDataProvider->getAvailableAttributes(); $availableAttributes = $this->getProviderAvailableAttributes($authorizationDataProvider);
if (in_array($attributeName, $availableAttributes, true)) { if (!in_array($attributeName, $availableAttributes, true)) {
$this->loadUserAttributesFromAuthorizationProvider($userIdentifier, $authorizationDataProvider); continue;
}
$userAttributes = $this->getProviderUserAttributes($authorizationDataProvider, $userIdentifier);
if (!array_key_exists($attributeName, $userAttributes)) {
continue;
}
$value = $userAttributes[$attributeName];
$wasFound = true; $wasFound = true;
break; break;
} }
$event = new GetAttributeEvent($this, $attributeName, $userIdentifier);
if ($wasFound) {
$event->setValue($value);
} }
if ($wasFound === false) { // Avoid endless recursions by only emitting an event for each attribtue only once
throw new AuthorizationException(sprintf('custom attribute \'%s\' undefined', $attributeName), AuthorizationException::ATTRIBUTE_UNDEFINED); if (!in_array($attributeName, $this->attributeStack, true)) {
array_push($this->attributeStack, $attributeName);
$this->eventDispatcher->dispatch($event);
array_pop($this->attributeStack);
} }
return $event->getValue($defaultValue);
} }
} }
...@@ -6,6 +6,9 @@ namespace Dbp\Relay\CoreBundle\Authorization; ...@@ -6,6 +6,9 @@ namespace Dbp\Relay\CoreBundle\Authorization;
use Dbp\Relay\CoreBundle\Authorization\ExpressionLanguage\ExpressionLanguage; use Dbp\Relay\CoreBundle\Authorization\ExpressionLanguage\ExpressionLanguage;
/**
* @internal
*/
class AuthorizationExpressionChecker class AuthorizationExpressionChecker
{ {
public const RIGHTS_CONFIG_ATTRIBUTE = 'rights'; public const RIGHTS_CONFIG_ATTRIBUTE = 'rights';
......
...@@ -18,14 +18,14 @@ class DebugCommand extends Command implements LoggerAwareInterface ...@@ -18,14 +18,14 @@ class DebugCommand extends Command implements LoggerAwareInterface
protected static $defaultName = 'dbp:relay:core:auth-debug'; protected static $defaultName = 'dbp:relay:core:auth-debug';
/** /**
* @var AuthorizationDataProviderProvider * @var AuthorizationDataMuxer
*/ */
private $provider; private $mux;
public function __construct(AuthorizationDataProviderProvider $provider) public function __construct(AuthorizationDataMuxer $mux)
{ {
parent::__construct(); parent::__construct();
$this->provider = $provider; $this->mux = $mux;
} }
protected function configure() protected function configure()
...@@ -38,15 +38,12 @@ class DebugCommand extends Command implements LoggerAwareInterface ...@@ -38,15 +38,12 @@ class DebugCommand extends Command implements LoggerAwareInterface
{ {
$username = $input->getArgument('username'); $username = $input->getArgument('username');
// Fetch all attributes first (to get potential log spam first) $attrs = $this->mux->getAvailableAttributes();
$providers = $this->provider->getAuthorizationDataProviders();
$mux = new AuthorizationDataMuxer($providers);
$attrs = $mux->getAvailableAttributes();
$all = []; $all = [];
$default = new \stdClass(); $default = new \stdClass();
sort($attrs, SORT_STRING | SORT_FLAG_CASE); sort($attrs, SORT_STRING | SORT_FLAG_CASE);
foreach ($attrs as $attr) { foreach ($attrs as $attr) {
$all[$attr] = $mux->getAttribute($username, $attr, $default); $all[$attr] = $this->mux->getAttribute($username, $attr, $default);
} }
// Now print them out // Now print them out
......
<?php
declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Authorization\Event;
use Dbp\Relay\CoreBundle\Authorization\AuthorizationDataMuxer;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This hook can be used to change the value of attributes at the time they are requested.
*
* Can be used to change existing attribute values, and introduce new attributes.
* In case of new attributes you have to make sure to also handle GetAvailableAttributesEvent
* and register your new attribute there.
*/
class GetAttributeEvent extends Event
{
/**
* @var AuthorizationDataMuxer
*/
private $mux;
/**
* @var ?string
*/
private $userIdentifier;
/**
* @var string
*/
private $name;
/**
* @var mixed
*/
private $value;
/**
* @var bool
*/
private $hasValue;
public function __construct(AuthorizationDataMuxer $mux, string $name, ?string $userIdentifier)
{
$this->mux = $mux;
$this->userIdentifier = $userIdentifier;
$this->name = $name;
$this->hasValue = false;
}
/**
* @param mixed|null $defaultValue
*
* @return mixed|null
*/
public function getAttribute(string $attributeName, $defaultValue = null)
{
return $this->mux->getAttribute($this->userIdentifier, $attributeName, $defaultValue);
}
public function getAttributeName(): string
{
return $this->name;
}
/**
* @param mixed $value
*/
public function setValue($value): void
{
$this->value = $value;
$this->hasValue = true;
}
/**
* @param mixed $default
*
* @return mixed
*/
public function getValue($default = null)
{
if (!$this->hasValue) {
return $default;
}
return $this->value;
}
}
<?php
declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Authorization\Event;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This hook can be used to change the set of available attributes.
*
* You can extend the set, or remove attributes.
*/
class GetAvailableAttributesEvent extends Event
{
/**
* @var string[]
*/
private $attributes;
/**
* @param string[] $attributes
*/
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
/**
* @return string[]
*/
public function getAttributes(): array
{
return $this->attributes;
}
/**
* @param string[] $attributes
*/
public function setAttributes(array $attributes): void
{
$this->attributes = $attributes;
}
}
...@@ -4,9 +4,7 @@ declare(strict_types=1); ...@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\LocalData; namespace Dbp\Relay\CoreBundle\LocalData;
use Dbp\Relay\CoreBundle\API\UserSessionInterface;
use Dbp\Relay\CoreBundle\Authorization\AbstractAuthorizationService; use Dbp\Relay\CoreBundle\Authorization\AbstractAuthorizationService;
use Dbp\Relay\CoreBundle\Authorization\AuthorizationDataProviderProvider;
use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
...@@ -20,10 +18,8 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio ...@@ -20,10 +18,8 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio
/** @var string[] */ /** @var string[] */
private $attributeMapping; private $attributeMapping;
public function __construct(UserSessionInterface $userSession, AuthorizationDataProviderProvider $authorizationDataProviderProvider) public function __construct()
{ {
parent::__construct($userSession, $authorizationDataProviderProvider);
$this->attributeMapping = []; $this->attributeMapping = [];
} }
......
...@@ -93,6 +93,10 @@ services: ...@@ -93,6 +93,10 @@ services:
autoconfigure: true autoconfigure: true
arguments: [ !tagged auth.authorization_data_provider ] arguments: [ !tagged auth.authorization_data_provider ]
Dbp\Relay\CoreBundle\Authorization\AuthorizationDataMuxer:
autowire: true
autoconfigure: true
Dbp\Relay\CoreBundle\Helpers\Locale: Dbp\Relay\CoreBundle\Helpers\Locale:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment