Skip to content

Commit

Permalink
Merge 56e46ef into 8c44ae3
Browse files Browse the repository at this point in the history
  • Loading branch information
moufmouf committed Jul 12, 2019
2 parents 8c44ae3 + 56e46ef commit 4557519
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 20 deletions.
10 changes: 8 additions & 2 deletions Controller/GraphQL/InvalidUserPasswordException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
namespace TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL;


class InvalidUserPasswordException
{
use Exception;
use TheCodingMachine\GraphQLite\GraphQLException;

class InvalidUserPasswordException extends GraphQLException
{
public static function create(Exception $previous = null)
{
return new self('The provided user / password is incorrect.', 401, $previous);
}
}
95 changes: 92 additions & 3 deletions Controller/GraphQL/LoginController.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,99 @@
<?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\UserProviderInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use TheCodingMachine\GraphQLite\Annotations\Query;

class LoginController
{

}
/**
* @var UserProviderInterface
*/
private $userProvider;
/**
* @var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var string
*/
private $firewallName;
/**
* @var SessionInterface
*/
private $session;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;

public function __construct(UserProviderInterface $userProvider, UserPasswordEncoderInterface $passwordEncoder, TokenStorageInterface $tokenStorage, SessionInterface $session, EventDispatcherInterface $eventDispatcher, string $firewallName)
{
$this->userProvider = $userProvider;
$this->passwordEncoder = $passwordEncoder;
$this->tokenStorage = $tokenStorage;
$this->firewallName = $firewallName;
$this->session = $session;
$this->eventDispatcher = $eventDispatcher;
}

/**
* @Query()
*/
public function login(string $userName, string $password, Request $request): bool
{
try {
$user = $this->userProvider->loadUserByUsername($userName);
} catch (UsernameNotFoundException $e) {
// FIXME: should we return false instead???
throw InvalidUserPasswordException::create($e);
}

if (!$this->passwordEncoder->isPasswordValid($user, $password)) {
throw InvalidUserPasswordException::create();
}

// User and passwords are valid. Let's login!

// Handle getting or creating the user entity likely with a posted form
// The third parameter "main" can change according to the name of your firewall in security.yml
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->tokenStorage->setToken($token);

// If the firewall name is not main, then the set value would be instead:
// $this->get('session')->set('_security_XXXFIREWALLNAMEXXX', serialize($token));
$this->session->set('_security_'.$this->firewallName, serialize($token));

// Fire the login event manually
$event = new InteractiveLoginEvent($request, $token);
$this->eventDispatcher->dispatch('security.interactive_login', $event);

return true;
}

/**
* @Query()
*/
public function logout(): bool
{
$this->tokenStorage->setToken(null);

$this->session->remove('_security_'.$this->firewallName);

return true;
}
}
6 changes: 6 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public function getConfigTreeBuilder()
->booleanNode('RETHROW_UNSAFE_EXCEPTIONS')->defaultTrue()->info('Exceptions that do not implement ClientAware interface are not caught by the engine and propagated to Symfony.')->end()
->end()
->end()
->arrayNode('security')
->children()
->booleanNode('enable_login')->defaultFalse()->info('Set to true to automatically create a login/logout request')->end()
->scalarNode('firewall_name')->defaultValue('main')->info('The name of the firewall to use for login')->end()
->end()
->end()
;

return $treeBuilder;
Expand Down
14 changes: 12 additions & 2 deletions DependencyInjection/GraphqliteCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter;
use TheCodingMachine\CacheUtils\FileBoundCache;
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
use TheCodingMachine\GraphQLite\AggregateControllerQueryProviderFactory;
use TheCodingMachine\GraphQLite\AnnotationReader;
use TheCodingMachine\GraphQLite\Annotations\AbstractRequest;
use TheCodingMachine\GraphQLite\Annotations\Autowire;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Mutation;
use TheCodingMachine\GraphQLite\Annotations\Parameter;
use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController;
use TheCodingMachine\GraphQLite\FieldsBuilder;
use TheCodingMachine\GraphQLite\FieldsBuilderFactory;
use TheCodingMachine\GraphQLite\GraphQLException;
Expand Down Expand Up @@ -88,16 +90,22 @@ public function process(ContainerBuilder $container)

$schemaFactory = $container->getDefinition(SchemaFactory::class);

// If the security is disabled, let's remove the LoginController
if ($container->getParameter('graphqlite.security.enable_login') === false) {
$container->removeDefinition(LoginController::class);
$container->removeDefinition(AggregateControllerQueryProviderFactory::class);
}

foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isAbstract() || $definition->getClass() === null) {
continue;
}
$class = $definition->getClass();
foreach ($controllersNamespaces as $controllersNamespace) {
/* foreach ($controllersNamespaces as $controllersNamespace) {
if (strpos($class, $controllersNamespace) === 0) {
$definition->addTag('graphql.annotated.controller');
}
}
}*/

foreach ($typesNamespaces as $typesNamespace) {
if (strpos($class, $typesNamespace) === 0) {
Expand Down Expand Up @@ -165,10 +173,12 @@ public function process(ContainerBuilder $container)

// Register graphql.queryprovider
$this->mapAdderToTag('graphql.queryprovider', 'addQueryProvider', $container, $schemaFactory);
$this->mapAdderToTag('graphql.queryprovider_factory', 'addQueryProviderFactory', $container, $schemaFactory);
$this->mapAdderToTag('graphql.root_type_mapper', 'addRootTypeMapper', $container, $schemaFactory);
$this->mapAdderToTag('graphql.parameter_mapper', 'addParameterMapper', $container, $schemaFactory);
$this->mapAdderToTag('graphql.field_middleware', 'addFieldMiddleware', $container, $schemaFactory);
$this->mapAdderToTag('graphql.type_mapper', 'addTypeMapper', $container, $schemaFactory);
$this->mapAdderToTag('graphql.type_mapper_factory', 'addTypeMapperFactory', $container, $schemaFactory);
}

/**
Expand Down
4 changes: 4 additions & 0 deletions DependencyInjection/GraphqliteExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ public function load(array $configs, ContainerBuilder $container)
$namespaceType = [];
}

$enableLogin = $configs[0]['security']['enable_login'] ?? false;

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

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

Expand Down
18 changes: 16 additions & 2 deletions Resources/config/container/graphqlite.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
/>
</service>

<service id="TheCodingMachine\GraphQLite\AggregateControllerQueryProviderFactory">
<argument type="collection">
<argument>TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController</argument>
</argument>
<tag name="graphql.queryprovider_factory" />
</service>

<service id="GraphQL\Type\Schema" alias="TheCodingMachine\GraphQLite\Schema" />


Expand Down Expand Up @@ -62,6 +69,13 @@
<service id="TheCodingMachine\Graphqlite\Bundle\Mappers\RequestParameterMapper">
<tag name="graphql.parameter_mapper"/>
</service>
</services>

</container>
<service id="TheCodingMachine\Graphqlite\Bundle\Mappers\RequestParameterMapper">
<tag name="graphql.parameter_mapper"/>
</service>

<service id="TheCodingMachine\Graphqlite\Bundle\Controller\GraphQL\LoginController" public="true">
<argument key="$firewallName">%graphqlite.security.firewall_name%</argument>
</service>
</services>
</container>
3 changes: 3 additions & 0 deletions Tests/Fixtures/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ services:

someService:
class: stdClass

Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler:
class: Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
#App\Controller\:
Expand Down
48 changes: 43 additions & 5 deletions Tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class FunctionalTest extends TestCase
{
public function testServiceWiring(): void
{
$kernel = new GraphqliteTestingKernel('test', true);
$kernel = new GraphqliteTestingKernel();
$kernel->boot();
$container = $kernel->getContainer();

Expand Down Expand Up @@ -70,7 +70,7 @@ public function testServiceWiring(): void

public function testServiceAutowiring(): void
{
$kernel = new GraphqliteTestingKernel('test', true);
$kernel = new GraphqliteTestingKernel();
$kernel->boot();
$container = $kernel->getContainer();

Expand Down Expand Up @@ -100,7 +100,7 @@ public function testServiceAutowiring(): void

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

$request = Request::create('/graphql', 'GET', ['query' => '
Expand Down Expand Up @@ -136,7 +136,7 @@ public function testErrors(): void

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

$request = Request::create('/graphql', 'GET', ['query' => '
Expand All @@ -157,7 +157,7 @@ public function testLoggedMiddleware(): void

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

$request = Request::create('/graphql', 'GET', ['query' => '
Expand Down Expand Up @@ -206,6 +206,44 @@ public function testInjectQuery(): void
], $result);
}

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

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

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

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

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

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

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

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

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

$this->assertSame('Cannot query field "login" 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

0 comments on commit 4557519

Please sign in to comment.