Skip to content

Commit

Permalink
Merge pull request #28 from moufmouf/login_action
Browse files Browse the repository at this point in the history
Adding login action
  • Loading branch information
moufmouf committed Jul 14, 2019
2 parents 8c44ae3 + 5cd8d7a commit 22b829f
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 35 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);
}
}
96 changes: 93 additions & 3 deletions Controller/GraphQL/LoginController.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,100 @@
<?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\Mutation;
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;
}

/**
* @Mutation()
*/
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($event, 'security.interactive_login');

return true;
}

/**
* @Mutation()
*/
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()
->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()
->scalarNode('firewall_name')->defaultValue('main')->info('The name of the firewall to use for login')->end()
->end()
->end()
;

return $treeBuilder;
Expand Down
51 changes: 45 additions & 6 deletions DependencyInjection/GraphqliteCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use TheCodingMachine\CacheUtils\ClassBoundCache;
use TheCodingMachine\CacheUtils\ClassBoundCacheContract;
use TheCodingMachine\CacheUtils\ClassBoundCacheContractInterface;
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 @@ -80,24 +87,54 @@ public function process(ContainerBuilder $container)
$typesNamespaces = $container->getParameter('graphqlite.namespace.types');

// 2 seconds of TTL in environment mode. Otherwise, let's cache forever!

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

$env = $container->getParameter('kernel.environment');
$globTtl = null;
if ($env === 'dev') {
$globTtl = 2;
if ($env === 'prod') {
$schemaFactory->addMethodCall('prodMode');
} elseif ($env === 'dev') {
$schemaFactory->addMethodCall('devMode');
}

$disableLogin = false;
if ($container->getParameter('graphqlite.security.enable_login') === 'auto'
&& (!$container->has(UserProviderInterface::class) ||
!$container->has(UserPasswordEncoderInterface::class) ||
!$container->has(TokenStorageInterface::class) ||
!$container->has(SessionInterface::class)
)) {
$disableLogin = true;
}
if ($container->getParameter('graphqlite.security.enable_login') === 'off') {
$disableLogin = true;
}
// 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') {
if (!$container->has(SessionInterface::class)) {
throw new GraphQLException('In order to enable the login/logout mutations (via the graphqlite.security.enable_login parameter), you need to enable session support (via the "framework.session.enabled" config parameter).');
}
if (!$container->has(UserPasswordEncoderInterface::class) || !$container->has(TokenStorageInterface::class) || !$container->has(UserProviderInterface::class)) {
throw new GraphQLException('In order to enable the login/logout mutations (via the graphqlite.security.enable_login parameter), you need to install the security bundle. Please be sure to correctly configure the user provider (in the security.providers configuration settings)');
}
}

$schemaFactory = $container->getDefinition(SchemaFactory::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 +202,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'] ?? 'auto';

$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

0 comments on commit 22b829f

Please sign in to comment.