Skip to content

Commit

Permalink
Merge 1a6badd into 0cd551b
Browse files Browse the repository at this point in the history
  • Loading branch information
moufmouf committed Jul 26, 2019
2 parents 0cd551b + 1a6badd commit d044b35
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 9 deletions.
7 changes: 4 additions & 3 deletions Controller/GraphQL/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use TheCodingMachine\GraphQLite\Annotations\Mutation;
Expand Down Expand Up @@ -55,12 +56,12 @@ public function __construct(UserProviderInterface $userProvider, UserPasswordEnc
/**
* @Mutation()
*/
public function login(string $userName, string $password, Request $request): bool
public function login(string $userName, string $password, Request $request): UserInterface
{
try {
$user = $this->userProvider->loadUserByUsername($userName);
} catch (UsernameNotFoundException $e) {
// FIXME: should we return false instead???
// FIXME: should we return null instead???
throw InvalidUserPasswordException::create($e);
}

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

return true;
return $user;
}

/**
Expand Down
51 changes: 51 additions & 0 deletions Controller/GraphQL/MeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
namespace TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL;


use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use TheCodingMachine\GraphQLite\Annotations\Mutation;
use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\Graphqlite\Bundle\Types\BasicUser;

class MeController
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage;

public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}

/**
* @Query()
*/
public function me(): ?UserInterface
{
$token = $this->tokenStorage->getToken();
if ($token === null) {
return null;
}

$user = $token->getUser();

if (!$user instanceof UserInterface) {
// getUser() can be an object with a toString or a string
$userName = (string) $user;
$user = new BasicUser($userName);
}

return $user;
}
}
1 change: 1 addition & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function getConfigTreeBuilder()
->arrayNode('security')
->children()
->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()
->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()
->scalarNode('firewall_name')->defaultValue('main')->info('The name of the firewall to use for login')->end()
->end()
->end()
Expand Down
40 changes: 39 additions & 1 deletion DependencyInjection/GraphqliteCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
use TheCodingMachine\GraphQLite\Annotations\Parameter;
use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController;
use TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\MeController;
use TheCodingMachine\GraphQLite\FieldsBuilder;
use TheCodingMachine\GraphQLite\FieldsBuilderFactory;
use TheCodingMachine\GraphQLite\GraphQLException;
Expand Down Expand Up @@ -115,7 +116,6 @@ public function process(ContainerBuilder $container)
// If the security is disabled, let's remove the LoginController
if ($disableLogin === true) {
$container->removeDefinition(LoginController::class);
$container->removeDefinition(AggregateControllerQueryProviderFactory::class);
}

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

$container->findDefinition(LoginController::class)->setArgument(0, new Reference($provider));

$this->registerController(LoginController::class, $container);
}

$disableMe = false;
if ($container->getParameter('graphqlite.security.enable_me') === 'auto'
&& !$container->has(TokenStorageInterface::class)) {
$disableMe = true;
}
if ($container->getParameter('graphqlite.security.enable_me') === 'off') {
$disableMe = true;
}
// If the security is disabled, let's remove the LoginController
if ($disableMe === true) {
$container->removeDefinition(MeController::class);
}

if ($container->getParameter('graphqlite.security.enable_me') === 'on') {
if (!$container->has(TokenStorageInterface::class)) {
throw new GraphQLException('In order to enable the "me" query (via the graphqlite.security.enable_me parameter), you need to install the security bundle.');
}
}

if ($disableMe === false) {
$this->registerController(MeController::class, $container);
}

// Perf improvement: let's remove the AggregateControllerQueryProviderFactory if it is empty.
if (empty($container->findDefinition(AggregateControllerQueryProviderFactory::class)->getArgument(0))) {
$container->removeDefinition(AggregateControllerQueryProviderFactory::class);
}


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

private function registerController(string $controllerClassName, ContainerBuilder $container): void
{
$aggregateQueryProvider = $container->findDefinition(AggregateControllerQueryProviderFactory::class);
$controllersList = $aggregateQueryProvider->getArgument(0);
$controllersList[] = $controllerClassName;
$aggregateQueryProvider->setArgument(0, $controllersList);
}

/**
* Register a method call on SchemaFactory for each tagged service, passing the service in parameter.
*
Expand Down
2 changes: 2 additions & 0 deletions DependencyInjection/GraphqliteExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ public function load(array $configs, ContainerBuilder $container)
}

$enableLogin = $configs[0]['security']['enable_login'] ?? 'auto';
$enableMe = $configs[0]['security']['enable_me'] ?? 'auto';

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

$loader->load('graphqlite.xml');
Expand Down
12 changes: 11 additions & 1 deletion Resources/config/container/graphqlite.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@

<service id="TheCodingMachine\GraphQLite\AggregateControllerQueryProviderFactory">
<argument type="collection">
<argument>TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController</argument>
</argument>
<tag name="graphql.queryprovider_factory" />
</service>
Expand Down Expand Up @@ -77,5 +76,16 @@
<service id="TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController" public="true">
<argument key="$firewallName">%graphqlite.security.firewall_name%</argument>
</service>

<service id="TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\MeController" public="true" />

<service id="TheCodingMachine\Graphqlite\Bundle\Types\UserType" public="true"/>

<service id="TheCodingMachine\GraphQLite\Mappers\StaticClassListTypeMapperFactory">
<argument type="collection">
<argument>TheCodingMachine\Graphqlite\Bundle\Types\UserType</argument>
</argument>
<tag name="graphql.type_mapper_factory"/>
</service>
</services>
</container>
114 changes: 111 additions & 3 deletions Tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,10 @@ public function testLoginQuery(): void

$request = Request::create('/graphql', 'POST', ['query' => '
mutation login {
login(userName: "foo", password: "bar")
login(userName: "foo", password: "bar") {
userName
roles
}
}']);

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

$this->assertSame([
'data' => [
'login' => true
'login' => [
'userName' => 'foo',
'roles' => [
'ROLE_USER'
]
]
]
], $result);
}

public function testMeQuery(): void
{
$kernel = new GraphqliteTestingKernel();
$kernel->boot();

$session = new Session(new MockArraySessionStorage());
$container = $kernel->getContainer();
$container->set('session', $session);

$request = Request::create('/graphql', 'POST', ['query' => '
{
me {
userName
roles
}
}
']);

$response = $kernel->handle($request);

$result = json_decode($response->getContent(), true);

$this->assertSame([
'data' => [
'me' => [
'userName' => 'anon.',
'roles' => [],
]
]
], $result);

}

public function testNoLoginNoSessionQuery(): void
Expand All @@ -241,7 +282,9 @@ public function testNoLoginNoSessionQuery(): void

$request = Request::create('/graphql', 'POST', ['query' => '
mutation login {
login(userName: "foo", password: "bar")
login(userName: "foo", password: "bar") {
userName
}
}']);

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

public function testForceMeNoSecurity(): void
{
$kernel = new GraphqliteTestingKernel(false, 'off', false, 'on');
$this->expectException(GraphQLException::class);
$this->expectExceptionMessage('In order to enable the "me" query (via the graphqlite.security.enable_me parameter), you need to install the security bundle.');
$kernel->boot();
}

public function testForceLoginNoSecurity(): void
{
$kernel = new GraphqliteTestingKernel(true, 'on', false);
Expand All @@ -267,6 +318,63 @@ public function testForceLoginNoSecurity(): void
$kernel->boot();
}

/*public function testAutoMeNoSecurity(): void
{
$kernel = new GraphqliteTestingKernel(true, null, false);
$kernel->boot();
$session = new Session(new MockArraySessionStorage());
$container = $kernel->getContainer();
$container->set('session', $session);
$request = Request::create('/graphql', 'POST', ['query' => '
{
me {
userName
roles
}
}
']);
$response = $kernel->handle($request);
$result = json_decode($response->getContent(), true);
$this->assertSame([
'data' => [
'me' => [
'userName' => 'anon.',
'roles' => [],
]
]
], $result);
}*/

public function testAllOff(): void
{
$kernel = new GraphqliteTestingKernel(true, 'off', true, 'off');
$kernel->boot();

$session = new Session(new MockArraySessionStorage());
$container = $kernel->getContainer();
$container->set('session', $session);

$request = Request::create('/graphql', 'POST', ['query' => '
{
me {
userName
roles
}
}
']);

$response = $kernel->handle($request);

$result = json_decode($response->getContent(), true);

$this->assertSame('Cannot query field "me" on type "Query".', $result['errors'][0]['message']);
}

private function logIn(ContainerInterface $container)
{
// put a token into the storage so the final calls can function
Expand Down
13 changes: 12 additions & 1 deletion Tests/GraphqliteTestingKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ class GraphqliteTestingKernel extends Kernel
* @var bool
*/
private $enableSecurity;
/**
* @var string|null
*/
private $enableMe;

public function __construct(bool $enableSession = true, ?string $enableLogin = null, bool $enableSecurity = true)
public function __construct(bool $enableSession = true, ?string $enableLogin = null, bool $enableSecurity = true, ?string $enableMe = null)
{
parent::__construct('test', true);
$this->enableSession = $enableSession;
$this->enableLogin = $enableLogin;
$this->enableSecurity = $enableSecurity;
$this->enableMe = $enableMe;
}

public function registerBundles()
Expand Down Expand Up @@ -115,6 +120,12 @@ public function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
];
}

if ($this->enableMe) {
$graphqliteConf['security'] = [
'enable_me' => $this->enableMe,
];
}

$container->loadFromExtension('graphqlite', $graphqliteConf);
});
$confDir = $this->getProjectDir().'/Tests/Fixtures/config';
Expand Down

0 comments on commit d044b35

Please sign in to comment.