Skip to content

Commit

Permalink
Merge pull request #18 from ajgarlag/feature/authorization_code
Browse files Browse the repository at this point in the history
Add support for the "authorization_code" grant type
  • Loading branch information
spideyfusion authored Jun 3, 2019
2 parents 28d2a42 + 87475b4 commit a61114a
Show file tree
Hide file tree
Showing 33 changed files with 1,428 additions and 44 deletions.
74 changes: 74 additions & 0 deletions Controller/AuthorizationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Controller;

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Trikoder\Bundle\OAuth2Bundle\Converter\UserConverter;
use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEvent;
use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEventFactory;
use Trikoder\Bundle\OAuth2Bundle\OAuth2Events;

final class AuthorizationController
{
/**
* @var AuthorizationServer
*/
private $server;

/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;

/**
* @var AuthorizationRequestResolveEventFactory
*/
private $eventFactory;

/**
* @var UserConverter
*/
private $userConverter;

public function __construct(AuthorizationServer $server, EventDispatcherInterface $eventDispatcher, AuthorizationRequestResolveEventFactory $eventFactory, UserConverter $userConverter)
{
$this->server = $server;
$this->eventDispatcher = $eventDispatcher;
$this->eventFactory = $eventFactory;
$this->userConverter = $userConverter;
}

public function indexAction(ServerRequestInterface $serverRequest, ResponseFactoryInterface $responseFactory): ResponseInterface
{
$serverResponse = $responseFactory->createResponse();

try {
$authRequest = $this->server->validateAuthorizationRequest($serverRequest);

/** @var AuthorizationRequestResolveEvent $event */
$event = $this->eventDispatcher->dispatch(
OAuth2Events::AUTHORIZATION_REQUEST_RESOLVE,
$this->eventFactory->fromAuthorizationRequest($authRequest)
);

$authRequest->setUser($this->userConverter->toLeague($event->getUser()));

if ($event->hasResponse()) {
return $event->getResponse();
}

$authRequest->setAuthorizationApproved($event->getAuthorizationResolution());

return $this->server->completeAuthorizationRequest($authRequest, $serverResponse);
} catch (OAuthServerException $e) {
return $e->generateHttpResponse($serverResponse);
}
}
}
22 changes: 22 additions & 0 deletions Converter/UserConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Converter;

use League\OAuth2\Server\Entities\UserEntityInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\User;

final class UserConverter
{
public function toLeague(UserInterface $user = null): UserEntityInterface
{
$userEntity = new User();
if ($user instanceof UserInterface) {
$userEntity->setIdentifier($user->getUsername());
}

return $userEntity;
}
}
9 changes: 9 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ private function createAuthorizationServerNode(): NodeDefinition
->cannotBeEmpty()
->defaultValue('P1M')
->end()
->scalarNode('auth_code_ttl')
->info("How long the issued auth code should be valid for.\nThe value should be a valid interval: http://php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters")
->cannotBeEmpty()
->defaultValue('PT10M')
->end()
->booleanNode('enable_client_credentials_grant')
->info('Whether to enable the client credentials grant')
->defaultTrue()
Expand All @@ -71,6 +76,10 @@ private function createAuthorizationServerNode(): NodeDefinition
->info('Whether to enable the refresh token grant')
->defaultTrue()
->end()
->booleanNode('enable_auth_code_grant')
->info('Whether to enable the authorization code grant')
->defaultTrue()
->end()
->end()
;

Expand Down
20 changes: 20 additions & 0 deletions DependencyInjection/TrikoderOAuth2Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ private function configureAuthorizationServer(ContainerBuilder $container, array
]);
}

if ($config['enable_auth_code_grant']) {
$authorizationServer->addMethodCall('enableGrantType', [
new Reference('league.oauth2.server.grant.auth_code_grant'),
new Definition(DateInterval::class, [$config['access_token_ttl']]),
]);
}

$this->configureGrants($container, $config);
}

Expand All @@ -182,6 +189,14 @@ private function configureGrants(ContainerBuilder $container, array $config): vo
new Definition(DateInterval::class, [$config['refresh_token_ttl']]),
])
;

$container
->getDefinition('league.oauth2.server.grant.auth_code_grant')
->replaceArgument('$authCodeTTL', new Definition(DateInterval::class, [$config['auth_code_ttl']]))
->addMethodCall('setRefreshTokenTTL', [
new Definition(DateInterval::class, [$config['refresh_token_ttl']]),
])
;
}

private function configurePersistence(LoaderInterface $loader, ContainerBuilder $container, array $config)
Expand Down Expand Up @@ -228,6 +243,11 @@ private function configureDoctrinePersistence(ContainerBuilder $container, array
->replaceArgument('$entityManager', $entityManager)
;

$container
->getDefinition('trikoder.oauth2.manager.doctrine.authorization_code_manager')
->replaceArgument('$entityManager', $entityManager)
;

$container->setParameter('trikoder.oauth2.persistence.doctrine.enabled', true);
$container->setParameter('trikoder.oauth2.persistence.doctrine.manager', $entityManagerName);
}
Expand Down
159 changes: 159 additions & 0 deletions Event/AuthorizationRequestResolveEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Event;

use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\User\UserInterface;
use Trikoder\Bundle\OAuth2Bundle\Converter\ScopeConverter;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Model\Client;
use Trikoder\Bundle\OAuth2Bundle\Model\Scope;

final class AuthorizationRequestResolveEvent extends Event
{
public const AUTHORIZATION_APPROVED = true;
public const AUTHORIZATION_DENIED = false;

/**
* @var AuthorizationRequest
*/
private $authorizationRequest;

/**
* @var ScopeConverter
*/
private $scopeConverter;

/**
* @var ClientManagerInterface
*/
private $clientManager;

/**
* @var bool
*/
private $authorizationResolution = self::AUTHORIZATION_DENIED;

/**
* @var ResponseInterface|null
*/
private $response;

/**
* @var UserInterface|null
*/
private $user;

public function __construct(AuthorizationRequest $authorizationRequest, ScopeConverter $scopeConverter, ClientManagerInterface $clientManager)
{
$this->authorizationRequest = $authorizationRequest;
$this->scopeConverter = $scopeConverter;
$this->clientManager = $clientManager;
}

public function getAuthorizationResolution(): bool
{
return $this->authorizationResolution;
}

public function resolveAuthorization(bool $authorizationResolution): self
{
$this->authorizationResolution = $authorizationResolution;
$this->response = null;
$this->stopPropagation();

return $this;
}

public function hasResponse(): bool
{
return $this->response instanceof ResponseInterface;
}

public function getResponse(): ResponseInterface
{
if (!$this->hasResponse()) {
throw new LogicException('There is no response. You should call "hasResponse" to check if the response exists.');
}

return $this->response;
}

public function setResponse(ResponseInterface $response): self
{
$this->response = $response;
$this->stopPropagation();

return $this;
}

public function getGrantTypeId(): string
{
return $this->authorizationRequest->getGrantTypeId();
}

public function getClient(): Client
{
$identifier = $this->authorizationRequest->getClient()->getIdentifier();
$client = $this->clientManager->find($identifier);

if (null === $client) {
throw new RuntimeException(sprintf('No client found for the given identifier "%s".', $identifier));
}

return $client;
}

public function getUser(): ?UserInterface
{
return $this->user;
}

public function setUser(?UserInterface $user): self
{
$this->user = $user;

return $this;
}

/**
* @return Scope[]
*/
public function getScopes(): array
{
return $this->scopeConverter->toDomainArray(
$this->authorizationRequest->getScopes()
);
}

public function isAuthorizationApproved(): bool
{
return $this->authorizationRequest->isAuthorizationApproved();
}

public function getRedirectUri(): ?string
{
return $this->authorizationRequest->getRedirectUri();
}

public function getState(): ?string
{
return $this->authorizationRequest->getState();
}

public function getCodeChallenge(): string
{
return $this->authorizationRequest->getCodeChallenge();
}

public function getCodeChallengeMethod(): string
{
return $this->authorizationRequest->getCodeChallengeMethod();
}
}
33 changes: 33 additions & 0 deletions Event/AuthorizationRequestResolveEventFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Event;

use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use Trikoder\Bundle\OAuth2Bundle\Converter\ScopeConverter;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;

class AuthorizationRequestResolveEventFactory
{
/**
* @var ScopeConverter
*/
private $scopeConverter;

/**
* @var ClientManagerInterface
*/
private $clientManager;

public function __construct(ScopeConverter $scopeConverter, ClientManagerInterface $clientManager)
{
$this->scopeConverter = $scopeConverter;
$this->clientManager = $clientManager;
}

public function fromAuthorizationRequest(AuthorizationRequest $authorizationRequest): AuthorizationRequestResolveEvent
{
return new AuthorizationRequestResolveEvent($authorizationRequest, $this->scopeConverter, $this->clientManager);
}
}
38 changes: 38 additions & 0 deletions EventListener/AuthorizationRequestUserResolvingListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\EventListener;

use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEvent;

/**
* Class AuthorizationRequestUserResolvingListener
*
* Listener sets currently authenticated user to authorization request context
*/
class AuthorizationRequestUserResolvingListener
{
/**
* @var Security
*/
private $security;

/**
* AuthorizationRequestUserResolvingListener constructor.
*/
public function __construct(Security $security)
{
$this->security = $security;
}

public function onAuthorizationRequest(AuthorizationRequestResolveEvent $event)
{
$user = $this->security->getUser();
if ($user instanceof UserInterface) {
$event->setUser($user);
}
}
}
Loading

0 comments on commit a61114a

Please sign in to comment.