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

Skip to content

Commit c7e6620

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 c7e6620

File tree

2 files changed

+82
-21
lines changed

2 files changed

+82
-21
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1717
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1818
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
1920
use Symfony\Component\Security\Core\User\UserInterface;
2021
use Symfony\Component\Security\Http\Attribute\CurrentUser;
2122

@@ -41,13 +42,22 @@ public function supports(Request $request, ArgumentMetadata $argument): bool
4142
return false;
4243
}
4344

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

4948
public function resolve(Request $request, ArgumentMetadata $argument): iterable
5049
{
51-
yield $this->tokenStorage->getToken()->getUser();
50+
$user = $this->tokenStorage->getToken()?->getUser();
51+
52+
if (null === $user) {
53+
if (!$argument->isNullable()) {
54+
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()));
55+
}
56+
yield null;
< 10000 code>57+
} elseif (null === $argument->getType() || $user instanceof ($argument->getType())) {
58+
yield $user;
59+
} else {
60+
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()));
61+
}
5262
}
5363
}

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

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,60 +17,81 @@
1717
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
1818
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1919
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
20+
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
2021
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
22+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
2123
use Symfony\Component\Security\Core\User\InMemoryUser;
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)));
5765
}
5866

59-
public function testResolve()
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)));
78+
}
79+
public function testResolveSucceedsWithNullableAttribute()
6080
{
6181
$user = new InMemoryUser('username', 'password');
6282
$token = new UsernamePasswordToken($user, 'provider');
6383
$tokenStorage = new TokenStorage();
6484
$tokenStorage->setToken($token);
6585

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

6990
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
7091
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
7192
}
7293

73-
public function testResolveWithAttribute()
94+
public function testResolveSucceedsWithTypedAttribute()
7495
{
7596
$user = new InMemoryUser('username', 'password');
7697
$token = new UsernamePasswordToken($user, 'provider');
@@ -79,20 +100,50 @@ public function testResolveWithAttribute()
79100

80101
$resolver = new UserValueResolver($tokenStorage);
81102
$metadata = $this->createMock(ArgumentMetadata::class);
82-
$metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]);
103+
$metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]);
83104

84105
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
85106
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
86107
}
87108

88-
public function testResolveWithAttributeAndNoUser()
109+
public function testResolveThrowsAccessDeniedWithWrongUserClass()
89110
{
111+
$user = $this->createMock(UserInterface::class);;
112+
$token = new UsernamePasswordToken($user, 'provider');
90113
$tokenStorage = new TokenStorage();
114+
$tokenStorage->setToken($token);
91115

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

95-
$this->assertFalse($resolver->supports(Request::create('/'), $metadata));
119+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
120+
$this->expectException(AccessDeniedException::class);
121+
$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.$/');
122+
iterator_to_array($resolver->resolve(Request::create('/'), $metadata));
123+
}
124+
125+
public function testResolveThrowsAccessDeniedWithAttributeAndNoUser()
126+
{
127+
$tokenStorage = new TokenStorage();
128+
129+
$resolver = new UserValueResolver($tokenStorage);
130+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null, false, [new CurrentUser()]);
131+
132+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
133+
$this->expectException(AccessDeniedException::class);
134+
$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.');
135+
iterator_to_array($resolver->resolve(Request::create('/'), $metadata));
136+
}
137+
138+
public function testResolveThrowsAcessDeniedWithNoToken()
139+
{
140+
$tokenStorage = new TokenStorage();
141+
$resolver = new UserValueResolver($tokenStorage);
142+
$metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null);
143+
144+
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
145+
$this->expectException(AccessDeniedException::class);
146+
iterator_to_array($resolver->resolve(Request::create('/'), $metadata));
96147
}
97148

98149
public function testIntegration()

0 commit comments

Comments
 (0)
0