Skip to content

Commit

Permalink
New listener for api authentication (both oauth and cookie works !)
Browse files Browse the repository at this point in the history
  • Loading branch information
ngodfraind committed Jan 12, 2016
1 parent 55aa72f commit 5305300
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 2 deletions.
4 changes: 4 additions & 0 deletions ClarolineCoreBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Claroline\CoreBundle\DependencyInjection\Compiler\ImportersConfigPass;
use Claroline\CoreBundle\DependencyInjection\Compiler\RichTextFormatterConfigPass;
use Claroline\CoreBundle\DependencyInjection\Compiler\RuleConstraintsConfigPass;
use Claroline\CoreBundle\DependencyInjection\Factory\ApiFactory;
use FOS\OAuthServerBundle\FOSOAuthServerBundle;
use IDCI\Bundle\ExporterBundle\IDCIExporterBundle;
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
Expand All @@ -39,6 +40,9 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new RichTextFormatterConfigPass());
$container->addCompilerPass(new DoctrineEntityListenerPass());
$container->addCompilerPass(new RuleConstraintsConfigPass());

$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new ApiFactory());
}

public function supports($environment)
Expand Down
52 changes: 52 additions & 0 deletions DependencyInjection/Factory/ApiFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Claroline\CoreBundle\DependencyInjection\Factory;

use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;

class ApiFactory implements SecurityFactoryInterface
{
/**
* {@inheritdoc}
*/
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.fos_oauth_server.'. $id;
$container
->setDefinition($providerId, new DefinitionDecorator('fos_oauth_server.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;

$listenerId = 'security.authentication.listener.fos_oauth_server.'. $id;
$container->setDefinition($listenerId, new DefinitionDecorator('claroline.core_bundle.library.security.authentication.claroline_api_listener'));

return array($providerId, $listenerId, 'fos_oauth_server.security.entry_point');
}

/**
* {@inheritdoc}
*/
public function getPosition()
{
return 'pre_auth';
}

/**
* {@inheritdoc}
*/
public function getKey()
{
return 'claro_api';
}

/**
* {@inheritdoc}
*/
public function addConfiguration(NodeDefinition $node)
{
}
}
155 changes: 155 additions & 0 deletions Library/Security/Authentication/ClarolineApiListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php

/*
* This file is part of the FOSOAuthServerBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Claroline\CoreBundle\Library\Security\Authentication;

use FOS\OAuthServerBundle\Security\Authentication\Token\OAuthToken;
use OAuth2\OAuth2;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider;
use JMS\DiExtraBundle\Annotation as DI;

/**
* OAuthListener class.
* This class is pretty much copied from oauthserverbundle. We use it to override what we need (and easy debug)
*
* @DI\Service("claroline.core_bundle.library.security.authentication.claroline_api_listener")
*/
class ClarolineApiListener implements ListenerInterface
{
/**
* @var \Symfony\Component\Security\Core\SecurityContextInterface
*/
protected $securityContext;

/**
* @var \Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface
*/
protected $authenticationManager;

/**
* @var \OAuth2\OAuth2
*/
protected $serverService;

/**
* @DI\InjectParams({
* "securityContext" = @DI\Inject("security.context"),
* "authenticationManager" = @DI\Inject("security.authentication.manager"),
* "serverService" = @DI\Inject("fos_oauth_server.server"),
* "userProvider" = @DI\Inject("security.user.provider.concrete.user_db")
* })
*/
public function __construct(
SecurityContextInterface $securityContext,
AuthenticationManagerInterface $authenticationManager,
OAuth2 $serverService,
EntityUserProvider $userProvider
)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
$this->serverService = $serverService;
//always the same, copied from Symfony\Component\Security\Http\Firewall\ContextListener
$this->sessionKey = '_security_main';
$this->userProvider = $userProvider;
}

/**
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event The event.
*/
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();

if (null === $oauthToken = $this->serverService->getBearerToken($event->getRequest(), true)) {
//if it's null, then we try to regular authentication...
$token = $this->handleCookie($event);

if ($token) {
$this->securityContext->setToken($token);
return;
}
}

$token = new OAuthToken();
$token->setToken($oauthToken);
$returnValue = $this->authenticationManager->authenticate($token);

try {
$returnValue = $this->authenticationManager->authenticate($token);

if ($returnValue instanceof TokenInterface) {
return $this->securityContext->setToken($returnValue);
}

if ($returnValue instanceof Response) {
return $event->setResponse($returnValue);
}
} catch (AuthenticationException $e) {
if (null !== $p = $e->getPrevious()) {
$event->setResponse($p->getHttpResponse());
}
}
}

public function handleCookie(GetResponseEvent $event)
{
$request = $event->getRequest();
$session = $request->hasPreviousSession() ? $request->getSession() : null;

if (!$session) return;

$token = $session->get($this->sessionKey);
$token = unserialize($token);

if ($token instanceof TokenInterface) {
$token = $this->refreshUser($token);
} elseif (null !== $token) {
$token = null;
}

return $token;
}

/**
* Refreshes the user by reloading it from the user provider.
* This method was copied from always the same, copied from Symfony\Component\Security\Http\Firewall\ContextListener
*
* @param TokenInterface $token
*
* @return TokenInterface|null
*
* @throws \RuntimeException
*/
protected function refreshUser(TokenInterface $token)
{
$user = $token->getUser();

if (!$user instanceof UserInterface) {
return;
}

$refreshedUser = $this->userProvider->refreshUser($user);
$token->setUser($refreshedUser);

return $token;
}
}
4 changes: 2 additions & 2 deletions Resources/config/suggested/security_prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ security:

api:
pattern: ^/api
#fos_oauth: true
claro_api: true
#stateless: true
security: false
security: true

main:
pattern: ^/
Expand Down

0 comments on commit 5305300

Please sign in to comment.