Permalink
Browse files

added reverse interpreter for expressions

  • Loading branch information...
1 parent dc76f47 commit 16b57366029f5cb8bebfa61f9633d17573bae8dd @schmittjoh committed Aug 30, 2012
@@ -2,6 +2,8 @@
namespace JMS\SecurityExtraBundle\DependencyInjection\Compiler;
+use Symfony\Component\DependencyInjection\Reference;
+
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -18,5 +20,10 @@ public function process(ContainerBuilder $container)
$container->getDefinition('security.role_hierarchy')
->setPublic(true);
}
+
+ $container->setDefinition('security.access.decision_manager.delegate',
+ $container->getDefinition('security.access.decision_manager'));
+ $container->register('security.access.decision_manager', '%security.access.remembering_access_decision_manager.class%')
+ ->addArgument(new Reference('security.access.decision_manager.delegate'));
}
}
@@ -17,6 +17,8 @@
<parameter key="security.expressions.variable_compiler.class">JMS\SecurityExtraBundle\Security\Authorization\Expression\Compiler\ContainerAwareVariableCompiler</parameter>
<parameter key="security.expressions.parameter_compiler.class">JMS\SecurityExtraBundle\Security\Authorization\Expression\Compiler\ParameterExpressionCompiler</parameter>
+ <parameter key="security.expressions.reverse_interpreter.class">JMS\SecurityExtraBundle\Security\Authorization\Expression\ReverseInterpreter</parameter>
+
<parameter key="security.extra.config_driver.class">JMS\SecurityExtraBundle\Metadata\Driver\ConfigDriver</parameter>
<parameter key="security.extra.twig_extension.class">JMS\SecurityExtraBundle\Twig\SecurityExtension</parameter>
@@ -46,6 +48,11 @@
<tag name="security.expressions.variable" variable="role_hierarchy"
service="security.role_hierarchy" />
</service>
+
+ <service id="security.expressions.reverse_interpreter" class="%security.expressions.reverse_interpreter.class%">
+ <argument type="service" id="security.expressions.compiler" />
+ <argument type="service" id="security.expressions.handler" />
+ </service>
<service id="security.expressions.voter" class="%security.expressions.voter.class%" public="false">
<argument type="service" id="security.expressions.handler" />
@@ -9,6 +9,8 @@
<parameter key="security.access.method_interceptor.class">JMS\SecurityExtraBundle\Security\Authorization\Interception\MethodSecurityInterceptor</parameter>
<parameter key="security.access.method_access_control" type="collection" />
+
+ <parameter key="security.access.remembering_access_decision_manager.class">JMS\SecurityExtraBundle\Security\Authorization\RememberingAccessDecisionManager</parameter>
<parameter key="security.access.run_as_manager.class">JMS\SecurityExtraBundle\Security\Authorization\RunAsManager</parameter>
<parameter key="security.authentication.provider.run_as.class">JMS\SecurityExtraBundle\Security\Authentication\Provider\RunAsAuthenticationProvider</parameter>
@@ -31,14 +31,6 @@ public function getType()
public function compilePreconditions(ExpressionCompiler $compiler, ExpressionInterface $expr)
{
- if ('user' === $expr->name) {
- $compiler
- ->setAttribute('user_var_name', $name = $compiler->nextName())
- ->write("\$$name = ")
- ->compileInternal(new VariableExpression('token'))
- ->write("->getUser();\n\n")
- ;
- }
}
public function compile(ExpressionCompiler $compiler, ExpressionInterface $expr)
@@ -55,12 +47,6 @@ public function compile(ExpressionCompiler $compiler, ExpressionInterface $expr)
return;
}
- if ('user' === $expr->name) {
- $compiler->write("\${$compiler->attributes['user_var_name']}");
-
- return;
- }
-
if ($expr->allowNull) {
$compiler->write("(isset(\$context['{$expr->name}']) ? ");
}
@@ -41,6 +41,7 @@ public function createContext(TokenInterface $token, $object)
return array(
'container' => $this->container,
'token' => $token,
+ 'user' => $token->getUser(),
'object' => $object,
);
}
@@ -38,6 +38,7 @@ public function createContext(TokenInterface $token, $object)
{
$context = array(
'token' => $token,
+ 'user' => $token->getUser(),
'object' => $object,
'trust_resolver' => $this->trustResolver,
);
@@ -106,10 +106,10 @@ public function compile(ExpressionInterface $expr, $raw = null)
$this
->writeln('return function(array $context) {')
->indent()
- ->compilePreconditions($expr)
- ->write('return ')
- ->compileInternal($expr)
- ->writeln(';')
+ ->compilePreconditions($expr)
+ ->write('return ')
+ ->compileInternal($expr)
+ ->writeln(';')
->outdent()
->writeln('};')
;
@@ -256,7 +256,7 @@ public function nextName()
return $name;
}
}
-
+
public function compilePreconditions(ExpressionInterface $expr)
{
if ($typeCompiler = $this->findTypeCompiler(get_class($expr))) {
@@ -0,0 +1,83 @@
+<?php
+
+namespace JMS\SecurityExtraBundle\Security\Authorization\Expression;
+
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+
+/**
+ * Reverse interprets expressions to find the sub-expressions which caused
+ * access to be denied.
+ *
+ * In most cases, a single expression will be returned. The only exception
+ * are non-short circuiting expressions which might return multiple expressions.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ReverseInterpreter
+{
+ private $parser;
+ private $compiler;
+ private $handler;
+
+ public function __construct(ExpressionCompiler $compiler, ExpressionHandlerInterface $handler)
+ {
+ $this->compiler = $compiler;
+ $this->handler = $handler;
+ $this->parser = new ExpressionParser();
+ }
+
+ /**
+ * Returns the last access denying expression if available.
+ *
+ * @param TokenInterface $token
+ * @param array<*> $attributes
+ * @param object|null $object
+ *
+ * @return Ast\ExpressionInterface|null
+ */
+ public function getDenyingExpr(TokenInterface $token, array $attributes, $object = null)
+ {
+ if (count($attributes) !== 1 || ! $attributes[0] instanceof Expression) {
+ return null;
+ }
+
+ $context = $this->handler->createContext($token, $object);
+ $expr = $this->parser->parse($attributes[0]->expression);
+
+ return $this->getDenyingExprInternal($expr, $context);
+ }
+
+ private function getDenyingExprInternal(Ast\ExpressionInterface $expr, array $context)
+ {
+ switch (true) {
+ case $expr instanceof Ast\OrExpression:
+ if (null === $leftDenying = $this->getDenyingExprInternal($expr->left, $context)) {
+ return null;
+ }
+
+ if (null === $rightDenying = $this->getDenyingExprInternal($expr->right, $context)) {
+ return null;
+ }
+
+ return new Ast\OrExpression($leftDenying, $rightDenying);
+
+ case $expr instanceof Ast\AndExpression:
+ if (null !== $leftDenying = $this->getDenyingExprInternal($expr->left, $context)) {
+ return $leftDenying;
+ }
+
+ return $this->getDenyingExprInternal($expr->right, $context);
+
+ default:
+ return false === $this->evalExpr($expr, $context) ? $expr : null;
+ }
+ }
+
+ private function evalExpr(Ast\ExpressionInterface $expr, array $context)
+ {
+ $code = $this->compiler->compile($expr);
+ $evaluator = eval($code);
+
+ return $evaluator($context);
+ }
+}
@@ -0,0 +1,67 @@
+<?php
+
+namespace JMS\SecurityExtraBundle\Security\Authorization;
+
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
+
+/**
+ * An introspectable access decision manager.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class RememberingAccessDecisionManager implements AccessDecisionManagerInterface
+{
+ private $delegate;
+ private $lastDecisionCall;
+
+ public function __construct(AccessDecisionManagerInterface $delegate)
+ {
+ $this->delegate = $delegate;
+ }
+
+ public function getLastDecisionCall()
+ {
+ return $this->lastDecisionCall;
+ }
+
+ /**
+ * Decides whether the access is possible or not.
+ *
+ * @param TokenInterface $token A TokenInterface instance
+ * @param array $attributes An array of attributes associated with the method being invoked
+ * @param object $object The object to secure
+ *
+ * @return Boolean true if the access is granted, false otherwise
+ */
+ public function decide(TokenInterface $token, array $attributes, $object = null)
+ {
+ $this->lastDecisionCall = array($token, $attributes, $object);
+
+ return $this->delegate->decide($token, $attributes, $object);
+ }
+
+ /**
+ * Checks if the access decision manager supports the given attribute.
+ *
+ * @param string $attribute An attribute
+ *
+ * @return Boolean true if this decision manager supports the attribute, false otherwise
+ */
+ public function supportsAttribute($attribute)
+ {
+ return $this->delegate->supportsAttribute($attribute);
+ }
+
+ /**
+ * Checks if the access decision manager supports the given class.
+ *
+ * @param string $class A class name
+ *
+ * @return true if this decision manager can process the class
+ */
+ public function supportsClass($class)
+ {
+ return $this->delegate->supportsClass($class);
+ }
+}
@@ -14,7 +14,7 @@ public function testDisableAllVoters()
$adm = self::$kernel->getContainer()->get('security.access.decision_manager');
- $this->assertEquals(1, count($voters = $this->getField($adm, 'voters')));
+ $this->assertEquals(1, count($voters = $this->getField($this->getField($adm, 'delegate'), 'voters')));
$this->assertInstanceOf('JMS\SecurityExtraBundle\Security\Authorization\Expression\LazyLoadingExpressionVoter', $voters[0]);
}
@@ -28,7 +28,7 @@ public function testDefault()
$adm = self::$kernel->getContainer()->get('security.access.decision_manager');
- $this->assertEquals(2, count($voters = $this->getField($adm, 'voters')));
+ $this->assertEquals(2, count($voters = $this->getField($this->getField($adm, 'delegate'), 'voters')));
$this->assertInstanceOf('Symfony\Component\Security\Core\Authorization\Voter\RoleVoter', $voters[0]);
$this->assertInstanceOf('Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter', $voters[1]);
}
@@ -43,7 +43,7 @@ public function testSomeVotersDisabled()
$adm = self::$kernel->getContainer()->get('security.access.decision_manager');
- $this->assertEquals(1, count($voters = $this->getField($adm, 'voters')));
+ $this->assertEquals(1, count($voters = $this->getField($this->getField($adm, 'delegate'), 'voters')));
$this->assertInstanceOf('Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter', $voters[0]);
}
Oops, something went wrong.

0 comments on commit 16b5736

Please sign in to comment.