Skip to content

Commit

Permalink
Add support for the "authorization_code" grant type
Browse files Browse the repository at this point in the history
  • Loading branch information
ajgarlag committed May 24, 2019
1 parent 7ad80d0 commit d74318b
Show file tree
Hide file tree
Showing 29 changed files with 1,200 additions and 37 deletions.
80 changes: 80 additions & 0 deletions Controller/AuthorizationController.php
@@ -0,0 +1,80 @@
<?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 Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEvent;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\User;
use Trikoder\Bundle\OAuth2Bundle\OAuth2Events;

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

/**
* @var Security
*/
private $security;

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

public function __construct(AuthorizationServer $server, Security $security, EventDispatcherInterface $eventDispatcher)
{
$this->server = $server;
$this->security = $security;
$this->eventDispatcher = $eventDispatcher;
}

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

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

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

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

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

return $this->server->completeAuthorizationRequest($authRequest, $serverResponse);
} catch (OAuthServerException $e) {
return $e->generateHttpResponse($serverResponse);
}
}

private function getUserEntity(): User
{
$userEntity = new User();

$user = $this->security->getUser();
if ($user instanceof UserInterface) {
$userEntity->setIdentifier($user->getUsername());
}

return $userEntity;
}
}
9 changes: 9 additions & 0 deletions DependencyInjection/Configuration.php
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
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
117 changes: 117 additions & 0 deletions Event/AuthorizationRequestResolveEvent.php
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\Event;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\EventDispatcher\Event;

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

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

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

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

public function __construct(AuthorizationRequest $authorizationRequest)
{
$this->authorizationRequest = $authorizationRequest;
}

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

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

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): void
{
$this->response = $response;
}

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

public function getClient(): ClientEntityInterface
{
return $this->authorizationRequest->getClient();
}

public function getUser(): UserEntityInterface
{
return $this->authorizationRequest->getUser();
}

/**
* @return ScopeEntityInterface[]
*/
public function getScopes(): array
{
return $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();
}
}
17 changes: 17 additions & 0 deletions League/Entity/AuthCode.php
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\League\Entity;

use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\Traits\AuthCodeTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;

final class AuthCode implements AuthCodeEntityInterface
{
use AuthCodeTrait;
use EntityTrait;
use TokenEntityTrait;
}
111 changes: 111 additions & 0 deletions League/Repository/AuthCodeRepository.php
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Trikoder\Bundle\OAuth2Bundle\League\Repository;

use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use Trikoder\Bundle\OAuth2Bundle\Converter\ScopeConverter;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\AuthCode;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthorizationCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Model\AuthorizationCode;

final class AuthCodeRepository implements AuthCodeRepositoryInterface
{
/**
* @var AuthorizationCodeManagerInterface
*/
private $authorizationCodeManager;

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

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

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

/**
* {@inheritdoc}
*/
public function getNewAuthCode()
{
return new AuthCode();
}

/**
* {@inheritdoc}
*/
public function persistNewAuthCode(AuthCodeEntityInterface $authCode)
{
$authorizationCode = $this->authorizationCodeManager->find($authCode->getIdentifier());

if (null !== $authorizationCode) {
throw UniqueTokenIdentifierConstraintViolationException::create();
}

$authorizationCode = $this->buildAuthorizationCode($authCode);

$this->authorizationCodeManager->save($authorizationCode);
}

/**
* {@inheritdoc}
*/
public function revokeAuthCode($codeId)
{
$authorizationCode = $this->authorizationCodeManager->find($codeId);

if (null === $codeId) {
return;
}

$authorizationCode->revoke();

$this->authorizationCodeManager->save($authorizationCode);
}

/**
* {@inheritdoc}
*/
public function isAuthCodeRevoked($codeId)
{
$authorizationCode = $this->authorizationCodeManager->find($codeId);

if (null === $authorizationCode) {
return true;
}

return $authorizationCode->isRevoked();
}

private function buildAuthorizationCode(AuthCode $authCode): AuthorizationCode
{
$client = $this->clientManager->find($authCode->getClient()->getIdentifier());

$authorizationCode = new AuthorizationCode(
$authCode->getIdentifier(),
$authCode->getExpiryDateTime(),
$client,
$authCode->getUserIdentifier(),
$this->scopeConverter->toDomainArray($authCode->getScopes())
);

return $authorizationCode;
}
}

0 comments on commit d74318b

Please sign in to comment.