Skip to content

Added a SecurityUserValueResolver for controllers #18510

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

Merged
merged 1 commit into from
Jul 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions UPGRADE-3.2.md
Original file line number Diff line number Diff line change
@@ -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
-------------------

Expand Down
3 changes: 3 additions & 0 deletions UPGRADE-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
-----

Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public function testForward()
$this->assertEquals('xml--fr', $response->getContent());
}

/**
* @group legacy
*/
public function testGetUser()
{
$user = new User('user', 'pass');
Expand All @@ -67,6 +70,9 @@ public function testGetUser()
$this->assertSame($controller->getUser(), $user);
}

/**
* @group legacy
*/
public function testGetUserAnonymousUserConvertedToNull()
{
$token = new AnonymousToken('default', 'anon.');
Expand All @@ -77,6 +83,9 @@ public function testGetUserAnonymousUserConvertedToNull()
$this->assertNull($controller->getUser());
}

/**
* @group legacy
*/
public function testGetUserWithEmptyTokenStorage()
{
$controller = new TestController();
Expand All @@ -86,6 +95,7 @@ public function testGetUserWithEmptyTokenStorage()
}

/**
* @group legacy
* @expectedException \LogicException
* @expectedExceptionMessage The SecurityBundle is not registered in your application.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
-----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@

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

<service id="security.user_value_resolver" class="Symfony\Bundle\SecurityBundle\SecurityUserValueResolver" public="false">
<argument type="service" id="security.token_storage" />
<tag name="controller.argument_value_resolver" priority="40" />
</service>

<!-- Authentication related services -->
<service id="security.authentication.manager" class="Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager" public="false">
<argument type="collection" />
Expand Down
57 changes: 57 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/SecurityUserValueResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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;

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 <kjarli@gmail.com>
*/
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "::base.html.twig" %}

{% block body %}
Hello {{ app.user.username }}!<br /><br />
Hello {{ user.username }}!<br /><br />
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this should be changed, app.user is still a valid way to access the user in a template.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm I was in the assumption there was another location where this was tested, I'll add a second test to verify this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The CsrfFormLoginBundle also has after_login.html.twig test and still does Hello {{ app.user.username }}!

You're browsing to path "{{ app.request.pathInfo }}".

<a href="{{ logout_path('default') }}">Log out</a>.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?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\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
{
}
4 changes: 2 additions & 2 deletions src/Symfony/Bundle/SecurityBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down