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

endless loop detection for authz expressions

parent 0a2e9aef
No related branches found
No related tags found
No related merge requests found
Pipeline #213136 passed
...@@ -129,10 +129,14 @@ class AuthorizationDataMuxer ...@@ -129,10 +129,14 @@ class AuthorizationDataMuxer
$event = new GetAttributeEvent($this, $attributeName, $value, $userIdentifier); $event = new GetAttributeEvent($this, $attributeName, $value, $userIdentifier);
$event->setAttributeValue($value); $event->setAttributeValue($value);
// Avoid endless recursions by only emitting an event for each attribute only once // Prevent endless recursions by only emitting an event for each attribute only once
if (!in_array($attributeName, $this->attributeStack, true)) { if (in_array($attributeName, $this->attributeStack, true)) {
array_push($this->attributeStack, $attributeName); throw new AuthorizationException(sprintf('infinite loop caused by a %s subscriber. authorization attribute: %s', GetAttributeEvent::class, $attributeName), AuthorizationException::INFINITE_EVENT_LOOP_DETECTED);
}
array_push($this->attributeStack, $attributeName);
try {
$this->eventDispatcher->dispatch($event); $this->eventDispatcher->dispatch($event);
} finally {
array_pop($this->attributeStack); array_pop($this->attributeStack);
} }
......
...@@ -6,7 +6,8 @@ namespace Dbp\Relay\CoreBundle\Authorization; ...@@ -6,7 +6,8 @@ namespace Dbp\Relay\CoreBundle\Authorization;
class AuthorizationException extends \RuntimeException class AuthorizationException extends \RuntimeException
{ {
public const PRIVILEGE_UNDEFINED = 2; public const PRIVILEGE_UNDEFINED = 1;
public const ATTRIBUTE_UNDEFINED = 3; public const ATTRIBUTE_UNDEFINED = 2;
public const INFINITE_LOOP_DETECTED = 4; public const INFINITE_EXRPESSION_LOOP_DETECTED = 3;
public const INFINITE_EVENT_LOOP_DETECTED = 4;
} }
...@@ -31,6 +31,12 @@ class AuthorizationExpressionChecker ...@@ -31,6 +31,12 @@ class AuthorizationExpressionChecker
/** @var AuthorizationDataMuxer */ /** @var AuthorizationDataMuxer */
private $dataMux; private $dataMux;
/** @var array */
private $rightExpressionStack;
/** @var array */
private $attributeExpressionStack;
public function __construct(AuthorizationDataMuxer $dataMux) public function __construct(AuthorizationDataMuxer $dataMux)
{ {
$this->expressionLanguage = new ExpressionLanguage(); $this->expressionLanguage = new ExpressionLanguage();
...@@ -38,6 +44,8 @@ class AuthorizationExpressionChecker ...@@ -38,6 +44,8 @@ class AuthorizationExpressionChecker
$this->rightExpressions = []; $this->rightExpressions = [];
$this->attributeExpressions = []; $this->attributeExpressions = [];
$this->dataMux = $dataMux; $this->dataMux = $dataMux;
$this->rightExpressionStack = [];
$this->attributeExpressionStack = [];
} }
public function setConfig(array $config) public function setConfig(array $config)
...@@ -58,17 +66,24 @@ class AuthorizationExpressionChecker ...@@ -58,17 +66,24 @@ class AuthorizationExpressionChecker
*/ */
public function evalAttributeExpression(AuthorizationUser $currentAuthorizationUser, string $expressionName, $defaultValue = null) public function evalAttributeExpression(AuthorizationUser $currentAuthorizationUser, string $expressionName, $defaultValue = null)
{ {
$this->tryIncreaseRecursionCounter($expressionName); if (in_array($expressionName, $this->attributeExpressionStack, true)) {
throw new AuthorizationException(sprintf('infinite loop caused by authorization attribute expression %s detected', $expressionName), AuthorizationException::INFINITE_EXRPESSION_LOOP_DETECTED);
if (($expression = $this->attributeExpressions[$expressionName] ?? null) !== null) { }
$result = $this->expressionLanguage->evaluate($expression, [ array_push($this->attributeExpressionStack, $expressionName);
'user' => $currentAuthorizationUser,
]); try {
} else { if (($expression = $this->attributeExpressions[$expressionName] ?? null) !== null) {
throw new AuthorizationException(sprintf('expression \'%s\' undefined', $expressionName), AuthorizationException::ATTRIBUTE_UNDEFINED); $result = $this->expressionLanguage->evaluate($expression, [
'user' => $currentAuthorizationUser,
]);
} else {
throw new AuthorizationException(sprintf('expression \'%s\' undefined', $expressionName), AuthorizationException::ATTRIBUTE_UNDEFINED);
}
return $result ?? $defaultValue;
} finally {
array_pop($this->attributeExpressionStack);
} }
return $result ?? $defaultValue;
} }
/** /**
...@@ -91,17 +106,24 @@ class AuthorizationExpressionChecker ...@@ -91,17 +106,24 @@ class AuthorizationExpressionChecker
*/ */
public function isGranted(AuthorizationUser $currentAuthorizationUser, string $rightName, $subject): bool public function isGranted(AuthorizationUser $currentAuthorizationUser, string $rightName, $subject): bool
{ {
$this->tryIncreaseRecursionCounter($rightName); if (in_array($rightName, $this->rightExpressionStack, true)) {
throw new AuthorizationException(sprintf('infinite loop caused by authorization right expression %s detected', $rightName), AuthorizationException::INFINITE_EXRPESSION_LOOP_DETECTED);
$rightExpression = $this->rightExpressions[$rightName] ?? null;
if ($rightExpression === null) {
throw new AuthorizationException(sprintf('right \'%s\' undefined', $rightName), AuthorizationException::PRIVILEGE_UNDEFINED);
} }
array_push($this->rightExpressionStack, $rightName);
try {
$rightExpression = $this->rightExpressions[$rightName] ?? null;
if ($rightExpression === null) {
throw new AuthorizationException(sprintf('right \'%s\' undefined', $rightName), AuthorizationException::PRIVILEGE_UNDEFINED);
}
return $this->expressionLanguage->evaluate($rightExpression, [ return $this->expressionLanguage->evaluate($rightExpression, [
'user' => $currentAuthorizationUser, 'user' => $currentAuthorizationUser,
'subject' => $subject, 'subject' => $subject,
]); ]);
} finally {
array_pop($this->rightExpressionStack);
}
} }
private function loadExpressions(array $expressions, array &$target): void private function loadExpressions(array $expressions, array &$target): void
...@@ -110,14 +132,4 @@ class AuthorizationExpressionChecker ...@@ -110,14 +132,4 @@ class AuthorizationExpressionChecker
$target[$name] = $expression; $target[$name] = $expression;
} }
} }
/**
* @throws AuthorizationException
*/
private function tryIncreaseRecursionCounter(string $hint): void
{
if (++$this->callCounter > self::MAX_NUM_CALLS) {
throw new AuthorizationException(sprintf('possible infinite loop in authorization expression detected (%s)', $hint), AuthorizationException::INFINITE_LOOP_DETECTED);
}
}
} }
...@@ -10,8 +10,8 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; ...@@ -10,8 +10,8 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterface abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterface
{ {
/** @var GetAttributeEvent|null */ /** @var array */
private $event; private $eventStack;
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
...@@ -21,6 +21,11 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac ...@@ -21,6 +21,11 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac
]; ];
} }
public function __construct()
{
$this->eventStack = [];
}
public function onGetAvailableAttributes(GetAvailableAttributesEvent $event) public function onGetAvailableAttributes(GetAvailableAttributesEvent $event)
{ {
$event->addAttributes($this->getNewAttributes()); $event->addAttributes($this->getNewAttributes());
...@@ -29,7 +34,7 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac ...@@ -29,7 +34,7 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac
public function onGetAttributeEvent(GetAttributeEvent $event) public function onGetAttributeEvent(GetAttributeEvent $event)
{ {
try { try {
$this->event = $event; array_push($this->eventStack, $event);
$attributeName = $event->getAttributeName(); $attributeName = $event->getAttributeName();
$event->setAttributeValue(in_array($attributeName, $this->getNewAttributes(), true) ? $event->setAttributeValue(in_array($attributeName, $this->getNewAttributes(), true) ?
...@@ -37,7 +42,7 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac ...@@ -37,7 +42,7 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac
$this->updateExistingAttributeValue($event->getUserIdentifier(), $attributeName, $event->getAttributeValue()) $this->updateExistingAttributeValue($event->getUserIdentifier(), $attributeName, $event->getAttributeValue())
); );
} finally { } finally {
$this->event = null; array_pop($this->eventStack);
} }
} }
...@@ -48,7 +53,7 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac ...@@ -48,7 +53,7 @@ abstract class AbstractGetAttributeSubscriber implements EventSubscriberInterfac
*/ */
public function getAttribute(string $attributeName, $defaultValue = null) public function getAttribute(string $attributeName, $defaultValue = null)
{ {
return $this->event->getAttribute($attributeName, $defaultValue); return $this->eventStack[array_key_last($this->eventStack)]->getAttribute($attributeName, $defaultValue);
} }
/** /**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment