Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenID Connect #114

Open
wants to merge 59 commits into
base: v2.x
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
db82e08
Add support for the "authorization_code" grant type
ajgarlag Jan 16, 2019
b4e41c5
Add some integration tests
ajgarlag Jan 18, 2019
34d6e4e
Add final integration tests
ajgarlag Jan 21, 2019
6aace21
Use an event to resolve authorization request
ajgarlag Jan 21, 2019
4e8a508
Minor fixes
ajgarlag Jan 22, 2019
b1b67e8
Remove unused dependencies
ajgarlag Jan 22, 2019
a79d41e
Use ScopeConverter to convert scopes
ajgarlag Jan 22, 2019
87ab8cc
Improve naming and semantics
ajgarlag Jan 24, 2019
c89cfb8
Adds "OpenID Connect" support
ajgarlag Jan 22, 2019
84a2a5a
Move the authorization check into an EventListener
ajgarlag Feb 7, 2019
c2ca3f5
Remove authorization check
ajgarlag Feb 7, 2019
2e4c9c7
Fix README
ajgarlag Feb 7, 2019
7e4d68a
Merge remote-tracking branch 'ajgarlag/feature/authorization_code'
MichaelKubovic Feb 7, 2019
074a27e
Merge remote-tracking branch 'ajgarlag/feature/openid-connect'
MichaelKubovic Feb 7, 2019
345c0b6
Rename `decisionUri` to `resolutionUri`
ajgarlag Feb 8, 2019
98720dd
Merge remote-tracking branch 'ajgarlag/feature/authorization_code'
MichaelKubovic Feb 8, 2019
a8dd556
Add return types to AuthorizationRequestResolveEvent
ajgarlag Feb 11, 2019
cc4a780
refactored authoriztion controller to independent event listeners
MichaelKubovic Feb 12, 2019
050fc3a
refactored authoriztion controller to independent event listeners
MichaelKubovic Feb 12, 2019
2754317
adds authorization strategy configuration
MichaelKubovic Feb 13, 2019
b2c1108
Merge branch 'feature/authorization_code' into develop
MichaelKubovic Feb 13, 2019
fd2f6f8
configure openid connect authentiction listener
MichaelKubovic Feb 13, 2019
39e479f
Use Security class instead of TokenStorageInterface
ajgarlag Feb 19, 2019
c060226
Use integers instead of booleans values for authorization resolution.
ajgarlag Feb 19, 2019
b6f385a
Test authorization code request with faked redirect uri
ajgarlag Feb 19, 2019
d45129d
Rename AuthCode to AuthorizationCode
ajgarlag Feb 20, 2019
f3ada2f
Method AbstractIntegrationTest::handleAuthorizeRequest returns Respon…
ajgarlag Feb 20, 2019
6f9e8b0
Rename test methods
ajgarlag Mar 8, 2019
57fe8d2
Update the AuthorizationRequestResolveEvent workflow
ajgarlag Mar 8, 2019
ddb8720
Merge remote-tracking branch 'ajgarlag/feature/authorization_code' in…
MichaelKubovic Apr 9, 2019
f109684
Merge branch 'feature/authorization_code' into develop
MichaelKubovic Apr 10, 2019
fbafd72
configure authentication listener properly
MichaelKubovic Apr 10, 2019
882eeb3
use PSR response interface, reorder methods
MichaelKubovic Apr 10, 2019
7b5cf26
configure consent strategy correctly
MichaelKubovic Apr 10, 2019
475ffb2
configure authentication listener properly
MichaelKubovic Apr 10, 2019
afc1b50
use PSR response interface, reorder methods
MichaelKubovic Apr 10, 2019
6c27e70
configure consent strategy correctly
MichaelKubovic Apr 10, 2019
4527719
reordered methods
MichaelKubovic Apr 10, 2019
0f12aa3
Merge branch 'feature/authorization_code' into develop
MichaelKubovic Apr 10, 2019
f385bd1
get definition by id instead of alias
MichaelKubovic May 9, 2019
5348443
Made authentication listener extendable, fixed service configurations
MichaelKubovic May 10, 2019
6e4e68f
Merge branch 'master' into develop
MichaelKubovic May 20, 2019
f48d907
make properties protected to make them accesible for subclasses
MichaelKubovic May 21, 2019
79cc5cf
adds nonce support
MichaelKubovic May 27, 2019
de9f8de
Merge remote-tracking branch 'upstream/master' into develop
MichaelKubovic May 27, 2019
6075e5e
Merge remote-tracking branch 'upstream/master' into develop
MichaelKubovic Jun 21, 2019
4d84913
brings nonce back after merge
MichaelKubovic Jun 21, 2019
dffde0c
approveAuthorization method name changed in upstream
MichaelKubovic Jun 21, 2019
c8712d4
Merge pull request #97 from trikoder/v2.x
spideyfusion Aug 13, 2019
2158c1d
Merge remote-tracking branch 'upstream/v2.x' into develop
MichaelKubovic Sep 7, 2019
1a44f60
fixes after merge
MichaelKubovic Sep 7, 2019
1f8524b
Merge remote-tracking branch 'upstream/master' into develop
MichaelKubovic Sep 30, 2019
8d8fe9f
Reorganized listeners to follow the upstream structure
MichaelKubovic Oct 9, 2019
8c0573b
fixed the use of listener
Tayfun74 Dec 27, 2019
667ed6e
redirect fix
Tayfun74 Jan 2, 2020
ceaedb9
Merge pull request #2 from Tayfun74/develop
MichaelKubovic Jan 2, 2020
766c513
Upgrade lib to V3
Mar 20, 2020
22ba0a2
Update event-dispatcher-constracts constraints
Mar 24, 2020
c28b7e7
Merge pull request #3 from MichaelKubovic/upgrade-to-v3
MichaelKubovic May 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for the "authorization_code" grant type
ajgarlag committed Jan 20, 2019
commit db82e08e3353ffd0d26d5ad6e0b118fc6c7d5374
74 changes: 74 additions & 0 deletions Controller/AuthorizationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Controller;

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\User;
use Zend\Diactoros\Response;

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

/**
* @var AuthorizationCheckerInterface
*/
private $authorizationChecker;

/**
* @var TokenStorageInterface
*/
private $tokenStorage;

public function __construct(AuthorizationServer $server, AuthorizationCheckerInterface $authorizationChecker, TokenStorageInterface $tokenStorage)
{
$this->server = $server;
$this->authorizationChecker = $authorizationChecker;
$this->tokenStorage = $tokenStorage;
}

public function indexAction(ServerRequestInterface $serverRequest): ResponseInterface
{
if (!$this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
throw new LogicException('There is no logged in user. Review your security config to protect this endpoint.');
}

$serverResponse = new Response();

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

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

private function getUserEntity(): User
{
$token = $this->tokenStorage->getToken();
if (null === $token) {
throw new LogicException('There is no security token available. Review your security config to protect endpoint.');
}

$user = $token->getUser();
$username = $user instanceof UserInterface ? $user->getUsername() : (string) $user;

$userEntity = new User();
$userEntity->setIdentifier($username);

return $userEntity;
}
}
5 changes: 5 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
@@ -53,6 +53,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()
->end()
;

18 changes: 18 additions & 0 deletions DependencyInjection/TrikoderOAuth2Extension.php
Original file line number Diff line number Diff line change
@@ -89,6 +89,11 @@ private function configureAuthorizationServer(ContainerBuilder $container, array
new Definition(DateInterval::class, [$config['access_token_ttl']]),
]);

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

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

@@ -107,6 +112,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)
@@ -153,6 +166,11 @@ private function configureDoctrinePersistence(ContainerBuilder $container, array
->replaceArgument('$entityManager', $entityManager)
;

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

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

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, EntityTrait, TokenEntityTrait;
}
109 changes: 109 additions & 0 deletions League/Repository/AuthCodeRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

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 as AuthCodeEntity;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Model\AuthCode as AuthCodeModel;

final class AuthCodeRepository implements AuthCodeRepositoryInterface
{
/**
* @var AuthCodeManagerInterface
*/
private $authCodeManager;

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

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

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

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

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

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

$authCode = $this->buildAuthCodeModel($authCodeEntity);

$this->authCodeManager->save($authCode);
}

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

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

$authCode->revoke();

$this->authCodeManager->save($authCode);
}

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

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

return $authCode->isRevoked();
}

private function buildAuthCodeModel(AuthCodeEntity $authCodeEntity): AuthCodeModel
{
$client = $this->clientManager->find($authCodeEntity->getClient()->getIdentifier());

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

return $authCode;
}
}
12 changes: 12 additions & 0 deletions Manager/AuthCodeManagerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Manager;

use Trikoder\Bundle\OAuth2Bundle\Model\AuthCode;

interface AuthCodeManagerInterface
{
public function find(string $identifier): ?AuthCode;

public function save(AuthCode $authCode): void;
}
37 changes: 37 additions & 0 deletions Manager/Doctrine/AuthCodeManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine;

use Doctrine\ORM\EntityManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Model\AuthCode;

final class AuthCodeManager implements AuthCodeManagerInterface
{
/**
* @var EntityManagerInterface
*/
private $entityManager;

public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}

/**
* {@inheritdoc}
*/
public function find(string $identifier): ?AuthCode
{
return $this->entityManager->find(AuthCode::class, $identifier);
}

/**
* {@inheritdoc}
*/
public function save(AuthCode $authCode): void
{
$this->entityManager->persist($authCode);
$this->entityManager->flush();
}
}
24 changes: 24 additions & 0 deletions Manager/InMemory/AuthCodeManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Manager\InMemory;

use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Model\AuthCode;

final class AuthCodeManager implements AuthCodeManagerInterface
{
/**
* @var AuthCode[]
*/
private $authCodes = [];

public function find(string $identifier): ?AuthCode
{
return $this->authCodes[$identifier] ?? null;
}

public function save(AuthCode $authCode): void
{
$this->authCodes[$authCode->getIdentifier()] = $authCode;
}
}
97 changes: 97 additions & 0 deletions Model/AuthCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Model;

use DateTime;

class AuthCode
{
/**
* @var string
*/
private $identifier;

/**
* @var DateTime
*/
private $expiry;

/**
* @var string|null
*/
private $userIdentifier;

/**
* @var Client
*/
private $client;

/**
* @var Scope[]
*/
private $scopes = [];

/**
* @var bool
*/
private $revoked = false;

public function __construct(
string $identifier,
DateTime $expiry,
Client $client,
?string $userIdentifier,
array $scopes)
{
$this->identifier = $identifier;
$this->expiry = $expiry;
$this->client = $client;
$this->userIdentifier = $userIdentifier;
$this->scopes = $scopes;
}

public function __toString(): string
{
return $this->getIdentifier();
}

public function getIdentifier(): string
{
return $this->identifier;
}

public function getExpiryDateTime(): DateTime
{
return $this->expiry;
}

public function getUserIdentifier(): ?string
{
return $this->userIdentifier;
}

public function getClient(): Client
{
return $this->client;
}

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

public function isRevoked(): bool
{
return $this->revoked;
}

public function revoke(): self
{
$this->revoked = true;

return $this;
}
}
3 changes: 2 additions & 1 deletion OAuth2Grants.php
Original file line number Diff line number Diff line change
@@ -41,11 +41,12 @@ final class OAuth2Grants

public static function has(string $grant): bool
{
// TODO: Add support for "authorization_code" and "implicit" grant types.
// TODO: Add support for "implicit" grant type.
return \in_array($grant, [
self::CLIENT_CREDENTIALS,
self::PASSWORD,
self::REFRESH_TOKEN,
self::AUTHORIZATION_CODE,
]);
}
}
19 changes: 19 additions & 0 deletions Resources/config/doctrine/model/AuthCode.orm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="Trikoder\Bundle\OAuth2Bundle\Model\AuthCode" table="oauth2_auth_code">
<id name="identifier" type="string" length="80">
<options>
<option name="fixed">true</option>
</options>
</id>
<field name="expiry" type="datetime" />
<field name="userIdentifier" type="string" length="128" nullable="true" />
<field name="scopes" type="oauth2_scope" nullable="true" />
<field name="revoked" type="boolean" />
<many-to-one field="client" target-entity="Trikoder\Bundle\OAuth2Bundle\Model\Client">
<join-column name="client" referenced-column-name="identifier" nullable="false" />
</many-to-one>
</entity>
</doctrine-mapping>
1 change: 1 addition & 0 deletions Resources/config/routes.xml
Original file line number Diff line number Diff line change
@@ -3,5 +3,6 @@
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="oauth2_authorize" path="/authorize" controller="trikoder.oauth2.controller.authorization_controller:indexAction" methods="GET|POST" />
<route id="oauth2_token" path="/token" controller="trikoder.oauth2.controller.token_controller:indexAction" methods="GET|POST" />
</routes>
21 changes: 21 additions & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
@@ -29,6 +29,11 @@
<argument type="service" id="Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface" />
<argument type="service" id="Symfony\Component\EventDispatcher\EventDispatcherInterface" />
</service>
<service id="trikoder.oauth2.league.repository.auth_code_repository" class="Trikoder\Bundle\OAuth2Bundle\League\Repository\AuthCodeRepository">
<argument type="service" id="Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface" />
<argument type="service" id="Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface" />
<argument type="service" id="trikoder.oauth2.converter.scope_converter" />
</service>

<!-- Security layer -->
<service id="trikoder.oauth2.security.authentication.provider.oauth2_provider" class="Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Provider\OAuth2Provider">
@@ -41,6 +46,9 @@
<argument type="service" id="security.authentication.manager" />
<argument type="service" id="sensio_framework_extra.psr7.http_message_factory" />
</service>
<service id="trikoder.oauth2.security.authorization.voter.always_approve_auth_request_voter" class="Trikoder\Bundle\OAuth2Bundle\Security\Authorization\Voter\AlwaysApproveAuthRequestVoter">
<tag name="security.voter" />
</service>

<!-- The league authorization server -->
<service id="league.oauth2.server.authorization_server" class="League\OAuth2\Server\AuthorizationServer">
@@ -66,6 +74,19 @@
<service id="league.oauth2.server.grant.refresh_token_grant" class="League\OAuth2\Server\Grant\RefreshTokenGrant">
<argument type="service" id="trikoder.oauth2.league.repository.refresh_token_repository" />
</service>
<service id="league.oauth2.server.grant.auth_code_grant" class="League\OAuth2\Server\Grant\AuthCodeGrant" >
<argument type="service" id="trikoder.oauth2.league.repository.auth_code_repository" />
<argument type="service" id="trikoder.oauth2.league.repository.refresh_token_repository" />
<argument key="$authCodeTTL" />
</service>

<!-- Authorization controller -->
<service id="trikoder.oauth2.controller.authorization_controller" class="Trikoder\Bundle\OAuth2Bundle\Controller\AuthorizationController">
<argument type="service" id="League\OAuth2\Server\AuthorizationServer" />
<argument type="service" id="Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface" />
<argument type="service" id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" />
<tag name="controller.service_arguments" />
</service>

<!-- Token controller -->
<service id="trikoder.oauth2.controller.token_controller" class="Trikoder\Bundle\OAuth2Bundle\Controller\TokenController">
4 changes: 4 additions & 0 deletions Resources/config/storage/doctrine.xml
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface" alias="trikoder.oauth2.manager.doctrine.access_token_manager" />
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface" alias="trikoder.oauth2.manager.doctrine.refresh_token_manager" />
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface" alias="trikoder.oauth2.manager.in_memory.scope_manager" />
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface" alias="trikoder.oauth2.manager.doctrine.auth_code_manager" />

<!-- Services -->
<service id="trikoder.oauth2.manager.doctrine.client_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\ClientManager">
@@ -19,5 +20,8 @@
<argument key="$entityManager" />
</service>
<service id="trikoder.oauth2.manager.in_memory.scope_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\ScopeManager" />
<service id="trikoder.oauth2.manager.doctrine.auth_code_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\AuthCodeManager">
<argument key="$entityManager" />
</service>
</services>
</container>
2 changes: 2 additions & 0 deletions Resources/config/storage/in_memory.xml
Original file line number Diff line number Diff line change
@@ -7,11 +7,13 @@
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface" alias="trikoder.oauth2.manager.in_memory.access_token_manager" />
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface" alias="trikoder.oauth2.manager.in_memory.refresh_token_manager" />
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface" alias="trikoder.oauth2.manager.in_memory.scope_manager" />
<service id="Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface" alias="trikoder.oauth2.manager.in_memory.auth_code_manager" />

<!-- Services -->
<service id="trikoder.oauth2.manager.in_memory.client_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\ClientManager" />
<service id="trikoder.oauth2.manager.in_memory.access_token_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\AccessTokenManager" />
<service id="trikoder.oauth2.manager.in_memory.refresh_token_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\RefreshTokenManager" />
<service id="trikoder.oauth2.manager.in_memory.scope_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\ScopeManager" />
<service id="trikoder.oauth2.manager.in_memory.auth_code_manager" class="Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\AuthCodeManager" />
</services>
</container>
20 changes: 20 additions & 0 deletions Security/Authorization/Voter/AlwaysApproveAuthRequestVoter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Security\Authorization\Voter;

use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

class AlwaysApproveAuthRequestVoter extends Voter
{
protected function supports($attribute, $subject): bool
{
return $attribute instanceof AuthorizationRequest;
}

protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
return Voter::ACCESS_GRANTED;
}
}
4 changes: 3 additions & 1 deletion Tests/Acceptance/AbstractAcceptanceTest.php
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface;
@@ -36,7 +37,8 @@ protected function setUp()
$this->client->getContainer()->get(ScopeManagerInterface::class),
$this->client->getContainer()->get(ClientManagerInterface::class),
$this->client->getContainer()->get(AccessTokenManagerInterface::class),
$this->client->getContainer()->get(RefreshTokenManagerInterface::class)
$this->client->getContainer()->get(RefreshTokenManagerInterface::class),
$this->client->getContainer()->get(AuthCodeManagerInterface::class)
);
}
}
68 changes: 68 additions & 0 deletions Tests/Acceptance/AuthorizationEndpointTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Trikoder\Bundle\OAuth2Bundle\Tests\Acceptance;

use DateTime;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\FixtureFactory;

final class AuthorizationEndpointTest extends AbstractAcceptanceTest
{
public function testSuccessfulCodeRequest()
{
timecop_freeze(new DateTime());

$this->client->request(
'GET',
'/authorize',
[
'client_id' => FixtureFactory::FIXTURE_CLIENT_FIRST,
'response_type' => 'code',
'state' => 'foobar',
],
[],
[
'PHP_AUTH_USER' => FixtureFactory::FIXTURE_USER,
'PHP_AUTH_PW' => FixtureFactory::FIXTURE_PASSWORD,
]
);

timecop_return();

$response = $this->client->getResponse();

$this->assertSame(302, $response->getStatusCode());
$redirectUri = $response->headers->get('Location');

$this->assertStringStartsWith(FixtureFactory::FIXTURE_CLIENT_FIRST_REDIRECT_URI, $redirectUri);
$query = [];
parse_str(parse_url($redirectUri, PHP_URL_QUERY), $query);
$this->assertArrayHasKey('code', $query);
$this->assertArrayHasKey('state', $query);
$this->assertEquals('foobar', $query['state']);
}

public function testFailedAuthorizeRequest()
{
$this->client->request(
'GET',
'/authorize',
[],
[],
[
'PHP_AUTH_USER' => FixtureFactory::FIXTURE_USER,
'PHP_AUTH_PW' => FixtureFactory::FIXTURE_PASSWORD,
]
);

$response = $this->client->getResponse();

$this->assertSame(400, $response->getStatusCode());
$this->assertSame('application/json', $response->headers->get('Content-Type'));

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

$this->assertSame('unsupported_grant_type', $jsonResponse['error']);
$this->assertSame('The authorization grant type is not supported by the authorization server.', $jsonResponse['message']);
$this->assertSame('Check that all required parameters have been provided', $jsonResponse['hint']);
}
}
32 changes: 32 additions & 0 deletions Tests/Acceptance/TokenEndpointTest.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

use DateTime;
use Trikoder\Bundle\OAuth2Bundle\Event\UserResolveEvent;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Tests\Fixtures\FixtureFactory;
use Trikoder\Bundle\OAuth2Bundle\Tests\TestHelper;
@@ -99,6 +100,37 @@ public function testSuccessfulRefreshTokenRequest()
$this->assertNotEmpty($jsonResponse['refresh_token']);
}

public function testSuccessfulAuthorizationCodeRequest()
{
$authCode = $this->client
->getContainer()
->get(AuthCodeManagerInterface::class)
->find(FixtureFactory::FIXTURE_AUTH_CODE);

timecop_freeze(new DateTime());

$this->client->request('POST', '/token', [
'client_id' => 'foo',
'client_secret' => 'secret',
'grant_type' => 'authorization_code',
'redirect_uri' => 'https://example.org/oauth2/redirect-uri',
'code' => TestHelper::generateEncryptedAuthCodePayload($authCode),
]);

timecop_return();

$response = $this->client->getResponse();

$this->assertSame(200, $response->getStatusCode());
$this->assertSame('application/json; charset=UTF-8', $response->headers->get('Content-Type'));

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

$this->assertSame('Bearer', $jsonResponse['token_type']);
$this->assertSame(3600, $jsonResponse['expires_in']);
$this->assertNotEmpty($jsonResponse['access_token']);
}

public function testFailedTokenRequest()
{
$this->client->request('GET', '/token');
36 changes: 34 additions & 2 deletions Tests/Fixtures/FixtureFactory.php
Original file line number Diff line number Diff line change
@@ -4,12 +4,15 @@

use DateTime;
use Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Model\AccessToken;
use Trikoder\Bundle\OAuth2Bundle\Model\AuthCode;
use Trikoder\Bundle\OAuth2Bundle\Model\Client;
use Trikoder\Bundle\OAuth2Bundle\Model\Grant;
use Trikoder\Bundle\OAuth2Bundle\Model\RedirectUri;
use Trikoder\Bundle\OAuth2Bundle\Model\RefreshToken;
use Trikoder\Bundle\OAuth2Bundle\Model\Scope;

@@ -28,21 +31,27 @@ final class FixtureFactory
public const FIXTURE_REFRESH_TOKEN_EXPIRED = '3b3db453a137debb7b5f445c971bef18bb4f045d272a66a27054a0713096d2a8377679d204495c88';
public const FIXTURE_REFRESH_TOKEN_REVOKED = '63641841630c2e4d747e0f9ebe12ee04424e322874b8e68ef69fd58f1899ef70beb09733e23928a6';

public const FIXTURE_AUTH_CODE = '0aa70e8152259988b3c8e9e8cff604019bb986eb226bd126da189829b95a2be631e2506042064e12';

public const FIXTURE_CLIENT_FIRST = 'foo';
public const FIXTURE_CLIENT_SECOND = 'bar';
public const FIXTURE_CLIENT_INACTIVE = 'baz_inactive';
public const FIXTURE_CLIENT_RESTRCITED_GRANTS = 'qux_restricted';

public const FIXTURE_CLIENT_FIRST_REDIRECT_URI = 'https://example.org/oauth2/redirect-uri';

public const FIXTURE_SCOPE_FIRST = 'fancy';
public const FIXTURE_SCOPE_SECOND = 'rock';

public const FIXTURE_USER = 'user';
public const FIXTURE_PASSWORD = 'pass';

public static function initializeFixtures(
ScopeManagerInterface $scopeManager,
ClientManagerInterface $clientManager,
AccessTokenManagerInterface $accessTokenManager,
RefreshTokenManagerInterface $refreshTokenManager
RefreshTokenManagerInterface $refreshTokenManager,
AuthCodeManagerInterface $authCodeManager
): void {
foreach (self::createScopes() as $scope) {
$scopeManager->save($scope);
@@ -59,6 +68,10 @@ public static function initializeFixtures(
foreach (self::createRefreshTokens($accessTokenManager) as $refreshToken) {
$refreshTokenManager->save($refreshToken);
}

foreach (self::createAuthCodes($clientManager) as $authCode) {
$authCodeManager->save($authCode);
}
}

/**
@@ -163,14 +176,33 @@ public static function createRefreshTokens(AccessTokenManagerInterface $accessTo
return $refreshTokens;
}

/**
* @return AuthCode[]
*/
public static function createAuthCodes(ClientManagerInterface $clientManager): array
{
$authCodes = [];

$authCodes[] = new AuthCode(
self::FIXTURE_AUTH_CODE,
new DateTime('+2 minute'),
$clientManager->find(self::FIXTURE_CLIENT_FIRST),
self::FIXTURE_USER,
[]
);

return $authCodes;
}

/**
* @return Client[]
*/
public static function createClients(): array
{
$clients = [];

$clients[] = new Client(self::FIXTURE_CLIENT_FIRST, 'secret');
$clients[] = (new Client(self::FIXTURE_CLIENT_FIRST, 'secret'))
->setRedirectUris(new RedirectUri(self::FIXTURE_CLIENT_FIRST_REDIRECT_URI));

$clients[] = new Client(self::FIXTURE_CLIENT_SECOND, 'top_secret');

2 changes: 1 addition & 1 deletion Tests/Fixtures/User.php
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ public function getRoles()
*/
public function getPassword()
{
return null;
return FixtureFactory::FIXTURE_PASSWORD;
}

/**
11 changes: 10 additions & 1 deletion Tests/Integration/AbstractIntegrationTest.php
Original file line number Diff line number Diff line change
@@ -27,8 +27,10 @@
use Trikoder\Bundle\OAuth2Bundle\League\Repository\ScopeRepository;
use Trikoder\Bundle\OAuth2Bundle\League\Repository\UserRepository;
use Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\AccessTokenManager;
use Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\AuthCodeManager;
use Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\ClientManager;
use Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\RefreshTokenManager;
use Trikoder\Bundle\OAuth2Bundle\Manager\InMemory\ScopeManager;
@@ -58,6 +60,11 @@ abstract class AbstractIntegrationTest extends TestCase
*/
protected $accessTokenManager;

/**
* @var AuthCodeManagerInterface
*/
protected $authCodeManager;

/**
* @var RefreshTokenManagerInterface
*/
@@ -87,13 +94,15 @@ protected function setUp()
$this->clientManager = new ClientManager();
$this->accessTokenManager = new AccessTokenManager();
$this->refreshTokenManager = new RefreshTokenManager();
$this->authCodeManager = new AuthCodeManager();
$this->eventDispatcher = new EventDispatcher();

FixtureFactory::initializeFixtures(
$this->scopeManager,
$this->clientManager,
$this->accessTokenManager,
$this->refreshTokenManager
$this->refreshTokenManager,
$this->authCodeManager
);

$scopeConverter = new ScopeConverter();
21 changes: 21 additions & 0 deletions Tests/TestHelper.php
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
use Trikoder\Bundle\OAuth2Bundle\League\Entity\Client as ClientEntity;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\Scope as ScopeEntity;
use Trikoder\Bundle\OAuth2Bundle\Model\AccessToken as AccessTokenModel;
use Trikoder\Bundle\OAuth2Bundle\Model\AuthCode as AuthCodeModel;
use Trikoder\Bundle\OAuth2Bundle\Model\RefreshToken as RefreshTokenModel;

final class TestHelper
@@ -38,6 +39,26 @@ public static function generateEncryptedPayload(RefreshTokenModel $refreshToken)
}
}

public static function generateEncryptedAuthCodePayload(AuthCodeModel $authCode): ?string
{
$payload = json_encode([
'client_id' => $authCode->getClient()->getIdentifier(),
'redirect_uri' => (string) $authCode->getClient()->getRedirectUris()[0],
'auth_code_id' => $authCode->getIdentifier(),
'scopes' => $authCode->getScopes(),
'user_id' => $authCode->getUserIdentifier(),
'expire_time' => $authCode->getExpiryDateTime()->getTimestamp(),
'code_challenge' => null,
'code_challenge_method' => null,
]);

try {
return Crypto::encryptWithPassword($payload, self::ENCRYPTION_KEY);
} catch (CryptoException $e) {
return null;
}
}

public static function generateJwtToken(AccessTokenModel $accessToken): string
{
$clientEntity = new ClientEntity();
20 changes: 20 additions & 0 deletions Tests/TestKernel.php
Original file line number Diff line number Diff line change
@@ -8,7 +8,9 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
use Symfony\Component\Security\Core\User\UserInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface;
@@ -88,6 +90,11 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa

$container->loadFromExtension('security', [
'firewalls' => [
'auth' => [
'pattern' => '^/authorize',
'stateless' => true,
'http_basic' => true,
],
'test' => [
'pattern' => '^/security-test',
'stateless' => true,
@@ -99,12 +106,16 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa
'memory' => [
'users' => [
FixtureFactory::FIXTURE_USER => [
'password' => FixtureFactory::FIXTURE_PASSWORD,
'roles' => ['ROLE_USER'],
],
],
],
],
],
'encoders' => [
UserInterface::class => 'plaintext',
],
]);

$container->loadFromExtension('sensio_framework_extra', [
@@ -194,5 +205,14 @@ public function process(ContainerBuilder $container)
)
->setPublic(true)
;

$container
->getDefinition(
$container
->getAlias(AuthCodeManagerInterface::class)
->setPublic(true)
)
->setPublic(true)
;
}
}