Skip to content

Commit 957a621

Browse files
authored
Merge pull request #30 from moufmouf/userinterface_usage
Add a "me" query and modify login return type
2 parents 0cd551b + 73f00db commit 957a621

File tree

12 files changed

+382
-9
lines changed

12 files changed

+382
-9
lines changed

Controller/GraphQL/LoginController.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
1010
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
1111
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
12+
use Symfony\Component\Security\Core\User\UserInterface;
1213
use Symfony\Component\Security\Core\User\UserProviderInterface;
1314
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
1415
use TheCodingMachine\GraphQLite\Annotations\Mutation;
@@ -55,12 +56,12 @@ public function __construct(UserProviderInterface $userProvider, UserPasswordEnc
5556
/**
5657
* @Mutation()
5758
*/
58-
public function login(string $userName, string $password, Request $request): bool
59+
public function login(string $userName, string $password, Request $request): UserInterface
5960
{
6061
try {
6162
$user = $this->userProvider->loadUserByUsername($userName);
6263
} catch (UsernameNotFoundException $e) {
63-
// FIXME: should we return false instead???
64+
// FIXME: should we return null instead???
6465
throw InvalidUserPasswordException::create($e);
6566
}
6667

@@ -83,7 +84,7 @@ public function login(string $userName, string $password, Request $request): boo
8384
$event = new InteractiveLoginEvent($request, $token);
8485
$this->eventDispatcher->dispatch($event, 'security.interactive_login');
8586

86-
return true;
87+
return $user;
8788
}
8889

8990
/**

Controller/GraphQL/MeController.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
namespace TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL;
3+
4+
5+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
6+
use Symfony\Component\HttpFoundation\Request;
7+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
8+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
9+
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
10+
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
11+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
12+
use Symfony\Component\Security\Core\User\UserInterface;
13+
use Symfony\Component\Security\Core\User\UserProviderInterface;
14+
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
15+
use TheCodingMachine\GraphQLite\Annotations\Mutation;
16+
use TheCodingMachine\GraphQLite\Annotations\Query;
17+
use TheCodingMachine\Graphqlite\Bundle\Types\BasicUser;
18+
19+
class MeController
20+
{
21+
/**
22+
* @var TokenStorageInterface
23+
*/
24+
private $tokenStorage;
25+
26+
public function __construct(TokenStorageInterface $tokenStorage)
27+
{
28+
$this->tokenStorage = $tokenStorage;
29+
}
30+
31+
/**
32+
* @Query()
33+
*/
34+
public function me(): ?UserInterface
35+
{
36+
$token = $this->tokenStorage->getToken();
37+
if ($token === null) {
38+
return null;
39+
}
40+
41+
$user = $token->getUser();
42+
43+
if (!$user instanceof UserInterface) {
44+
// getUser() can be an object with a toString or a string
45+
$userName = (string) $user;
46+
$user = new BasicUser($userName);
47+
}
48+
49+
return $user;
50+
}
51+
}

DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public function getConfigTreeBuilder()
4040
->arrayNode('security')
4141
->children()
4242
->enumNode('enable_login')->values(['on', 'off', 'auto'])->defaultValue('auto')->info('Enable to automatically create a login/logout mutation. "on": enable, "auto": enable if security bundle is available.')->end()
43+
->enumNode('enable_me')->values(['on', 'off', 'auto'])->defaultValue('auto')->info('Enable to automatically create a "me" query to fetch the current user. "on": enable, "auto": enable if security bundle is available.')->end()
4344
->scalarNode('firewall_name')->defaultValue('main')->info('The name of the firewall to use for login')->end()
4445
->end()
4546
->end()

DependencyInjection/GraphqliteCompilerPass.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
use TheCodingMachine\GraphQLite\Annotations\Parameter;
4848
use TheCodingMachine\GraphQLite\Annotations\Query;
4949
use TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController;
50+
use TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\MeController;
5051
use TheCodingMachine\GraphQLite\FieldsBuilder;
5152
use TheCodingMachine\GraphQLite\FieldsBuilderFactory;
5253
use TheCodingMachine\GraphQLite\GraphQLException;
@@ -115,7 +116,6 @@ public function process(ContainerBuilder $container)
115116
// If the security is disabled, let's remove the LoginController
116117
if ($disableLogin === true) {
117118
$container->removeDefinition(LoginController::class);
118-
$container->removeDefinition(AggregateControllerQueryProviderFactory::class);
119119
}
120120

121121
if ($container->getParameter('graphqlite.security.enable_login') === 'on') {
@@ -132,6 +132,36 @@ public function process(ContainerBuilder $container)
132132
$provider = $container->findDefinition('security.firewall.map.config.'.$firewallName)->getArgument(5);
133133

134134
$container->findDefinition(LoginController::class)->setArgument(0, new Reference($provider));
135+
136+
$this->registerController(LoginController::class, $container);
137+
}
138+
139+
$disableMe = false;
140+
if ($container->getParameter('graphqlite.security.enable_me') === 'auto'
141+
&& !$container->has(TokenStorageInterface::class)) {
142+
$disableMe = true;
143+
}
144+
if ($container->getParameter('graphqlite.security.enable_me') === 'off') {
145+
$disableMe = true;
146+
}
147+
// If the security is disabled, let's remove the LoginController
148+
if ($disableMe === true) {
149+
$container->removeDefinition(MeController::class);
150+
}
151+
152+
if ($container->getParameter('graphqlite.security.enable_me') === 'on') {
153+
if (!$container->has(TokenStorageInterface::class)) {
154+
throw new GraphQLException('In order to enable the "me" query (via the graphqlite.security.enable_me parameter), you need to install the security bundle.');
155+
}
156+
}
157+
158+
if ($disableMe === false) {
159+
$this->registerController(MeController::class, $container);
160+
}
161+
162+
// Perf improvement: let's remove the AggregateControllerQueryProviderFactory if it is empty.
163+
if (empty($container->findDefinition(AggregateControllerQueryProviderFactory::class)->getArgument(0))) {
164+
$container->removeDefinition(AggregateControllerQueryProviderFactory::class);
135165
}
136166

137167

@@ -220,6 +250,14 @@ public function process(ContainerBuilder $container)
220250
$this->mapAdderToTag('graphql.type_mapper_factory', 'addTypeMapperFactory', $container, $schemaFactory);
221251
}
222252

253+
private function registerController(string $controllerClassName, ContainerBuilder $container): void
254+
{
255+
$aggregateQueryProvider = $container->findDefinition(AggregateControllerQueryProviderFactory::class);
256+
$controllersList = $aggregateQueryProvider->getArgument(0);
257+
$controllersList[] = $controllerClassName;
258+
$aggregateQueryProvider->setArgument(0, $controllersList);
259+
}
260+
223261
/**
224262
* Register a method call on SchemaFactory for each tagged service, passing the service in parameter.
225263
*

DependencyInjection/GraphqliteExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ public function load(array $configs, ContainerBuilder $container)
5555
}
5656

5757
$enableLogin = $configs[0]['security']['enable_login'] ?? 'auto';
58+
$enableMe = $configs[0]['security']['enable_me'] ?? 'auto';
5859

5960
$container->setParameter('graphqlite.namespace.controllers', $namespaceController);
6061
$container->setParameter('graphqlite.namespace.types', $namespaceType);
6162
$container->setParameter('graphqlite.security.enable_login', $enableLogin);
63+
$container->setParameter('graphqlite.security.enable_me', $enableMe);
6264
$container->setParameter('graphqlite.security.firewall_name', $configs[0]['security']['firewall_name'] ?? 'main');
6365

6466
$loader->load('graphqlite.xml');

Resources/config/container/graphqlite.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
<service id="TheCodingMachine\GraphQLite\AggregateControllerQueryProviderFactory">
3232
<argument type="collection">
33-
<argument>TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController</argument>
3433
</argument>
3534
<tag name="graphql.queryprovider_factory" />
3635
</service>
@@ -77,5 +76,16 @@
7776
<service id="TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController" public="true">
7877
<argument key="$firewallName">%graphqlite.security.firewall_name%</argument>
7978
</service>
79+
80+
<service id="TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\MeController" public="true" />
81+
82+
<service id="TheCodingMachine\Graphqlite\Bundle\Types\UserType" public="true"/>
83+
84+
<service id="TheCodingMachine\GraphQLite\Mappers\StaticClassListTypeMapperFactory">
85+
<argument type="collection">
86+
<argument>TheCodingMachine\Graphqlite\Bundle\Types\UserType</argument>
87+
</argument>
88+
<tag name="graphql.type_mapper_factory"/>
89+
</service>
8090
</services>
8191
</container>

Tests/FunctionalTest.php

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,10 @@ public function testLoginQuery(): void
220220

221221
$request = Request::create('/graphql', 'POST', ['query' => '
222222
mutation login {
223-
login(userName: "foo", password: "bar")
223+
login(userName: "foo", password: "bar") {
224+
userName
225+
roles
226+
}
224227
}']);
225228

226229
$response = $kernel->handle($request);
@@ -229,9 +232,47 @@ public function testLoginQuery(): void
229232

230233
$this->assertSame([
231234
'data' => [
232-
'login' => true
235+
'login' => [
236+
'userName' => 'foo',
237+
'roles' => [
238+
'ROLE_USER'
239+
]
240+
]
241+
]
242+
], $result);
243+
}
244+
245+
public function testMeQuery(): void
246+
{
247+
$kernel = new GraphqliteTestingKernel();
248+
$kernel->boot();
249+
250+
$session = new Session(new MockArraySessionStorage());
251+
$container = $kernel->getContainer();
252+
$container->set('session', $session);
253+
254+
$request = Request::create('/graphql', 'POST', ['query' => '
255+
{
256+
me {
257+
userName
258+
roles
259+
}
260+
}
261+
']);
262+
263+
$response = $kernel->handle($request);
264+
265+
$result = json_decode($response->getContent(), true);
266+
267+
$this->assertSame([
268+
'data' => [
269+
'me' => [
270+
'userName' => 'anon.',
271+
'roles' => [],
272+
]
233273
]
234274
], $result);
275+
235276
}
236277

237278
public function testNoLoginNoSessionQuery(): void
@@ -241,7 +282,9 @@ public function testNoLoginNoSessionQuery(): void
241282

242283
$request = Request::create('/graphql', 'POST', ['query' => '
243284
mutation login {
244-
login(userName: "foo", password: "bar")
285+
login(userName: "foo", password: "bar") {
286+
userName
287+
}
245288
}']);
246289

247290
$response = $kernel->handle($request);
@@ -259,6 +302,14 @@ public function testForceLoginNoSession(): void
259302
$kernel->boot();
260303
}
261304

305+
public function testForceMeNoSecurity(): void
306+
{
307+
$kernel = new GraphqliteTestingKernel(false, 'off', false, 'on');
308+
$this->expectException(GraphQLException::class);
309+
$this->expectExceptionMessage('In order to enable the "me" query (via the graphqlite.security.enable_me parameter), you need to install the security bundle.');
310+
$kernel->boot();
311+
}
312+
262313
public function testForceLoginNoSecurity(): void
263314
{
264315
$kernel = new GraphqliteTestingKernel(true, 'on', false);
@@ -267,6 +318,63 @@ public function testForceLoginNoSecurity(): void
267318
$kernel->boot();
268319
}
269320

321+
/*public function testAutoMeNoSecurity(): void
322+
{
323+
$kernel = new GraphqliteTestingKernel(true, null, false);
324+
$kernel->boot();
325+
326+
$session = new Session(new MockArraySessionStorage());
327+
$container = $kernel->getContainer();
328+
$container->set('session', $session);
329+
330+
$request = Request::create('/graphql', 'POST', ['query' => '
331+
{
332+
me {
333+
userName
334+
roles
335+
}
336+
}
337+
']);
338+
339+
$response = $kernel->handle($request);
340+
341+
$result = json_decode($response->getContent(), true);
342+
343+
$this->assertSame([
344+
'data' => [
345+
'me' => [
346+
'userName' => 'anon.',
347+
'roles' => [],
348+
]
349+
]
350+
], $result);
351+
}*/
352+
353+
public function testAllOff(): void
354+
{
355+
$kernel = new GraphqliteTestingKernel(true, 'off', true, 'off');
356+
$kernel->boot();
357+
358+
$session = new Session(new MockArraySessionStorage());
359+
$container = $kernel->getContainer();
360+
$container->set('session', $session);
361+
362+
$request = Request::create('/graphql', 'POST', ['query' => '
363+
{
364+
me {
365+
userName
366+
roles
367+
}
368+
}
369+
']);
370+
371+
$response = $kernel->handle($request);
372+
373+
$result = json_decode($response->getContent(), true);
374+
375+
$this->assertSame('Cannot query field "me" on type "Query".', $result['errors'][0]['message']);
376+
}
377+
270378
private function logIn(ContainerInterface $container)
271379
{
272380
// put a token into the storage so the final calls can function

Tests/GraphqliteTestingKernel.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ class GraphqliteTestingKernel extends Kernel
3333
* @var bool
3434
*/
3535
private $enableSecurity;
36+
/**
37+
* @var string|null
38+
*/
39+
private $enableMe;
3640

37-
public function __construct(bool $enableSession = true, ?string $enableLogin = null, bool $enableSecurity = true)
41+
public function __construct(bool $enableSession = true, ?string $enableLogin = null, bool $enableSecurity = true, ?string $enableMe = null)
3842
{
3943
parent::__construct('test', true);
4044
$this->enableSession = $enableSession;
4145
$this->enableLogin = $enableLogin;
4246
$this->enableSecurity = $enableSecurity;
47+
$this->enableMe = $enableMe;
4348
}
4449

4550
public function registerBundles()
@@ -115,6 +120,12 @@ public function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
115120
];
116121
}
117122

123+
if ($this->enableMe) {
124+
$graphqliteConf['security'] = [
125+
'enable_me' => $this->enableMe,
126+
];
127+
}
128+
118129
$container->loadFromExtension('graphqlite', $graphqliteConf);
119130
});
120131
$confDir = $this->getProjectDir().'/Tests/Fixtures/config';

0 commit comments

Comments
 (0)