Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit38b7fde

Browse files
committed
added support for expression in control access rules
1 parent2777ac7 commit38b7fde

File tree

15 files changed

+229
-9
lines changed

15 files changed

+229
-9
lines changed

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode)
169169
->beforeNormalization()->ifString()->then(function($v) {returnpreg_split('/\s*,\s*/',$v); })->end()
170170
->prototype('scalar')->end()
171171
->end()
172+
->scalarNode('allow_if')->defaultNull()->end()
172173
->end()
173174
->fixXmlConfig('role')
174175
->children()

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
useSymfony\Component\DependencyInjection\Reference;
2424
useSymfony\Component\DependencyInjection\Parameter;
2525
useSymfony\Component\Config\FileLocator;
26+
useSymfony\Component\ExpressionLanguage\Expression;
2627

2728
/**
2829
* SecurityExtension.
@@ -33,6 +34,7 @@
3334
class SecurityExtensionextends Extension
3435
{
3536
private$requestMatchers =array();
37+
private$expressions =array();
3638
private$contextListeners =array();
3739
private$listenerPositions =array('pre_auth','form','http','remember_me');
3840
private$factories =array();
@@ -188,8 +190,13 @@ private function createAuthorization($config, ContainerBuilder $container)
188190
$access['ips']
189191
);
190192

193+
$attributes =$access['roles'];
194+
if ($access['allow_if']) {
195+
$attributes[] =$this->createExpression($container,$access['allow_if']);
196+
}
197+
191198
$container->getDefinition('security.access_map')
192-
->addMethodCall('add',array($matcher,$access['roles'],$access['requires_channel']));
199+
->addMethodCall('add',array($matcher,$attributes,$access['requires_channel']));
193200
}
194201
}
195202

@@ -596,6 +603,21 @@ private function createSwitchUserListener($container, $id, $config, $defaultProv
596603
return$switchUserListenerId;
597604
}
598605

606+
privatefunctioncreateExpression($container,$expression)
607+
{
608+
if (isset($this->expressions[$id ='security.expression.'.sha1($expression)])) {
609+
return$this->expressions[$id];
610+
}
611+
612+
$container
613+
->register($id,'Symfony\Component\ExpressionLanguage\Expression')
614+
->setPublic(false)
615+
->addArgument($expression)
616+
;
617+
618+
return$this->expressions[$id] =newReference($id);
619+
}
620+
599621
privatefunctioncreateRequestMatcher($container,$path =null,$host =null,$methods =array(),$ip =null,array$attributes =array())
600622
{
601623
$serialized =serialize(array($path,$host,$methods,$ip,$attributes));

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,21 @@
3232
<parameterkey="security.access.simple_role_voter.class">Symfony\Component\Security\Core\Authorization\Voter\RoleVoter</parameter>
3333
<parameterkey="security.access.authenticated_voter.class">Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter</parameter>
3434
<parameterkey="security.access.role_hierarchy_voter.class">Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter</parameter>
35+
<parameterkey="security.access.expression_voter.class">Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter</parameter>
3536

3637
<parameterkey="security.firewall.class">Symfony\Component\Security\Http\Firewall</parameter>
3738
<parameterkey="security.firewall.map.class">Symfony\Bundle\SecurityBundle\Security\FirewallMap</parameter>
3839
<parameterkey="security.firewall.context.class">Symfony\Bundle\SecurityBundle\Security\FirewallContext</parameter>
3940
<parameterkey="security.matcher.class">Symfony\Component\HttpFoundation\RequestMatcher</parameter>
41+
<parameterkey="security.expression_matcher.class">Symfony\Component\HttpFoundation\ExpressionRequestMatcher</parameter>
4042

4143
<parameterkey="security.role_hierarchy.class">Symfony\Component\Security\Core\Role\RoleHierarchy</parameter>
4244

4345
<parameterkey="security.http_utils.class">Symfony\Component\Security\Http\HttpUtils</parameter>
4446

4547
<parameterkey="security.validator.user_password.class">Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator</parameter>
48+
49+
<parameterkey="security.expression_language.class">Symfony\Component\Security\Core\Authorization\ExpressionLanguage</parameter>
4650
</parameters>
4751

4852
<services>
@@ -78,6 +82,7 @@
7882

7983
<serviceid="security.user_checker"class="%security.user_checker.class%"public="false" />
8084

85+
<serviceid="security.expression_language"class="%security.expression_language.class%"public="false" />
8186

8287
<!-- Authorization related services-->
8388
<serviceid="security.access.decision_manager"class="%security.access.decision_manager.class%"public="false">
@@ -104,6 +109,13 @@
104109
<tagname="security.voter"priority="245" />
105110
</service>
106111

112+
<serviceid="security.access.expression_voter"class="%security.access.expression_voter.class%"public="false">
113+
<argumenttype="service"id="security.expression_language" />
114+
<argumenttype="service"id="security.authentication.trust_resolver" />
115+
<argumenttype="service"id="security.role_hierarchy"on-invalid="null" />
116+
<tagname="security.voter"priority="245" />
117+
</service>
118+
107119

108120
<!-- Firewall related services-->
109121
<serviceid="security.firewall"class="%security.firewall.class%">

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
useSymfony\Bundle\SecurityBundle\SecurityBundle;
1818
useSymfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
1919
useSymfony\Component\DependencyInjection\ContainerBuilder;
20+
useSymfony\Component\ExpressionLanguage\Expression;
2021

2122
abstractclass CompleteConfigurationTestextends \PHPUnit_Framework_TestCase
2223
{
@@ -133,27 +134,31 @@ public function testAccess()
133134

134135
$matcherIds =array();
135136
foreach ($rulesas$rule) {
136-
list($matcherId,$roles,$channel) =$rule;
137+
list($matcherId,$attributes,$channel) =$rule;
137138
$requestMatcher =$container->getDefinition($matcherId);
138139

139140
$this->assertFalse(isset($matcherIds[$matcherId]));
140141
$matcherIds[$matcherId] =true;
141142

142143
$i =count($matcherIds);
143144
if (1 ===$i) {
144-
$this->assertEquals(array('ROLE_USER'),$roles);
145+
$this->assertEquals(array('ROLE_USER'),$attributes);
145146
$this->assertEquals('https',$channel);
146147
$this->assertEquals(
147148
array('/blog/524',null,array('GET','POST')),
148149
$requestMatcher->getArguments()
149150
);
150151
}elseif (2 ===$i) {
151-
$this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'),$roles);
152+
$this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'),$attributes);
152153
$this->assertNull($channel);
153154
$this->assertEquals(
154155
array('/blog/.*'),
155156
$requestMatcher->getArguments()
156157
);
158+
}elseif (3 ===$i) {
159+
$this->assertEquals('IS_AUTHENTICATED_ANONYMOUSLY',$attributes[0]);
160+
$expression =$container->getDefinition($attributes[1])->getArgument(0);
161+
$this->assertEquals("token.getUsername() =~ '/^admin/'",$expression);
157162
}
158163
}
159164
}

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
'access_control' =>array(
8383
array('path' =>'/blog/524','role' =>'ROLE_USER','requires_channel' =>'https','methods' =>array('get','POST')),
8484
array('path' =>'/blog/.*','role' =>'IS_AUTHENTICATED_ANONYMOUSLY'),
85+
array('path' =>'/blog/524','role' =>'IS_AUTHENTICATED_ANONYMOUSLY','allow_if' =>"token.getUsername() =~ '/^admin/'"),
8586
),
8687

8788
'role_hierarchy' =>array(

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@
6868

6969
<rulepath="/blog/524"role="ROLE_USER"requires-channel="https"methods="get,POST" />
7070
<rulerole='IS_AUTHENTICATED_ANONYMOUSLY'path="/blog/.*" />
71+
<rulerole='IS_AUTHENTICATED_ANONYMOUSLY'allow-if="token.getUsername() =~ '/^admin/'"path="/blog/524" />
7172
</config>
7273
</srv:container>

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,4 @@ security:
6969
-
7070
path:/blog/.*
7171
role:IS_AUTHENTICATED_ANONYMOUSLY
72+
-{ path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUsername() =~ '/^admin/'" }

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/config/routing.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,6 @@ form_logout:
3737
form_secure_action:
3838
path:/secure-but-not-covered-by-access-control
3939
defaults:{ _controller: FormLoginBundle:Login:secure }
40+
41+
protected-via-expression:
42+
path:/protected-via-expression

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,28 @@ public function testSecurityConfigurationForMultipleIPAddresses($config)
9191
$this->assertRestricted($barredClient,'/secured-by-two-ips');
9292
}
9393

94+
/**
95+
* @dataProvider getConfigs
96+
*/
97+
publicfunctiontestSecurityConfigurationForExpression($config)
98+
{
99+
$allowedClient =$this->createClient(array('test_case' =>'StandardFormLogin','root_config' =>$config),array('HTTP_USER_AGENT' =>'Firefox 1.0'));
100+
$this->assertAllowed($allowedClient,'/protected-via-expression');
101+
102+
$barredClient =$this->createClient(array('test_case' =>'StandardFormLogin','root_config' =>$config),array());
103+
$this->assertRestricted($barredClient,'/protected-via-expression');
104+
105+
$allowedClient =$this->createClient(array('test_case' =>'StandardFormLogin','root_config' =>$config),array());
106+
107+
$allowedClient->request('GET','/protected-via-expression');
108+
$form =$allowedClient->followRedirect()->selectButton('login')->form();
109+
$form['_username'] ='johannes';
110+
$form['_password'] ='test';
111+
$allowedClient->submit($form);
112+
$this->assertRedirect($allowedClient->getResponse(),'/protected-via-expression');
113+
$this->assertAllowed($allowedClient,'/protected-via-expression');
114+
}
115+
94116
privatefunctionassertAllowed($client,$path)
95117
{
96118
$client->request('GET',$path);

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ security:
3131
-{ path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY }
3232
-{ path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY }
3333
-{ path: ^/highly_protected_resource$, roles: IS_ADMIN }
34+
-{ path: ^/protected-via-expression$, allow_if: "(is_anonymous() and object.headers.get('user-agent') =~ '/Firefox/i') or has_role('ROLE_USER')" }
3435
-{ path: .*, roles: IS_AUTHENTICATED_FULLY }

‎src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"symfony/twig-bundle":"~2.2",
2626
"symfony/form":"~2.1",
2727
"symfony/validator":"~2.2",
28-
"symfony/yaml":"~2.0"
28+
"symfony/yaml":"~2.0",
29+
"symfony/expression-language":"~2.4"
2930
},
3031
"autoload": {
3132
"psr-0": {"Symfony\\Bundle\\SecurityBundle\\":"" }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\Security\Core\Authorization;
13+
14+
useSymfony\Component\ExpressionLanguage\ExpressionLanguageasBaseExpressionLanguage;
15+
16+
/**
17+
* Adds some function to the default ExpressionLanguage.
18+
*
19+
* @author Fabien Potencier <fabien@symfony.com>
20+
*/
21+
class ExpressionLanguageextends BaseExpressionLanguage
22+
{
23+
protectedfunctionregisterFunctions()
24+
{
25+
parent::registerFunctions();
26+
27+
$this->addFunction('is_anonymous',function () {
28+
return'$trust_resolver->isAnonymous($token)';
29+
},function (array$variables) {
30+
return$variables['trust_resolver']->isAnonymous($variables['token']);
31+
});
32+
33+
$this->addFunction('is_authenticated',function () {
34+
return'!$trust_resolver->isAnonymous($token)';
35+
},function (array$variables) {
36+
return !$variables['trust_resolver']->isAnonymous($variables['token']);
37+
});
38+
39+
$this->addFunction('is_fully_authenticated',function () {
40+
return'!$trust_resolver->isFullFledge($token)';
41+
},function (array$variables) {
42+
return !$variables['trust_resolver']->isFullFledge($variables['token']);
43+
});
44+
45+
$this->addFunction('is_remember_me',function () {
46+
return'!$trust_resolver->isRememberMe($token)';
47+
},function (array$variables) {
48+
return !$variables['trust_resolver']->isRememberMe($variables['token']);
49+
});
50+
51+
$this->addFunction('has_role',function ($role) {
52+
returnsprintf('in_array(%s, $roles)',$role);
53+
},function (array$variables,$role) {
54+
returnin_array($role,$variables['roles']);
55+
});
56+
}
57+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\Security\Core\Authorization\Voter;
13+
14+
useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;
15+
useSymfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
16+
useSymfony\Component\Security\Core\Authorization\ExpressionLanguage;
17+
useSymfony\Component\Security\Core\Role\RoleHierarchyInterface;
18+
useSymfony\Component\ExpressionLanguage\Expression;
19+
20+
/**
21+
* ExpressionVoter votes based on the evaluation of an expression.
22+
*
23+
* @author Fabien Potencier <fabien@symfony.com>
24+
*/
25+
class ExpressionVoterimplements VoterInterface
26+
{
27+
private$expressionLanguage;
28+
private$trustResolver;
29+
private$roleHierarchy;
30+
31+
/**
32+
* Constructor.
33+
*
34+
* @param ExpressionLanguage $expressionLanguage
35+
*/
36+
publicfunction__construct(ExpressionLanguage$expressionLanguage,AuthenticationTrustResolverInterface$trustResolver,RoleHierarchyInterface$roleHierarchy =null)
37+
{
38+
$this->expressionLanguage =$expressionLanguage;
39+
$this->trustResolver =$trustResolver;
40+
$this->roleHierarchy =$roleHierarchy;
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
publicfunctionsupportsAttribute($attribute)
47+
{
48+
return$attributeinstanceof Expression;
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
publicfunctionsupportsClass($class)
55+
{
56+
returntrue;
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
publicfunctionvote(TokenInterface$token,$object,array$attributes)
63+
{
64+
if (null !==$this->roleHierarchy) {
65+
$roles =$this->roleHierarchy->getReachableRoles($token->getRoles());
66+
}else {
67+
$roles =$token->getRoles();
68+
}
69+
70+
$variables =array(
71+
'token' =>$token,
72+
'user' =>$token->getUser(),
73+
'object' =>$object,
74+
'roles' =>array_map(function ($role) {return$role->getRole(); },$roles),
75+
'trust_resolver' =>$this->trustResolver,
76+
);
77+
78+
$result = VoterInterface::ACCESS_ABSTAIN;
79+
foreach ($attributesas$attribute) {
80+
if (!$this->supportsAttribute($attribute)) {
81+
continue;
82+
}
83+
84+
$result = VoterInterface::ACCESS_DENIED;
85+
if ($this->expressionLanguage->evaluate($attribute,$variables)) {
86+
return VoterInterface::ACCESS_GRANTED;
87+
}
88+
}
89+
90+
return$result;
91+
}
92+
}

‎src/Symfony/Component/Security/Http/AccessMap.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ class AccessMap implements AccessMapInterface
2828
* Constructor.
2929
*
3030
* @param RequestMatcherInterface $requestMatcher A RequestMatcherInterface instance
31-
* @param array $rolesAn array ofroles neededto accessthe resource
31+
* @param array $attributes An array ofattributes to passtotheaccessdecision manager (like roles)
3232
* @param string|null $channel The channel to enforce (http, https, or null)
3333
*/
34-
publicfunctionadd(RequestMatcherInterface$requestMatcher,array$roles =array(),$channel =null)
34+
publicfunctionadd(RequestMatcherInterface$requestMatcher,array$attributes =array(),$channel =null)
3535
{
36-
$this->map[] =array($requestMatcher,$roles,$channel);
36+
$this->map[] =array($requestMatcher,$attributes,$channel);
3737
}
3838

3939
publicfunctiongetPatterns(Request$request)

‎src/Symfony/Component/Security/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"doctrine/common":"~2.2",
2929
"doctrine/dbal":"~2.2",
3030
"psr/log":"~1.0",
31-
"ircmaxell/password-compat":"1.0.*"
31+
"ircmaxell/password-compat":"1.0.*",
32+
"symfony/expression-language":"~2.4"
3233
},
3334
"suggest": {
3435
"symfony/class-loader":"",

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp