diff --git a/app/Repositories/DoctrineOAuth2ClientRepository.php b/app/Repositories/DoctrineOAuth2ClientRepository.php index df278592..2a2e8fe9 100644 --- a/app/Repositories/DoctrineOAuth2ClientRepository.php +++ b/app/Repositories/DoctrineOAuth2ClientRepository.php @@ -122,7 +122,7 @@ public function getClientByIdCacheable(string $client_id, bool $withResourceServ $q = $qb->getQuery(); $q->useQueryCache(true); - $q->enableResultCache(600, 'client_by_id_'.$client_id); // TTL 10 min + $q->enableResultCache(600, 'client_by_id_'.md5($client_id)); // TTL 10 min $q->setHint(Query::HINT_READ_ONLY, true); return $q->getOneOrNullResult(); diff --git a/tests/OAuth2ClientCacheTest.php b/tests/OAuth2ClientCacheTest.php new file mode 100644 index 00000000..8e282d4a --- /dev/null +++ b/tests/OAuth2ClientCacheTest.php @@ -0,0 +1,72 @@ +repo = App::make(IClientRepository::class); + } + + /** + * A client ID containing '/' must be retrievable without throwing + * InvalidArgumentException due to PSR-6 reserved characters in the + * cache key. A second call must also succeed, confirming the cached + * entry is readable. + */ + public function testGetClientByIdCacheableWithReservedCharactersIsRetrievable(): void + { + $client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + + // First call: fetches from DB and writes the hashed cache key. + $client = $this->repo->getClientByIdCacheable($client_id); + $this->assertNotNull($client); + $this->assertEquals($client_id, $client->getClientId()); + + // Second call: reads from result cache — must not throw on the hashed key. + $client_cached = $this->repo->getClientByIdCacheable($client_id); + $this->assertNotNull($client_cached); + $this->assertEquals($client_id, $client_cached->getClientId()); + } + + /** + * Two distinct client IDs that both contain '/' must hash to different + * cache keys, so that each call returns the correct client and the + * md5 hashing does not accidentally collapse them to the same entry. + */ + public function testGetClientByIdCacheableDistinctReservedCharacterIdsReturnDifferentClients(): void + { + $client_id_a = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_id_b = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x2.openstack.client'; + + $client_a = $this->repo->getClientByIdCacheable($client_id_a); + $client_b = $this->repo->getClientByIdCacheable($client_id_b); + + $this->assertNotNull($client_a); + $this->assertNotNull($client_b); + $this->assertEquals($client_id_a, $client_a->getClientId()); + $this->assertEquals($client_id_b, $client_b->getClientId()); + $this->assertNotEquals($client_a->getClientId(), $client_b->getClientId()); + } +}