Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 473263a

Browse files
committed
feature #18510 Added a SecurityUserValueResolver for controllers (iltar)
This PR was merged into the 3.2-dev branch. Discussion ---------- Added a SecurityUserValueResolver for controllers | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | ~ | License | MIT | Doc PR | ~ This PR uses the new `ArgumentResolver` to inject a security user when the signature implies so. This is based on the [docs code example](symfony/symfony-docs#6438 (comment)) and [existing pr on the SFEB](sensiolabs/SensioFrameworkExtraBundle#327). With the new example you can do the following: ```php // when a User is mandatory, e.g. behind firewall public function fooAction(UserInterface $user) // when a User is optional, e.g. where it can be anonymous public function barAction(UserInterface $user = null) ``` This deprecates the `Controller::getUser()` method. I have added it on a priority of 40 so it falls just under the `RequestValueResolver`. This is because it's already used and the initial performance is less of an impact. There was a comment asking if the `controller_argument.value_resolver` tag name wasn't too long. If decided this tag should change before 3.1 is released, I will update it in here. *`RequestValueResolver` contains a small codestyle consistency fix.* Commits ------- d341889 Added a SecurityUserValueResolver for controllers
2 parents 62f0dbf + d341889 commit 473263a

File tree

13 files changed

+206
-7
lines changed

13 files changed

+206
-7
lines changed

UPGRADE-3.2.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
UPGRADE FROM 3.1 to 3.2
22
=======================
33

4+
FrameworkBundle
5+
---------------
6+
7+
* The `Controller::getUser()` method has been deprecated and will be removed in
8+
Symfony 4.0; typehint the security user object in the action instead.
9+
410
DependencyInjection
511
-------------------
612

UPGRADE-4.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ FrameworkBundle
117117
* The `framework.serializer.cache` option and the services
118118
`serializer.mapping.cache.apc` and `serializer.mapping.cache.doctrine.apc`
119119
have been removed. APCu should now be automatically used when available.
120+
121+
* The `Controller::getUser()` method has been removed in favor of the ability
122+
to typehint the security user object in the action.
120123

121124
HttpKernel
122125
----------

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
3.2.0
5+
-----
6+
7+
* The `Controller::getUser()` method has been deprecated and will be removed in
8+
Symfony 4.0; typehint the security user object in the action instead.
9+
410
3.1.0
511
-----
612

src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2323
use Symfony\Component\HttpKernel\HttpKernelInterface;
2424
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
25+
use Symfony\Component\Security\Core\User\UserInterface;
2526
use Symfony\Component\Security\Csrf\CsrfToken;
2627
use Symfony\Component\Form\Extension\Core\Type\FormType;
2728
use Symfony\Component\Form\Form;
@@ -362,12 +363,16 @@ protected function getDoctrine()
362363
*
363364
* @return mixed
364365
*
366+
* @deprecated as of 3.2 and will be removed in 4.0. You can typehint your method argument with Symfony\Component\Security\Core\User\UserInterface instead.
367+
*
365368
* @throws \LogicException If SecurityBundle is not available
366369
*
367370
* @see TokenInterface::getUser()
368371
*/
369372
protected function getUser()
370373
{
374+
@trigger_error(sprintf('%s() is deprecated as of 3.2 and will be removed in 4.0. You can typehint your method argument with %s instead.', __METHOD__, UserInterface::class), E_USER_DEPRECATED);
375+
371376
if (!$this->container->has('security.token_storage')) {
372377
throw new \LogicException('The SecurityBundle is not registered in your application.');
373378
}

src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ public function testForward()
5656
$this->assertEquals('xml--fr', $response->getContent());
5757
}
5858

59+
/**
60+
* @group legacy
61+
*/
5962
public function testGetUser()
6063
{
6164
$user = new User('user', 'pass');
@@ -67,6 +70,9 @@ public function testGetUser()
6770
$this->assertSame($controller->getUser(), $user);
6871
}
6972

73+
/**
74+
* @group legacy
75+
*/
7076
public function testGetUserAnonymousUserConvertedToNull()
7177
{
7278
$token = new AnonymousToken('default', 'anon.');
@@ -77,6 +83,9 @@ public function testGetUserAnonymousUserConvertedToNull()
7783
$this->assertNull($controller->getUser());
7884
}
7985

86+
/**
87+
* @group legacy
88+
*/
8089
public function testGetUserWithEmptyTokenStorage()
8190
{
8291
$controller = new TestController();
@@ -86,6 +95,7 @@ public function testGetUserWithEmptyTokenStorage()
8695
}
8796

8897
/**
98+
* @group legacy
8999
* @expectedException \LogicException
90100
* @expectedExceptionMessage The SecurityBundle is not registered in your application.
91101
*/

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
3.2.0
5+
-----
6+
7+
* Added the `SecurityUserValueResolver` to inject the security users in actions via
8+
`Symfony\Component\Security\Core\User\UserInterface` in the method signature.
9+
410
3.0.0
511
-----
612

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020

2121
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" />
2222

23+
<service id="security.user_value_resolver" class="Symfony\Bundle\SecurityBundle\SecurityUserValueResolver" public="false">
24+
<argument type="service" id="security.token_storage" />
25+
<tag name="controller.argument_value_resolver" priority="40" />
26+
</service>
27+
2328
<!-- Authentication related services -->
2429
<service id="security.authentication.manager" class="Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager" public="false">
2530
<argument type="collection" />
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 <[email protected]>
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+
namespace Symfony\Bundle\SecurityBundle;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
17+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
18+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19+
use Symfony\Component\Security\Core\User\UserInterface;
20+
21+
/**
22+
* Supports the argument type of {@see UserInterface}.
23+
*
24+
* @author Iltar van der Berg <[email protected]>
25+
*/
26+
final class SecurityUserValueResolver implements ArgumentValueResolverInterface
27+
{
28+
private $tokenStorage;
29+
30+
public function __construct(TokenStorageInterface $tokenStorage)
31+
{
32+
$this->tokenStorage = $tokenStorage;
33+
}
34+
35+
public function supports(Request $request, ArgumentMetadata $argument)
36+
{
37+
// only security user implementations are supported
38+
if (UserInterface::class !== $argument->getType()) {
39+
return false;
40+
}
41+
42+
$token = $this->tokenStorage->getToken();
43+
if (!$token instanceof TokenInterface) {
44+
return false;
45+
}
46+
47+
$user = $token->getUser();
48+
49+
// in case it's not an object we cannot do anything with it; E.g. "anon."
50+
return $user instanceof UserInterface;
51+
}
52+
53+
public function resolve(Request $request, ArgumentMetadata $argument)
54+
{
55+
yield $this->tokenStorage->getToken()->getUser();
56+
}
57+
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
use Symfony\Component\HttpFoundation\Request;
1818
use Symfony\Component\HttpFoundation\Response;
1919
use Symfony\Component\Security\Core\Security;
20+
use Symfony\Component\Security\Core\User\UserInterface;
2021

2122
class LoginController implements ContainerAwareInterface
2223
{
2324
use ContainerAwareTrait;
2425

25-
public function loginAction(Request $request)
26+
public function loginAction(Request $request, UserInterface $user = null)
2627
{
2728
// get the login error if there is one
2829
if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
@@ -38,9 +39,9 @@ public function loginAction(Request $request)
3839
));
3940
}
4041

41-
public function afterLoginAction()
42+
public function afterLoginAction(UserInterface $user)
4243
{
43-
return $this->container->get('templating')->renderResponse('FormLoginBundle:Login:after_login.html.twig');
44+
return $this->container->get('templating')->renderResponse('FormLoginBundle:Login:after_login.html.twig', array('user' => $user));
4445
}
4546

4647
public function loginCheckAction()

src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% extends "::base.html.twig" %}
22

33
{% block body %}
4-
Hello {{ app.user.username }}!<br /><br />
4+
Hello {{ user.username }}!<br /><br />
55
You're browsing to path "{{ app.request.pathInfo }}".
66

77
<a href="{{ logout_path('default') }}">Log out</a>.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
namespace Symfony\Bundle\SecurityBundle\Tests;
13+
14+
use Symfony\Bundle\SecurityBundle\SecurityUserValueResolver;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
17+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
18+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
20+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
21+
use Symfony\Component\Security\Core\User\UserInterface;
22+
23+
class SecurityUserValueResolverTest extends \PHPUnit_Framework_TestCase
24+
{
25+
public function testResolveNoToken()
26+
{
27+
$tokenStorage = new TokenStorage();
28+
$resolver = new SecurityUserValueResolver($tokenStorage);
29+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
30+
31+
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
32+
}
33+
34+
public function testResolveNoUser()
35+
{
36+
$mock = $this->getMock(UserInterface::class);
37+
$token = $this->getMock(TokenInterface::class);
38+
$tokenStorage = new TokenStorage();
39+
$tokenStorage->setToken($token);
40+
41+
$resolver = new SecurityUserValueResolver($tokenStorage);
42+
$metadata = new ArgumentMetadata('foo', get_class($mock), false, false, null);
43+
44+
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
45+
}
46+
47+
public function testResolveWrongType()
48+
{
49+
$tokenStorage = new TokenStorage();
50+
$resolver = new SecurityUserValueResolver($tokenStorage);
51+
$metadata = new ArgumentMetadata('foo', null, false, false, null);
52+
53+
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
54+
}
55+
56+
public function testResolve()
57+
{
58+
$user = $this->getMock(UserInterface::class);
59+
$token = $this->getMock(TokenInterface::class);
60+
$token->expects($this->any())->method('getUser')->willReturn($user);
61+
$tokenStorage = new TokenStorage();
62+
$tokenStorage->setToken($token);
63+
64+
$resolver = new SecurityUserValueResolver($tokenStorage);
65+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
66+
67+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
68+
$this->assertSame(array($user), iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
69+
}
70+
71+
public function testIntegration()
72+
{
73+
$user = $this->getMock(UserInterface::class);
74+
$token = $this->getMock(TokenInterface::class);
75+
$token->expects($this->any())->method('getUser')->willReturn($user);
76+
$tokenStorage = new TokenStorage();
77+
$tokenStorage->setToken($token);
78+
79+
$argumentResolver = new ArgumentResolver(null, array(new SecurityUserValueResolver($tokenStorage)));
80+
$this->assertSame(array($user), $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user) {}));
81+
}
82+
83+
public function testIntegrationNoUser()
84+
{
85+
$token = $this->getMock(TokenInterface::class);
86+
$tokenStorage = new TokenStorage();
87+
$tokenStorage->setToken($token);
88+
89+
$argumentResolver = new ArgumentResolver(null, array(new SecurityUserValueResolver($tokenStorage), new DefaultValueResolver()));
90+
$this->assertSame(array(null), $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user = null) {}));
91+
}
92+
}
93+
94+
abstract class DummyUser implements UserInterface
95+
{
96+
}
97+
98+
abstract class DummySubUser extends DummyUser
99+
{
100+
}

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": ">=5.5.9",
2020
"symfony/security": "~3.1,>=3.1.2",
21-
"symfony/http-kernel": "~2.8|~3.0",
21+
"symfony/http-kernel": "~3.1",
2222
"symfony/polyfill-php70": "~1.0"
2323
},
2424
"require-dev": {
@@ -27,7 +27,7 @@
2727
"symfony/css-selector": "~2.8|~3.0",
2828
"symfony/dom-crawler": "~2.8|~3.0",
2929
"symfony/form": "~2.8|~3.0",
30-
"symfony/framework-bundle": "~2.8|~3.0",
30+
"symfony/framework-bundle": "~3.1",
3131
"symfony/http-foundation": "~2.8|~3.0",
3232
"symfony/security-acl": "~2.8|~3.0",
3333
"symfony/twig-bundle": "~2.8|~3.0",

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class RequestValueResolver implements ArgumentValueResolverInterface
2727
*/
2828
public function supports(Request $request, ArgumentMetadata $argument)
2929
{
30-
return $argument->getType() === Request::class || is_subclass_of($argument->getType(), Request::class);
30+
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class);
3131
}
3232

3333
/**

0 commit comments

Comments
 (0)