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,
+ []
+ );
+ }
+}