From d341889b95d1f708f008a774edb94eea34a61aeb Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Mon, 11 Apr 2016 15:31:32 +0200 Subject: [PATCH] Added a SecurityUserValueResolver for controllers --- UPGRADE-3.2.md | 6 ++ UPGRADE-4.0.md | 3 + .../Bundle/FrameworkBundle/CHANGELOG.md | 6 ++ .../FrameworkBundle/Controller/Controller.php | 5 + .../Tests/Controller/ControllerTest.php | 10 ++ .../Bundle/SecurityBundle/CHANGELOG.md | 6 ++ .../Resources/config/security.xml | 5 + .../SecurityUserValueResolver.php | 57 ++++++++++ .../Controller/LoginController.php | 7 +- .../views/Login/after_login.html.twig | 2 +- .../Tests/SecurityUserValueResolverTest.php | 100 ++++++++++++++++++ .../Bundle/SecurityBundle/composer.json | 4 +- .../ArgumentResolver/RequestValueResolver.php | 2 +- 13 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/SecurityUserValueResolver.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php diff --git a/UPGRADE-3.2.md b/UPGRADE-3.2.md index 32bce0d146847..edd6d70cb5e70 100644 --- a/UPGRADE-3.2.md +++ b/UPGRADE-3.2.md @@ -1,6 +1,12 @@ UPGRADE FROM 3.1 to 3.2 ======================= +FrameworkBundle +--------------- + + * The `Controller::getUser()` method has been deprecated and will be removed in + Symfony 4.0; typehint the security user object in the action instead. + DependencyInjection ------------------- diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index b460020574ecd..14da5cd0533b4 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -117,6 +117,9 @@ FrameworkBundle * The `framework.serializer.cache` option and the services `serializer.mapping.cache.apc` and `serializer.mapping.cache.doctrine.apc` have been removed. APCu should now be automatically used when available. + + * The `Controller::getUser()` method has been removed in favor of the ability + to typehint the security user object in the action. HttpKernel ---------- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index b0237e402b5f0..c714350cb8e74 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +3.2.0 +----- + + * The `Controller::getUser()` method has been deprecated and will be removed in + Symfony 4.0; typehint the security user object in the action instead. + 3.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index 6ca1aa32cf414..62a2c634fba2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -22,6 +22,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Form; @@ -362,12 +363,16 @@ protected function getDoctrine() * * @return mixed * + * @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. + * * @throws \LogicException If SecurityBundle is not available * * @see TokenInterface::getUser() */ protected function getUser() { + @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); + if (!$this->container->has('security.token_storage')) { throw new \LogicException('The SecurityBundle is not registered in your application.'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php index 2821e7faf837e..9711ab260a811 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php @@ -56,6 +56,9 @@ public function testForward() $this->assertEquals('xml--fr', $response->getContent()); } + /** + * @group legacy + */ public function testGetUser() { $user = new User('user', 'pass'); @@ -67,6 +70,9 @@ public function testGetUser() $this->assertSame($controller->getUser(), $user); } + /** + * @group legacy + */ public function testGetUserAnonymousUserConvertedToNull() { $token = new AnonymousToken('default', 'anon.'); @@ -77,6 +83,9 @@ public function testGetUserAnonymousUserConvertedToNull() $this->assertNull($controller->getUser()); } + /** + * @group legacy + */ public function testGetUserWithEmptyTokenStorage() { $controller = new TestController(); @@ -86,6 +95,7 @@ public function testGetUserWithEmptyTokenStorage() } /** + * @group legacy * @expectedException \LogicException * @expectedExceptionMessage The SecurityBundle is not registered in your application. */ diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 3d2cf370c3745..d83d4a62d1e54 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +3.2.0 +----- + + * Added the `SecurityUserValueResolver` to inject the security users in actions via + `Symfony\Component\Security\Core\User\UserInterface` in the method signature. + 3.0.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 930616901064a..c34453cb3854f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -20,6 +20,11 @@ + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityUserValueResolver.php b/src/Symfony/Bundle/SecurityBundle/SecurityUserValueResolver.php new file mode 100644 index 0000000000000..01a4f2bda6d37 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/SecurityUserValueResolver.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Supports the argument type of {@see UserInterface}. + * + * @author Iltar van der Berg + */ +final class SecurityUserValueResolver implements ArgumentValueResolverInterface +{ + private $tokenStorage; + + public function __construct(TokenStorageInterface $tokenStorage) + { + $this->tokenStorage = $tokenStorage; + } + + public function supports(Request $request, ArgumentMetadata $argument) + { + // only security user implementations are supported + if (UserInterface::class !== $argument->getType()) { + return false; + } + + $token = $this->tokenStorage->getToken(); + if (!$token instanceof TokenInterface) { + return false; + } + + $user = $token->getUser(); + + // in case it's not an object we cannot do anything with it; E.g. "anon." + return $user instanceof UserInterface; + } + + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->tokenStorage->getToken()->getUser(); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php index ad003158f5179..acc8f43255494 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php @@ -17,12 +17,13 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\User\UserInterface; class LoginController implements ContainerAwareInterface { use ContainerAwareTrait; - public function loginAction(Request $request) + public function loginAction(Request $request, UserInterface $user = null) { // get the login error if there is one if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { @@ -38,9 +39,9 @@ public function loginAction(Request $request) )); } - public function afterLoginAction() + public function afterLoginAction(UserInterface $user) { - return $this->container->get('templating')->renderResponse('FormLoginBundle:Login:after_login.html.twig'); + return $this->container->get('templating')->renderResponse('FormLoginBundle:Login:after_login.html.twig', array('user' => $user)); } public function loginCheckAction() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig index 3ef1f9c7bd183..8f6a76098597f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig @@ -1,7 +1,7 @@ {% extends "::base.html.twig" %} {% block body %} - Hello {{ app.user.username }}!

+ Hello {{ user.username }}!

You're browsing to path "{{ app.request.pathInfo }}". Log out. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php new file mode 100644 index 0000000000000..e0d626a8cee40 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests; + +use Symfony\Bundle\SecurityBundle\SecurityUserValueResolver; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; + +class SecurityUserValueResolverTest extends \PHPUnit_Framework_TestCase +{ + public function testResolveNoToken() + { + $tokenStorage = new TokenStorage(); + $resolver = new SecurityUserValueResolver($tokenStorage); + $metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null); + + $this->assertFalse($resolver->supports(Request::create('/'), $metadata)); + } + + public function testResolveNoUser() + { + $mock = $this->getMock(UserInterface::class); + $token = $this->getMock(TokenInterface::class); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + + $resolver = new SecurityUserValueResolver($tokenStorage); + $metadata = new ArgumentMetadata('foo', get_class($mock), false, false, null); + + $this->assertFalse($resolver->supports(Request::create('/'), $metadata)); + } + + public function testResolveWrongType() + { + $tokenStorage = new TokenStorage(); + $resolver = new SecurityUserValueResolver($tokenStorage); + $metadata = new ArgumentMetadata('foo', null, false, false, null); + + $this->assertFalse($resolver->supports(Request::create('/'), $metadata)); + } + + public function testResolve() + { + $user = $this->getMock(UserInterface::class); + $token = $this->getMock(TokenInterface::class); + $token->expects($this->any())->method('getUser')->willReturn($user); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + + $resolver = new SecurityUserValueResolver($tokenStorage); + $metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null); + + $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); + $this->assertSame(array($user), iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); + } + + public function testIntegration() + { + $user = $this->getMock(UserInterface::class); + $token = $this->getMock(TokenInterface::class); + $token->expects($this->any())->method('getUser')->willReturn($user); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + + $argumentResolver = new ArgumentResolver(null, array(new SecurityUserValueResolver($tokenStorage))); + $this->assertSame(array($user), $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user) {})); + } + + public function testIntegrationNoUser() + { + $token = $this->getMock(TokenInterface::class); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + + $argumentResolver = new ArgumentResolver(null, array(new SecurityUserValueResolver($tokenStorage), new DefaultValueResolver())); + $this->assertSame(array(null), $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user = null) {})); + } +} + +abstract class DummyUser implements UserInterface +{ +} + +abstract class DummySubUser extends DummyUser +{ +} diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 40d2e047413b2..495c670e7d7b8 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.5.9", "symfony/security": "~3.1,>=3.1.2", - "symfony/http-kernel": "~2.8|~3.0", + "symfony/http-kernel": "~3.1", "symfony/polyfill-php70": "~1.0" }, "require-dev": { @@ -27,7 +27,7 @@ "symfony/css-selector": "~2.8|~3.0", "symfony/dom-crawler": "~2.8|~3.0", "symfony/form": "~2.8|~3.0", - "symfony/framework-bundle": "~2.8|~3.0", + "symfony/framework-bundle": "~3.1", "symfony/http-foundation": "~2.8|~3.0", "symfony/security-acl": "~2.8|~3.0", "symfony/twig-bundle": "~2.8|~3.0", diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php index 5dabc5db401ae..2a5060a612681 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -27,7 +27,7 @@ final class RequestValueResolver implements ArgumentValueResolverInterface */ public function supports(Request $request, ArgumentMetadata $argument) { - return $argument->getType() === Request::class || is_subclass_of($argument->getType(), Request::class); + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); } /** pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy