8000 [Security] Add strategy resolvers by fancyweb · Pull Request #21178 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Security] Add strategy resolvers #21178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[Security] Add strategy resolvers
  • Loading branch information
fancyweb committed Jan 6, 2017
commit 3eafdf2b4ca61e885bf4fce364a4e2fb1ea8eaf5
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class AddStrategyResolversPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('security.access.decision_manager')) {
return;
}

$strategyResolvers = new \SplPriorityQueue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a new feature you're PR should be based on master, you will then be able to use the trait, instead of relying on this as it does not preserve the original order in which the services have been registered (ref #20995).

Copy link
Contributor Author
@fancyweb fancyweb Jan 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry I didn't know new feature have to be on master, I only did patch PR until now

I rebased my branch and realized there is still some work that needs to be done because of the TraceableAccessDecisionManager (the fact that the strategy is not unique now).

Unrelated but about your comment on the PriorityTaggedServiceTrait trait : I actually almost copy / pasted the AddSecurityVotersPass when I did mine. I just checked and this one is not using the trait... Should it be done in a separate PR as there might be problems of order too ?

foreach ($container->findTaggedServiceIds('security.strategy_resolver') as $id => $attributes) {
$class = $container->getDefinition($id)->getClass();
$interface = 'Symfony\Component\Security\Core\Authorization\StrategyResolverInterface';
if (!is_subclass_of($class, $interface)) {
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}

$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$strategyResolvers->insert(new Reference($id), $priority);
}

$strategyResolvers = iterator_to_array($strategyResolvers);
ksort($strategyResolvers);

$container->getDefinition('security.access.decision_manager')->replaceArgument(4, array_values($strategyResolvers));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public function load(array $configs, ContainerBuilder $container)
->addArgument($config['access_decision_manager']['strategy'])
->addArgument($config['access_decision_manager']['allow_if_all_abstain'])
->addArgument($config['access_decision_manager']['allow_if_equal_granted_denied'])
->addArgument(array());
;
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/SecurityBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddStrategyResolversPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
Expand Down Expand Up @@ -57,5 +58,6 @@ public function build(ContainerBuilder $container)
$extension->addUserProviderFactory(new InMemoryFactory());
$extension->addUserProviderFactory(new LdapFactory());
$container->addCompilerPass(new AddSecurityVotersPass());
$container->addCompilerPass(new AddStrategyResolversPass());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,35 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
const STRATEGY_UNANIMOUS = 'unanimous';

private $voters;
private $strategy;
private $defaultStrategyMethod;
private $allowIfAllAbstainDecisions;
private $allowIfEqualGrantedDeniedDecisions;

/**
* @var array
*/
private $strategyResolvers;

/**
* Constructor.
*
* @param VoterInterface[] $voters An array of VoterInterface instances
* @param string $strategy The vote strategy
* @param bool $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not
* @param bool $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals
* @param VoterInterface[] $voters An array of VoterInterface instances
* @param string $defaultStrategy The vote default strategy
* @param bool $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not
* @param bool $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals
* @param StrategyResolverInterface[] $strategyResolvers An array of StrategyResolver instances
*
* @throws \InvalidArgumentException
*/
public function __construct(array $voters = array(), $strategy = self::STRATEGY_AFFIRMATIVE, $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true)
public function __construct(array $voters = array(), $defaultStrategy = self::STRATEGY_AFFIRMATIVE, $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true, array $strategyResolvers = array())
{
$strategyMethod = 'decide'.ucfirst($strategy);
if (!is_callable(array($this, $strategyMethod))) {
throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy));
$defaultStrategyMethod = $this->getStrategyMethod($defaultStrategy);
if (!is_callable(array($this, $defaultStrategyMethod))) {
throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $defaultStrategy));
}

$this->voters = $voters;
$this->strategy = $strategyMethod;
$this->defaultStrategyMethod = $defaultStrategyMethod;
$this->allowIfAllAbstainDecisions = (bool) $allowIfAllAbstainDecisions;
$this->allowIfEqualGrantedDeniedDecisions = (bool) $allowIfEqualGrantedDeniedDecisions;
}
Expand All @@ -69,7 +75,27 @@ public function setVoters(array $voters)
*/
public function decide(TokenInterface $token, array $attributes, $object = null)
{
return $this->{$this->strategy}($token, $attributes, $object);
$strategyMethod = $this->defaultStrategyMethod;
/* @var $strategyResolver StrategyResolverInterface */
foreach ($this->strategyResolvers as $strategyResolver) {
if ($strategyResolver->supports($token, $attributes, $object)) {
$resolvedStrategy = $strategyResolver->getStrategy($token, $attributes, $object);
if (!is_string($resolvedStrategy)) {
continue;
}

$resolvedStrategyMethod = $this->getStrategyMethod($resolvedStrategy);
if (!is_callable(array($this, $resolvedStrategyMethod))) {
continue;
}

$strategyMethod = $resolvedStrategyMethod;

break;
}
}

return $this->{$strategyMethod}($token, $attributes, $object);
}

/**
Expand Down Expand Up @@ -188,4 +214,14 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje

return $this->allowIfAllAbstainDecisions;
}

/**
* @param string $strategy
*
* @return string
*/
private function getStrategyMethod($strategy)
{
return 'decide' . ucfirst($strategy);
}
}
< 6456 td class="blob-code blob-code-addition js-file-line"> /**
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Security\Core\Authorization;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

interface StrategyResolverInterface
{
* This method must return one of the following constants from the AccessDecisionManager :
* STRATEGY_AFFIRMATIVE, STRATEGY_CONSENSUS, or STRATEGY_UNANIMOUS.
*
* @param TokenInterface $token
* @param array $attributes
* @param mixed $object
*
* @return string
*/
public function getStrategy(TokenInterface $token, array $attributes, $object = null);

/**
* @param TokenInterface $token
* @param array $attributes
* @param null $object
*
* @return bool
*/
public function supports(TokenInterface $token, array $attributes, $object = null);
}
0