diff --git a/DependencyInjection/TrikoderOAuth2Extension.php b/DependencyInjection/TrikoderOAuth2Extension.php index ed595dc7..9ca20cec 100644 --- a/DependencyInjection/TrikoderOAuth2Extension.php +++ b/DependencyInjection/TrikoderOAuth2Extension.php @@ -41,6 +41,7 @@ use Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface; use Trikoder\Bundle\OAuth2Bundle\Model\Scope as ScopeModel; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; +use Trikoder\Bundle\OAuth2Bundle\Service\CredentialsRevoker\DoctrineCredentialsRevoker; final class TrikoderOAuth2Extension extends Extension implements PrependExtensionInterface, CompilerPassInterface { @@ -268,6 +269,11 @@ private function configureDoctrinePersistence(ContainerBuilder $container, array ->replaceArgument('$entityManager', $entityManager) ; + $container + ->getDefinition(DoctrineCredentialsRevoker::class) + ->replaceArgument('$entityManager', $entityManager) + ; + $container->setParameter('trikoder.oauth2.persistence.doctrine.enabled', true); $container->setParameter('trikoder.oauth2.persistence.doctrine.manager', $entityManagerName); } diff --git a/Manager/ClientManagerInterface.php b/Manager/ClientManagerInterface.php index 71e6d34a..35169386 100644 --- a/Manager/ClientManagerInterface.php +++ b/Manager/ClientManagerInterface.php @@ -5,11 +5,10 @@ namespace Trikoder\Bundle\OAuth2Bundle\Manager; use Trikoder\Bundle\OAuth2Bundle\Model\Client; +use Trikoder\Bundle\OAuth2Bundle\Service\ClientFinderInterface; -interface ClientManagerInterface +interface ClientManagerInterface extends ClientFinderInterface { - public function find(string $identifier): ?Client; - public function save(Client $client): void; public function remove(Client $client): void; diff --git a/Resources/config/doctrine/model/AccessToken.orm.xml b/Resources/config/doctrine/model/AccessToken.orm.xml index 192c6d9c..10886306 100644 --- a/Resources/config/doctrine/model/AccessToken.orm.xml +++ b/Resources/config/doctrine/model/AccessToken.orm.xml @@ -17,5 +17,8 @@ + + + diff --git a/Resources/config/doctrine/model/AuthorizationCode.orm.xml b/Resources/config/doctrine/model/AuthorizationCode.orm.xml index f3930596..58eafedd 100644 --- a/Resources/config/doctrine/model/AuthorizationCode.orm.xml +++ b/Resources/config/doctrine/model/AuthorizationCode.orm.xml @@ -17,5 +17,8 @@ + + + diff --git a/Resources/config/services.xml b/Resources/config/services.xml index d2260595..0a137bf0 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -4,6 +4,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + diff --git a/Resources/config/storage/doctrine.xml b/Resources/config/storage/doctrine.xml index 90a7f617..a3b2f732 100644 --- a/Resources/config/storage/doctrine.xml +++ b/Resources/config/storage/doctrine.xml @@ -12,6 +12,11 @@ + + + + + diff --git a/Service/ClientFinderInterface.php b/Service/ClientFinderInterface.php new file mode 100644 index 00000000..8e298cdf --- /dev/null +++ b/Service/ClientFinderInterface.php @@ -0,0 +1,15 @@ +entityManager = $entityManager; + } + + public function revokeCredentialsForUser(UserInterface $user): void + { + $userIdentifier = $user->getUsername(); + + $this->entityManager->createQueryBuilder() + ->update(AccessToken::class, 'at') + ->set('at.revoked', true) + ->where('at.userIdentifier = :userIdentifier') + ->setParameter('userIdentifier', $userIdentifier) + ->getQuery() + ->execute(); + + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder + ->update(RefreshToken::class, 'rt') + ->set('rt.revoked', true) + ->where($queryBuilder->expr()->in( + 'rt.accessToken', + $this->entityManager->createQueryBuilder() + ->select('at.identifier') + ->from(AccessToken::class, 'at') + ->where('at.userIdentifier = :userIdentifier') + ->getDQL() + )) + ->setParameter('userIdentifier', $userIdentifier) + ->getQuery() + ->execute(); + + $this->entityManager->createQueryBuilder() + ->update(AuthorizationCode::class, 'ac') + ->set('ac.revoked', true) + ->where('ac.userIdentifier = :userIdentifier') + ->setParameter('userIdentifier', $userIdentifier) + ->getQuery() + ->execute(); + } + + public function revokeCredentialsForClient(Client $client): void + { + $doctrineClient = $this->entityManager + ->getRepository(Client::class) + ->findOneBy(['identifier' => $client->getIdentifier()]); + + $this->entityManager->createQueryBuilder() + ->update(AccessToken::class, 'at') + ->set('at.revoked', true) + ->where('at.client = :client') + ->setParameter('client', $doctrineClient) + ->getQuery() + ->execute(); + + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder->update(RefreshToken::class, 'rt') + ->set('rt.revoked', true) + ->where($queryBuilder->expr()->in( + 'rt.accessToken', + $this->entityManager->createQueryBuilder() + ->select('at.identifier') + ->from(AccessToken::class, 'at') + ->where('at.client = :client') + ->getDQL() + )) + ->setParameter('client', $doctrineClient) + ->getQuery() + ->execute(); + + $this->entityManager->createQueryBuilder() + ->update(AuthorizationCode::class, 'ac') + ->set('ac.revoked', true) + ->where('ac.client = :client') + ->setParameter('client', $doctrineClient) + ->getQuery() + ->execute(); + } +} diff --git a/Service/CredentialsRevokerInterface.php b/Service/CredentialsRevokerInterface.php new file mode 100644 index 00000000..65cd7d9e --- /dev/null +++ b/Service/CredentialsRevokerInterface.php @@ -0,0 +1,21 @@ +client->getContainer()->get('doctrine.orm.entity_manager'); + + $em->persist($client = new Client('client', 'secret')); + + $authCode = $this->buildAuthCode('foo', '+1 minute', $client, $userIdentifier); + $accessToken = $this->buildAccessToken('bar', '+1 minute', $client, $userIdentifier); + $refreshToken = $this->buildRefreshToken('baz', '+1 minute', $accessToken); + + $em->persist($authCode); + $em->persist($accessToken); + $em->persist($refreshToken); + $em->flush(); + + $revoker = new DoctrineCredentialsRevoker($em); + + $revoker->revokeCredentialsForUser(FixtureFactory::createUser()); + + $em->refresh($authCode); + $em->refresh($accessToken); + $em->refresh($refreshToken); + + $this->assertTrue($authCode->isRevoked()); + $this->assertTrue($accessToken->isRevoked()); + $this->assertTrue($refreshToken->isRevoked()); + } + + public function testRevokesAllCredentialsForClient(): void + { + /** @var EntityManagerInterface $em */ + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); + + $em->persist($client = new Client('acme', 'secret')); + + $authCode = $this->buildAuthCode('foo', '+1 minute', $client, 'john'); + $accessToken = $this->buildAccessToken('bar', '+1 minute', $client); + $refreshToken = $this->buildRefreshToken('baz', '+1 minute', $accessToken); + + $em->persist($authCode); + $em->persist($accessToken); + $em->persist($refreshToken); + $em->flush(); + + $revoker = new DoctrineCredentialsRevoker($em); + + $revoker->revokeCredentialsForClient($client); + + $em->refresh($authCode); + $em->refresh($accessToken); + $em->refresh($refreshToken); + + $this->assertTrue($authCode->isRevoked()); + $this->assertTrue($accessToken->isRevoked()); + $this->assertTrue($refreshToken->isRevoked()); + } + + private function buildRefreshToken(string $identifier, string $modify, AccessToken $accessToken): RefreshToken + { + return new RefreshToken( + $identifier, + new DateTimeImmutable($modify), + $accessToken + ); + } + + private function buildAccessToken(string $identifier, string $modify, Client $client, ?string $userIdentifier = null): AccessToken + { + return new AccessToken( + $identifier, + new DateTimeImmutable($modify), + $client, + $userIdentifier, + [] + ); + } + + private function buildAuthCode(string $identifier, string $modify, Client $client, ?string $userIdentifier = null): AuthorizationCode + { + return new AuthorizationCode( + $identifier, + new DateTimeImmutable($modify), + $client, + $userIdentifier, + [] + ); + } +}