Permalink
Browse files

feature #27305 [Security/Core] Add "is_granted()" to security express…

…ions, deprecate "has_role()" (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Security/Core] Add "is_granted()" to security expressions, deprecate "has_role()"

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #23084
| License       | MIT
| Doc PR        | -

Because `has_role()` doesn't use the auth checker, it is confusing. Let's move `is_granted()` to core (it's provided by SensioFrameworkExtraBundle / ApiPlatform for now.

Commits
-------

9dbf399 [Security/Core] Add "is_granted()" to security expressions, deprecate "has_role()"
  • Loading branch information...
fabpot committed May 21, 2018
2 parents 2ae7ad9 + 9dbf399 commit ec6d46c3604f8554c185383b28670777d831dde7
@@ -0,0 +1,7 @@
UPGRADE FROM 4.1 to 4.2
=======================
Security
--------
* Using the `has_role()` function in security expressions is deprecated, use the `is_granted()` function instead.
@@ -370,7 +370,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
->scalarNode('guard')
->cannotBeEmpty()
->info('An expression to block the transition')
->example('is_fully_authenticated() and has_role(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'')
->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'')
->end()
->arrayNode('from')
->beforeNormalization()
@@ -119,6 +119,7 @@
<service id="security.access.expression_voter" class="Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter">
<argument type="service" id="security.expression_language" />
<argument type="service" id="security.authentication.trust_resolver" />
<argument type="service" id="security.authorization_checker" />
<argument type="service" id="security.role_hierarchy" on-invalid="null" />
<tag name="security.voter" priority="245" />
</service>
@@ -40,5 +40,5 @@ security:
- { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/highly_protected_resource$, roles: IS_ADMIN }
- { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or has_role('ROLE_USER')" }
- { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or is_granted('ROLE_USER')" }
- { path: .*, roles: IS_AUTHENTICATED_FULLY }
@@ -18,7 +18,7 @@
"require": {
"php": "^7.1.3",
"ext-xml": "*",
"symfony/security": "~4.1",
"symfony/security": "~4.2",
"symfony/dependency-injection": "^3.4.3|^4.0.3",
"symfony/http-kernel": "^4.1"
},
@@ -1,6 +1,12 @@
CHANGELOG
=========
4.2.0
-----
* added the `is_granted()` function in security expressions
* deprecated the `has_role()` function in security expressions, use `is_granted()` instead
4.1.0
-----
@@ -42,15 +42,25 @@ public function getFunctions()
return $variables['trust_resolver']->isFullFledged($variables['token']);
}),
new ExpressionFunction('is_granted', function ($attributes, $object = 'null') {
return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object);
}, function (array $variables, $attributes, $object = null) {
return $variables['auth_checker']->isGranted($attributes, $object);
}),
new ExpressionFunction('is_remember_me', function () {
return '$trust_resolver->isRememberMe($token)';
}, function (array $variables) {
return $variables['trust_resolver']->isRememberMe($variables['token']);
}),
new ExpressionFunction('has_role', function ($role) {
@trigger_error('Using the "has_role()" function in security expressions is deprecated since Symfony 4.2, use "is_granted()" instead.', E_USER_DEPRECATED);
return sprintf('in_array(%s, $roles)', $role);
}, function (array $variables, $role) {
@trigger_error('Using the "has_role()" function in security expressions is deprecated since Symfony 4.2, use "is_granted()" instead.', E_USER_DEPRECATED);
return in_array($role, $variables['roles']);
}),
);
@@ -13,6 +13,7 @@
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
@@ -28,12 +29,27 @@ class ExpressionVoter implements VoterInterface
{
private $expressionLanguage;
private $trustResolver;
private $authChecker;
private $roleHierarchy;
public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null)
/**
* @param AuthorizationCheckerInterface $authChecker
*/
public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, $authChecker = null, RoleHierarchyInterface $roleHierarchy = null)
{
if ($authChecker instanceof RoleHierarchyInterface) {
@trigger_error(sprintf('Passing a RoleHierarchyInterface to "%s()" is deprecated since Symfony 4.2. Pass an AuthorizationCheckerInterface instead.', __METHOD__), E_USER_DEPRECATED);
$roleHierarchy = $authChecker;
$authChecker = null;
} elseif (null === $authChecker) {
@trigger_error(sprintf('Argument 3 passed to "%s()" should be an instanceof AuthorizationCheckerInterface, not passing it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
} elseif (!$authChecker instanceof AuthorizationCheckerInterface) {
throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be an instance of %s or null, %s given.', __METHOD__, AuthorizationCheckerInterface::class, is_object($authChecker) ? get_class($authChecker) : gettype($authChecker)));
}
$this->expressionLanguage = $expressionLanguage;
$this->trustResolver = $trustResolver;
$this->authChecker = $authChecker;
$this->roleHierarchy = $roleHierarchy;
}
@@ -87,6 +103,7 @@ private function getVariables(TokenInterface $token, $subject)
'subject' => $subject,
'roles' => array_map(function ($role) { return $role->getRole(); }, $roles),
'trust_resolver' => $this->trustResolver,
'auth_checker' => $this->authChecker,
);
// this is mainly to propose a better experience when the expression is used
@@ -12,10 +12,15 @@
namespace Symfony\Component\Security\Core\Tests\Authorization;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\User\User;
@@ -24,17 +29,21 @@ class ExpressionLanguageTest extends TestCase
/**
* @dataProvider provider
*/
public function testIsAuthenticated($token, $expression, $result, array $roles = array())
public function testIsAuthenticated($token, $expression, $result)
{
$anonymousTokenClass = 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken';
$rememberMeTokenClass = 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\RememberMeToken';
$expressionLanguage = new ExpressionLanguage();
$trustResolver = new AuthenticationTrustResolver($anonymousTokenClass, $rememberMeTokenClass);
$tokenStorage = new TokenStorage();
$tokenStorage->setToken($token);
$accessDecisionManager = new AccessDecisionManager(array(new RoleVoter()));
$authChecker = new AuthorizationChecker($tokenStorage, $this->getMockBuilder(AuthenticationManagerInterface::class)->getMock(), $accessDecisionManager);
$context = array();
$context['trust_resolver'] = $trustResolver;
$context['auth_checker'] = $authChecker;
$context['token'] = $token;
$context['roles'] = $roles;
$this->assertEquals($result, $expressionLanguage->evaluate($expression, $context));
}
@@ -54,27 +63,52 @@ public function provider()
array($noToken, 'is_authenticated()', false),
array($noToken, 'is_fully_authenticated()', false),
array($noToken, 'is_remember_me()', false),
array($noToken, "has_role('ROLE_USER')", false),
array($anonymousToken, 'is_anonymous()', true),
array($anonymousToken, 'is_authenticated()', false),
array($anonymousToken, 'is_fully_authenticated()', false),
array($anonymousToken, 'is_remember_me()', false),
array($anonymousToken, "has_role('ROLE_USER')", false),
array($anonymousToken, "is_granted('ROLE_USER')", false),
array($rememberMeToken, 'is_anonymous()', false),
array($rememberMeToken, 'is_authenticated()', true),
array($rememberMeToken, 'is_fully_authenticated()', false),
array($rememberMeToken, 'is_remember_me()', true),
array($rememberMeToken, "has_role('ROLE_FOO')", false, $roles),
array($rememberMeToken, "has_role('ROLE_USER')", true, $roles),
array($rememberMeToken, "is_granted('ROLE_FOO')", false),
array($rememberMeToken, "is_granted('ROLE_USER')", true),
array($usernamePasswordToken, 'is_anonymous()', false),
array($usernamePasswordToken, 'is_authenticated()', true),
array($usernamePasswordToken, 'is_fully_authenticated()', true),
array($usernamePasswordToken, 'is_remember_me()', false),
array($usernamePasswordToken, "has_role('ROLE_FOO')", false, $roles),
array($usernamePasswordToken, "has_role('ROLE_USER')", true, $roles),
array($usernamePasswordToken, "is_granted('ROLE_FOO')", false),
array($usernamePasswordToken, "is_granted('ROLE_USER')", true),
);
}
/**
* @dataProvider provideLegacyHasRole
* @group legacy
*/
public function testLegacyHasRole($expression, $result, $roles = array())
{
$expressionLanguage = new ExpressionLanguage();
$context = array('roles' => $roles);
$this->assertEquals($result, $expressionLanguage->evaluate($expression, $context));
}
public function provideLegacyHasRole()
{
$roles = array('ROLE_USER', 'ROLE_ADMIN');
return array(
array("has_role('ROLE_FOO')", false),
array("has_role('ROLE_USER')", false),
array("has_role('ROLE_ADMIN')", false),
array("has_role('ROLE_FOO')", false, $roles),
array("has_role('ROLE_USER')", true, $roles),
array("has_role('ROLE_ADMIN')", true, $roles),
);
}
}
@@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Core\Tests\Authorization\Voter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Role\Role;
@@ -23,7 +24,7 @@ class ExpressionVoterTest extends TestCase
*/
public function testVote($roles, $attributes, $expected, $tokenExpectsGetRoles = true, $expressionLanguageExpectsEvaluate = true)
{
$voter = new ExpressionVoter($this->createExpressionLanguage($expressionLanguageExpectsEvaluate), $this->createTrustResolver());
$voter = new ExpressionVoter($this->createExpressionLanguage($expressionLanguageExpectsEvaluate), $this->createTrustResolver(), $this->createAuthorizationChecker());
$this->assertSame($expected, $voter->vote($this->getToken($roles, $tokenExpectsGetRoles), null, $attributes));
}
@@ -75,9 +76,9 @@ protected function createTrustResolver()
return $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface')->getMock();
}
protected function createRoleHierarchy()
protected function createAuthorizationChecker()
{
return $this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleHierarchyInterface')->getMock();
return $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock();
}
protected function createExpression()

0 comments on commit ec6d46c

Please sign in to comment.