From 1bf1725a5103a86769f0e2665faf61f2c21e1228 Mon Sep 17 00:00:00 2001
From: Christian Foidl <christian@wunderwerk.io>
Date: Wed, 8 Jan 2025 15:11:51 +0100
Subject: [PATCH 1/3] Issue #3498190 by chfoidl: Implement lock to avoid token
 request race conditions

---
 src/Controller/Oauth2Token.php | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/src/Controller/Oauth2Token.php b/src/Controller/Oauth2Token.php
index 954cab8..dfbcc36 100644
--- a/src/Controller/Oauth2Token.php
+++ b/src/Controller/Oauth2Token.php
@@ -3,6 +3,7 @@
 namespace Drupal\simple_oauth\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\simple_oauth\Server\AuthorizationServerFactoryInterface;
 use GuzzleHttp\Psr7\Response;
 use League\OAuth2\Server\Exception\OAuthServerException;
@@ -40,6 +41,13 @@ class Oauth2Token extends ControllerBase {
    */
   protected ClientRepositoryInterface $clientRepository;
 
+  /**
+   * The lock backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected LockBackendInterface $lock;
+
   /**
    * The simple_oauth logger channel.
    *
@@ -56,6 +64,8 @@ class Oauth2Token extends ControllerBase {
    *   The PSR-7 converter.
    * @param \League\OAuth2\Server\Repositories\ClientRepositoryInterface $client_repository
    *   The client repository service.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend.
    * @param \Psr\Log\LoggerInterface $logger
    *   The simple_oauth logger channel.
    */
@@ -63,11 +73,13 @@ class Oauth2Token extends ControllerBase {
     AuthorizationServerFactoryInterface $authorization_server_factory,
     HttpMessageFactoryInterface $http_message_factory,
     ClientRepositoryInterface $client_repository,
+    LockBackendInterface $lock,
     LoggerInterface $logger,
   ) {
     $this->authorizationServerFactory = $authorization_server_factory;
     $this->httpMessageFactory = $http_message_factory;
     $this->clientRepository = $client_repository;
+    $this->lock = $lock;
     $this->logger = $logger;
   }
 
@@ -79,6 +91,7 @@ class Oauth2Token extends ControllerBase {
       $container->get('simple_oauth.server.authorization_server.factory'),
       $container->get('psr7.http_message_factory'),
       $container->get('simple_oauth.repositories.client'),
+      $container->get('lock'),
       $container->get('logger.channel.simple_oauth')
     );
   }
@@ -99,6 +112,17 @@ class Oauth2Token extends ControllerBase {
     $server_response = new Response();
     $client_id = $request->get('client_id');
 
+    $req_key = hash('sha256', $request->getContent());
+
+    // Try to acquire the lock.
+    while (!$this->lock->acquire($req_key)) {
+      // If we can't acquire the lock, wait for it.
+      if ($this->lock->wait($req_key, 30)) {
+        // Timeout reached after 30 seconds.
+        return new Response(408);
+      }
+    }
+
     try {
       if (empty($client_id)) {
         throw OAuthServerException::invalidRequest('client_id');
@@ -120,6 +144,10 @@ class Oauth2Token extends ControllerBase {
       );
       $response = $exception->generateHttpResponse($server_response);
     }
+    finally {
+      // Release the lock.
+      $this->lock->release($req_key);
+    }
 
     return $response;
   }
-- 
GitLab


From aa5c08f98a77ed8e5519b83d160fbc6479446353 Mon Sep 17 00:00:00 2001
From: Christian Foidl <christian@wunderwerk.io>
Date: Sat, 25 Jan 2025 13:54:13 +0100
Subject: [PATCH 2/3] Use more sophisticated lock key generation; use
 OAuthServerException for lock timeout #3498190

---
 src/Controller/Oauth2Token.php | 38 +++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/src/Controller/Oauth2Token.php b/src/Controller/Oauth2Token.php
index dfbcc36..14d9562 100644
--- a/src/Controller/Oauth2Token.php
+++ b/src/Controller/Oauth2Token.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\simple_oauth\Controller;
 
+use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\simple_oauth\Server\AuthorizationServerFactoryInterface;
@@ -112,18 +114,18 @@ class Oauth2Token extends ControllerBase {
     $server_response = new Response();
     $client_id = $request->get('client_id');
 
-    $req_key = hash('sha256', $request->getContent());
+    $lock_key = $this->createLockKey($request);
 
-    // Try to acquire the lock.
-    while (!$this->lock->acquire($req_key)) {
-      // If we can't acquire the lock, wait for it.
-      if ($this->lock->wait($req_key, 30)) {
-        // Timeout reached after 30 seconds.
-        return new Response(408);
+    try {
+      // Try to acquire the lock.
+      while (!$this->lock->acquire($lock_key)) {
+        // If we can't acquire the lock, wait for it.
+        if ($this->lock->wait($lock_key)) {
+          // Timeout reached after 30 seconds.
+          throw OAuthServerException::accessDenied('Request timed out. Could not aquire lock.');
+        }
       }
-    }
 
-    try {
       if (empty($client_id)) {
         throw OAuthServerException::invalidRequest('client_id');
       }
@@ -146,10 +148,26 @@ class Oauth2Token extends ControllerBase {
     }
     finally {
       // Release the lock.
-      $this->lock->release($req_key);
+      $this->lock->release($lock_key);
     }
 
     return $response;
   }
 
+  /**
+   * Creates a lock key for the request.
+   *
+   * The key consists of the request content, in this case the
+   * grant payload.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   *
+   * @return string
+   *   The lock key.
+   */
+  protected function createLockKey(Request $request): string {
+    return Crypt::hashBase64(Json::encode($request->request->all()));
+  }
+
 }
-- 
GitLab


From d13a881258df2f47d81e014ae6cf92bd6dc3c12d Mon Sep 17 00:00:00 2001
From: Bojan Bogdanovic <info@bojanbogdanovic.nl>
Date: Mon, 27 Jan 2025 08:47:14 +0100
Subject: [PATCH 3/3] Correct spelling

---
 src/Controller/Oauth2Token.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Controller/Oauth2Token.php b/src/Controller/Oauth2Token.php
index 14d9562..c0f31e2 100644
--- a/src/Controller/Oauth2Token.php
+++ b/src/Controller/Oauth2Token.php
@@ -122,7 +122,7 @@ class Oauth2Token extends ControllerBase {
         // If we can't acquire the lock, wait for it.
         if ($this->lock->wait($lock_key)) {
           // Timeout reached after 30 seconds.
-          throw OAuthServerException::accessDenied('Request timed out. Could not aquire lock.');
+          throw OAuthServerException::accessDenied('Request timed out. Could not acquire lock.');
         }
       }
 
-- 
GitLab