10000 Throw access denied if CurrentUser cannot be resolved or user does no… · symfony/symfony@7839261 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7839261

Browse files
committed
Throw access denied if CurrentUser cannot be resolved or user does not match expected class
This allows the UserValueResolver to be used for access control by typing a specific class, as well as ensuring that if the user is logged in anonymously they will get a 403/401 and not a 500
1 parent 8e8207b commit 7839261

File tree

2 files changed

+83
-22
lines changed

2 files changed

+83
-22
lines changed

src/Symfony/Component/Security/Http/Controller/UserValueResolver.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
1616
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1717
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
18-
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
18+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
1919
use Symfony\Component\Security\Core\User\UserInterface;
2020
use Symfony\Component\Security\Http\Attribute\CurrentUser;
2121

@@ -41,13 +41,22 @@ public function supports(Request $request, ArgumentMetadata $argument): bool
4141
return false;
4242
}
4343

44-
$token = $this->tokenStorage->getToken();
45-
46-
return $token instanceof TokenInterface;
44+
return true;
4745
}
4846

4947
public function resolve(Request $request, ArgumentMetadata $argument): iterable
5048
{
51-
yield $this->tokenStorage->getToken()->getUser();
49+
$user = $this->tokenStorage->getToken()?->getUser();
50+
51+
if (null === $user) {
52+
if (!$argument->isNullable()) {
53+
throw new AccessDeniedException(sprintf('There is no logged-in user to pass to $%s, make the argument nullable if you want to allow anonymous access to the action.', $argument->getName()));
54+
}
55+
yield null;
56+
} elseif (null === $argument->getType() || $user instanceof ($argument->getType())) {
57+
yield $user;
58+
} else {
59+
throw new AccessDeniedException(sprintf('The logged-in user is an instance of "%s" and an user of type "%s" is expected.', $user::class, $argument->getType()));
60+
}
5261
}
5362
}

src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,61 +16,83 @@
1616
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
1717
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
1818
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19+
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
1920
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
2021
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
22+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
2123
use Symfony\Component\Security\Core\User\In E864 MemoryUser;
2224
use Symfony\Component\Security\Core\User\UserInterface;
2325
use Symfony\Component\Security\Http\Attribute\CurrentUser;
2426
use Symfony\Component\Security\Http\Controller\UserValueResolver;
2527

2628
class UserValueResolverTest extends TestCase
2729
{
28-
public function testResolveNoToken()
30+
public function testSupportsFailsWithNoType()
2931
{
3032
$tokenStorage = new TokenStorage();
3133
$resolver = new UserValueResolver($tokenStorage);
32-
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
34+
$metadata = new ArgumentMetadata('foo', null, false, false, null);
3335

3436
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
3537
}
3638

37-
public function testResolveNoUser()
39+
public function testResolveSucceedsWithUserInterface()
3840
{
39-
$mock = $this->createMock(UserInterface::class);
40-
$token = new UsernamePasswordToken(new InMemoryUser('username', 'password'), 'provider');
41+
$user = new InMemoryUser('username', 'password');
42+
$token = new UsernamePasswordToken($user, 'provider');
4143
$tokenStorage = new TokenStorage();
4244
$tokenStorage->setToken($token);
4345

4446
$resolver = new UserValueResolver($tokenStorage);
45-
$metadata = new ArgumentMetadata('foo', \get_class($mock), false, false, null);
47+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
4648

47-
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
49+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
50+
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
4851
}
4952

50-
public function testResolveWrongType()
53+
public function testResolveSucceedsWithSubclassType()
5154
{
55+
$user = new InMemoryUser('username', 'password');
56+
$token = new UsernamePasswordToken($user, 'provider');
5257
$tokenStorage = new TokenStorage();
58+
$tokenStorage->setToken($token);
59+
5360
$resolver = new UserValueResolver($tokenStorage);
54-
$metadata = new ArgumentMetadata('foo', null, false, false, null);
61+
$metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]);
5562

56-
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
63+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
64+
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
65+
}
66+
67+
public function testResolveSucceedsWithNullableParamAndNoUser()
68+
{
69+
$token = new NullToken();
70+
$tokenStorage = new TokenStorage();
71+
$tokenStorage->setToken($token);
72+
73+
$resolver = new UserValueResolver($tokenStorage);
74+
$metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, true, [new CurrentUser()]);
75+
76+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
77+
$this->assertSame([null], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
5778
}
5879

59-
public function testResolve()
80+
public function testResolveSucceedsWithNullableAttribute()
6081
{
6182
$user = new InMemoryUser('username', 'password');
6283
$token = new UsernamePasswordToken($user, 'provider');
6384
$tokenStorage = new TokenStorage();
6485
$tokenStorage->setToken($token);
6586

6687
$resolver = new UserValueResolver($tokenStorage);
67-
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
88+
$metadata = $this->createMock(ArgumentMetadata::class);
89+
$metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]);
6890

6991
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
7092
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
7193
}
7294

73-
public function testResolveWithAttribute()
95+
public function testResolveSucceedsWithTypedAttribute()
7496
{
7597
$user = new InMemoryUser('username', 'password');
7698
$token = new UsernamePasswordToken($user, 'provider');
@@ -79,20 +101,50 @@ public function testResolveWithAttribute()
79101

80102
$resolver = new UserValueResolver($tokenStorage);
81103
$metadata = $this->createMock(ArgumentMetadata::class);
82-
$metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]);
104+
$metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]);
83105

84106
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
85107
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
86108
}
87109

88-
public function testResolveWithAttributeAndNoUser()
110+
public function testResolveThrowsAccessDeniedWithWrongUserClass()
89111
{
112+
$user = $this->createMock(UserInterface::class);
113+
$token = new UsernamePasswordToken($user, 'provider');
90114
$tokenStorage = new TokenStorage();
115+
$tokenStorage->setToken($token);
91116

92117
$resolver = new UserValueResolver($tokenStorage);
93-
$metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]);
118+
$metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]);
94119

95-
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
120+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
121+
$this->expectException(AccessDeniedException::class);
122+
$this->expectExceptionMessageMatches('/^The logged-in user is an instance of "Mock_UserInterface[^"]+" and an user of type "Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\InMemoryUser" is expected.$/');
123+
iterator_to_array($resolver->resolve(Request::create('/'), $metadata));
124+
}
125+
126+
public function testResolveThrowsAccessDeniedWithAttributeAndNoUser()
127+
{
128+
$tokenStorage = new TokenStorage();
129+
130+
$resolver = new UserValueResolver($tokenStorage);
131+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null, false, [new CurrentUser()]);
132+
133+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
134+
$this->expectException(AccessDeniedException::class);
135+
$this->expectExceptionMessage('There is no logged-in user to pass to $foo, make the argument nullable if you want to allow anonymous access to the action.');
136+
iterator_to_array($resolver->resolve(Request::create('/'), $metadata));
137+
}
138+
139+
public function testResolveThrowsAcessDeniedWithNoToken()
140+
{
141+
$tokenStorage = new TokenStorage();
142+
$resolver = new UserValueResolver($tokenStorage);
143+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
144+
145+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
146+
$this->expectException(AccessDeniedException::class);
147+
iterator_to_array($resolver->resolve(Request::create('/'), $metadata));
96148
}
97149

98150
public function testIntegration()

0 commit comments

Comments
 (0)
0