From 5b769901f997b63b322ced068b9ebe61bce0fa31 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Fri, 24 May 2024 09:30:53 +0200 Subject: [PATCH 01/36] add method to convert string to env enum value --- app/Domain/Integrations/Environment.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Domain/Integrations/Environment.php b/app/Domain/Integrations/Environment.php index 86f0f25b7..9af1efc6a 100644 --- a/app/Domain/Integrations/Environment.php +++ b/app/Domain/Integrations/Environment.php @@ -9,4 +9,14 @@ enum Environment: string case Acceptance = 'acc'; case Testing = 'test'; case Production = 'prod'; + + public static function fromString(string $string): self + { + return match ($string) { + 'acceptance' => self::Acceptance, + 'testing' => self::Testing, + 'production' => self::Production, + default => throw new \InvalidArgumentException(sprintf('Invalid environment: %s', $string)), + }; + } } From 0e83bb15e6d2947312f432744380711112605f4a Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Fri, 24 May 2024 09:31:20 +0200 Subject: [PATCH 02/36] move baseulr, secret, etc to Realm level --- app/Keycloak/Config.php | 15 --------- app/Keycloak/ConfigFactory.php | 40 ++++++++++++++++++++++++ app/Keycloak/KeycloakServiceProvider.php | 8 +---- app/Keycloak/Realm.php | 22 ++++++++++++- app/Keycloak/RealmCollection.php | 2 +- config/keycloak.php | 23 ++++++++++++-- 6 files changed, 83 insertions(+), 27 deletions(-) create mode 100644 app/Keycloak/ConfigFactory.php diff --git a/app/Keycloak/Config.php b/app/Keycloak/Config.php index 1cfd671f9..0b297f613 100644 --- a/app/Keycloak/Config.php +++ b/app/Keycloak/Config.php @@ -6,24 +6,9 @@ final readonly class Config { - public string $baseUrl; - public function __construct( public bool $isEnabled, // This is a feature flag - string $baseUrl, - public string $clientId, - public string $clientSecret, public RealmCollection $realms ) { - $this->baseUrl = $this->addTrailingSlash($baseUrl); - } - - private function addTrailingSlash(string $uri): string - { - if (str_ends_with($uri, '/')) { - return $uri; - } - - return $uri . '/'; } } diff --git a/app/Keycloak/ConfigFactory.php b/app/Keycloak/ConfigFactory.php new file mode 100644 index 000000000..75fd3eea5 --- /dev/null +++ b/app/Keycloak/ConfigFactory.php @@ -0,0 +1,40 @@ + $environment) { + if (empty($environment['internalName']) || empty($environment['base_url']) || empty($environment['client_id']) || empty($environment['client_secret'])) { + // If any of the fields are missing, do not create that realm. + continue; + } + + $realms->add(new Realm( + ucfirst($publicName), + $environment['internalName'], + $environment['base_url'], + $environment['client_id'], + $environment['client_secret'], + Environment::fromString($publicName) + )); + } + return $realms; + } +} diff --git a/app/Keycloak/KeycloakServiceProvider.php b/app/Keycloak/KeycloakServiceProvider.php index 34368a80b..97e2693d2 100644 --- a/app/Keycloak/KeycloakServiceProvider.php +++ b/app/Keycloak/KeycloakServiceProvider.php @@ -48,13 +48,7 @@ public function register(): void }); $this->app->singleton(Config::class, function () { - return new Config( - config('keycloak.enabled'), - config('keycloak.base_url'), - config('keycloak.client_id'), - config('keycloak.client_secret'), - RealmCollection::getRealms(), - ); + return ConfigFactory::build(); }); $this->app->singleton(ScopeConfig::class, function () { diff --git a/app/Keycloak/Realm.php b/app/Keycloak/Realm.php index a3a46e0be..edbcd554a 100644 --- a/app/Keycloak/Realm.php +++ b/app/Keycloak/Realm.php @@ -8,12 +8,32 @@ final readonly class Realm { - public function __construct(public string $internalName, public string $publicName, public Environment $environment) + public string $baseUrl; + + public function __construct( + public string $internalName, + public string $publicName, + string $baseUrl, + public string $clientId, + public string $clientSecret, + public Environment $environment) { + $this->baseUrl = $this->addTrailingSlash($baseUrl); } public static function getMasterRealm(): self { return new self('master', 'Master', Environment::Production); } + + + + private function addTrailingSlash(string $uri): string + { + if (str_ends_with($uri, '/')) { + return $uri; + } + + return $uri . '/'; + } } diff --git a/app/Keycloak/RealmCollection.php b/app/Keycloak/RealmCollection.php index 3a92d069e..766d18224 100644 --- a/app/Keycloak/RealmCollection.php +++ b/app/Keycloak/RealmCollection.php @@ -13,7 +13,7 @@ */ final class RealmCollection extends Collection { - public static function getRealms(): RealmCollection + private static function getRealms(): RealmCollection { //@todo Change this once all Realms have been configured return new self([new Realm('uitidpoc', 'Acceptance', Environment::Acceptance)]); diff --git a/config/keycloak.php b/config/keycloak.php index 0d79510c0..b96884180 100644 --- a/config/keycloak.php +++ b/config/keycloak.php @@ -4,9 +4,26 @@ return [ 'enabled' => env('KEYCLOAK_ENABLED', false), - 'base_url' => env('KEYCLOAK_BASE_URL', ''), - 'client_id' => env('KEYCLOAK_CLIENT_ID', ''), - 'client_secret' => env('KEYCLOAK_CLIENT_SECRET', ''), + 'environments' => [ + 'acceptance' => [ + 'internalName' => env('KEYCLOAK_STAG_REALM_NAME', ''), + 'base_url' => env('KEYCLOAK_STAG_BASE_URL', ''), + 'client_id' => env('KEYCLOAK_STAG_CLIENT_ID', ''), + 'client_secret' => env('KEYCLOAK_STAG_CLIENT_SECRET', ''), + ], + 'testing' => [ + 'internalName' => env('KEYCLOAK_TEST_REALM_NAME', ''), + 'base_url' => env('KEYCLOAK_TEST_BASE_URL', ''), + 'client_id' => env('KEYCLOAK_TEST_CLIENT_ID', ''), + 'client_secret' => env('KEYCLOAK_TEST_CLIENT_SECRET', ''), + ], + 'production' => [ + 'internalName' => env('KEYCLOAK_PROD_REALM_NAME', ''), + 'base_url' => env('KEYCLOAK_PROD_BASE_URL', ''), + 'client_id' => env('KEYCLOAK_PROD_CLIENT_ID', ''), + 'client_secret' => env('KEYCLOAK_PROD_CLIENT_SECRET', ''), + ], + ], 'scope' => [ 'search_api_id' => env('KEYCLOAK_SCOPE_SEARCH_API_ID', ''), 'entry_api_id' => env('KEYCLOAK_SCOPE_ENTRY_API_ID', ''), From 2c1cc9fc60a74d5311c0c968a5e8c5c4822a8d76 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Wed, 29 May 2024 15:18:03 +0200 Subject: [PATCH 03/36] Change Realm to contain the URI - phpstan is ok --- .../Integrations/Models/IntegrationModel.php | 7 +++- app/Keycloak/Client/ApiClient.php | 2 +- app/Keycloak/Client/KeycloakApiClient.php | 24 +++++++----- app/Keycloak/Client/KeycloakHttpClient.php | 16 ++++---- app/Keycloak/ConfigFactory.php | 2 +- app/Keycloak/KeycloakServiceProvider.php | 2 - app/Keycloak/Listeners/CreateClients.php | 7 +++- app/Keycloak/Listeners/UpdateClients.php | 2 +- app/Keycloak/Models/KeycloakClientModel.php | 8 +++- app/Keycloak/Realm.php | 17 +++++--- app/Keycloak/RealmCollection.php | 20 ++-------- .../EloquentKeycloakClientRepository.php | 6 +-- .../Repositories/KeycloakClientRepository.php | 2 +- .../TokenStrategy/ClientCredentials.php | 12 +++--- app/Nova/Resources/KeycloakClient.php | 11 ++++-- .../CachedKeycloakClientStatusTest.php | 6 ++- .../Keycloak/Client/KeycloakApiClientTest.php | 26 +++++-------- .../Client/KeycloakHttpClientTest.php | 30 ++++++-------- tests/Keycloak/ClientTest.php | 12 +++--- tests/Keycloak/ConfigFactory.php | 20 ++++++++++ .../IntegrationUrlConverterTest.php | 8 +++- .../Keycloak/Jobs/BlockClientHandlerTest.php | 10 +++-- .../Jobs/UnblockClientHandlerTest.php | 9 +++-- tests/Keycloak/KeycloakHttpClientFactory.php | 6 +-- tests/Keycloak/Listeners/BlockClientsTest.php | 15 +++---- .../Keycloak/Listeners/CreateClientsTest.php | 15 ++++--- .../Keycloak/Listeners/UpdateClientsTest.php | 15 +++---- tests/Keycloak/RealmFactory.php | 35 +++++++++++++++++ .../EloquentKeycloakClientRepositoryTest.php | 36 ++++++++--------- .../TokenStrategy/ClientCredentialsTest.php | 39 +++++++------------ .../Keycloak/BlockKeycloakClientGuardTest.php | 11 +++++- .../UnblockKeycloakClientGuardTest.php | 10 ++++- 32 files changed, 248 insertions(+), 193 deletions(-) create mode 100644 tests/Keycloak/ConfigFactory.php create mode 100644 tests/Keycloak/RealmFactory.php diff --git a/app/Domain/Integrations/Models/IntegrationModel.php b/app/Domain/Integrations/Models/IntegrationModel.php index 32c78d2e6..b7b27b970 100644 --- a/app/Domain/Integrations/Models/IntegrationModel.php +++ b/app/Domain/Integrations/Models/IntegrationModel.php @@ -25,8 +25,8 @@ use App\Domain\Subscriptions\Models\SubscriptionModel; use App\Insightly\Models\InsightlyMappingModel; use App\Insightly\Resources\ResourceType; +use App\Keycloak\Config; use App\Keycloak\Models\KeycloakClientModel; -use App\Keycloak\RealmCollection; use App\Models\UuidModel; use App\UiTiDv1\Models\UiTiDv1ConsumerModel; use App\UiTiDv1\UiTiDv1Environment; @@ -34,6 +34,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\App; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -266,7 +267,9 @@ public function hasMissingUiTiDv1Consumers(): bool public function hasMissingKeycloakConsumers(): bool { - return $this->keycloakClients()->count() < count(RealmCollection::getRealms()); + /** @var Config $config */ + $config = App::get('config'); + return $this->keycloakClients()->count() < $config->realms->count(); } public function toDomain(): Integration diff --git a/app/Keycloak/Client/ApiClient.php b/app/Keycloak/Client/ApiClient.php index 15d5df8c6..d87f80bcc 100644 --- a/app/Keycloak/Client/ApiClient.php +++ b/app/Keycloak/Client/ApiClient.php @@ -13,7 +13,7 @@ interface ApiClient { public function createClient(Realm $realm, Integration $integration, UuidInterface $clientId): void; - public function addScopeToClient(Realm $realm, UuidInterface $clientId, UuidInterface $scopeId): void; + public function addScopeToClient(Client $client, UuidInterface $scopeId): void; public function fetchClient(Realm $realm, Integration $integration, UuidInterface $clientId): Client; diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index ba54478a8..f9ffa6091 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -42,7 +42,8 @@ public function createClient(Realm $realm, Integration $integration, UuidInterfa sprintf('admin/realms/%s/clients', $realm->internalName), [], Json::encode(IntegrationToKeycloakClientConverter::convert($id, $integration, $clientId)) - ) + ), + $realm ); } catch (Throwable $e) { throw KeyCloakApiFailed::failedToCreateClient($e->getMessage()); @@ -69,14 +70,15 @@ public function createClient(Realm $realm, Integration $integration, UuidInterfa /** * @throws KeyCloakApiFailed */ - public function addScopeToClient(Realm $realm, UuidInterface $clientId, UuidInterface $scopeId): void + public function addScopeToClient(Client $client, UuidInterface $scopeId): void { try { $response = $this->client->sendWithBearer( new Request( 'PUT', - sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $realm->internalName, $clientId->toString(), $scopeId->toString()) - ) + sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->realm->internalName, $client->clientId->toString(), $scopeId->toString()) + ), + $client->realm ); } catch (GuzzleException $e) { throw KeyCloakApiFailed::failedToAddScopeToClient($e->getMessage()); @@ -96,7 +98,8 @@ public function deleteScopes(Client $client): void new Request( 'DELETE', sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->realm->internalName, $client->id->toString(), $scope->toString()), - ) + ), + $client->realm ); // Will throw a 404 when scope not attached to client, but this is no problem. @@ -118,8 +121,9 @@ public function fetchClient(Realm $realm, Integration $integration, UuidInterfac $response = $this->client->sendWithBearer( new Request( 'GET', - 'admin/realms/' . $realm->internalName . '/clients?' . http_build_query(['clientId' => $clientId->toString()]) - ) + sprintf('admin/realms/%s/clients/%s', $realm->internalName, $clientId->toString()) + ), + $realm ); $body = $response->getBody()->getContents(); @@ -145,7 +149,8 @@ public function fetchIsClientActive(Client $client): bool new Request( 'GET', 'admin/realms/' . $client->realm->internalName . '/clients?' . http_build_query(['clientId' => $client->clientId->toString()]) - ) + ), + $client->realm ); $body = $response->getBody()->getContents(); @@ -193,7 +198,8 @@ public function updateClient(Client $client, array $body): void 'admin/realms/' . $client->realm->internalName . '/clients/' . $client->id->toString(), [], Json::encode($body) - ) + ), + $client->realm ); if ($response->getStatusCode() !== 204) { diff --git a/app/Keycloak/Client/KeycloakHttpClient.php b/app/Keycloak/Client/KeycloakHttpClient.php index 7ff48551c..dbf3bdee5 100644 --- a/app/Keycloak/Client/KeycloakHttpClient.php +++ b/app/Keycloak/Client/KeycloakHttpClient.php @@ -4,7 +4,6 @@ namespace App\Keycloak\Client; -use App\Keycloak\Config; use App\Keycloak\Realm; use App\Keycloak\TokenStrategy\TokenStrategy; use GuzzleHttp\ClientInterface; @@ -15,17 +14,17 @@ final readonly class KeycloakHttpClient { - public function __construct(private ClientInterface $client, private Config $config, private TokenStrategy $tokenStrategy) + public function __construct(private ClientInterface $client, private TokenStrategy $tokenStrategy) { } /** * @throws GuzzleException */ - public function sendWithoutBearer(RequestInterface $request): ResponseInterface + public function sendWithoutBearer(RequestInterface $request, Realm $realm): ResponseInterface { $request = $request - ->withUri(new Uri($this->config->baseUrl . $request->getUri())); + ->withUri(new Uri($realm->baseUrl . $request->getUri())); return $this->client->send($request); } @@ -33,11 +32,14 @@ public function sendWithoutBearer(RequestInterface $request): ResponseInterface /** * @throws GuzzleException */ - public function sendWithBearer(RequestInterface $request): ResponseInterface + public function sendWithBearer(RequestInterface $request, Realm $realm): ResponseInterface { $request = $request - ->withUri(new Uri($this->config->baseUrl . $request->getUri())) - ->withAddedHeader('Authorization', 'Bearer ' . $this->tokenStrategy->fetchToken($this, Realm::getMasterRealm())); + ->withUri(new Uri($realm->baseUrl . $request->getUri())) + ->withAddedHeader( + 'Authorization', + 'Bearer ' . $this->tokenStrategy->fetchToken($this, $realm->getMasterRealm()) + ); return $this->client->send($request); } } diff --git a/app/Keycloak/ConfigFactory.php b/app/Keycloak/ConfigFactory.php index 75fd3eea5..c21da4ac8 100644 --- a/app/Keycloak/ConfigFactory.php +++ b/app/Keycloak/ConfigFactory.php @@ -6,7 +6,7 @@ use App\Domain\Integrations\Environment; -class ConfigFactory +final class ConfigFactory { public static function build(): Config { diff --git a/app/Keycloak/KeycloakServiceProvider.php b/app/Keycloak/KeycloakServiceProvider.php index 97e2693d2..3d8a890e7 100644 --- a/app/Keycloak/KeycloakServiceProvider.php +++ b/app/Keycloak/KeycloakServiceProvider.php @@ -36,10 +36,8 @@ public function register(): void return new KeycloakApiClient( new KeycloakHttpClient( new Client([RequestOptions::HTTP_ERRORS => false]), - $this->app->get(Config::class), new ClientCredentials( $this->app->get(Config::class), - $this->app->get(LoggerInterface::class) ) ), $this->app->get(ScopeConfig::class), diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index a0720a3d4..c486a853b 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -42,7 +42,10 @@ public function handleCreateClients(IntegrationCreated $event): void public function handleCreatingMissingClients(MissingClientsDetected $event): void { - $missingRealms = $this->keycloakClientRepository->getMissingRealmsByIntegrationId($event->id); + $missingRealms = $this->keycloakClientRepository->getMissingRealmsByIntegrationId( + $event->id, + $this->config->realms + ); if (count($missingRealms) === 0) { $this->logger->info($event->id . ' - already has all Keycloak clients'); @@ -83,7 +86,7 @@ private function createClientsInKeycloak(Integration $integration, RealmCollecti $this->client->createClient($realm, $integration, $clientId); $client = $this->client->fetchClient($realm, $integration, $clientId); - $this->client->addScopeToClient($realm, $client->id, $scopeId); + $this->client->addScopeToClient($client, $scopeId); $clientCollection->add($client); } catch (KeyCloakApiFailed $e) { diff --git a/app/Keycloak/Listeners/UpdateClients.php b/app/Keycloak/Listeners/UpdateClients.php index 0fac83d69..0d8cafab4 100644 --- a/app/Keycloak/Listeners/UpdateClients.php +++ b/app/Keycloak/Listeners/UpdateClients.php @@ -73,7 +73,7 @@ private function updateClient(Integration $integration, Client $keycloakClient, ) ); $this->client->deleteScopes($keycloakClient); - $this->client->addScopeToClient($keycloakClient->realm, $keycloakClient->id, $scopeId); + $this->client->addScopeToClient($keycloakClient, $scopeId); $this->logger->info('Keycloak client updated', [ 'integration_id' => $integration->id->toString(), diff --git a/app/Keycloak/Models/KeycloakClientModel.php b/app/Keycloak/Models/KeycloakClientModel.php index a7c8b0982..c91abc8ff 100644 --- a/app/Keycloak/Models/KeycloakClientModel.php +++ b/app/Keycloak/Models/KeycloakClientModel.php @@ -6,10 +6,11 @@ use App\Keycloak\Client; use App\Domain\Integrations\Models\IntegrationModel; -use App\Keycloak\RealmCollection; +use App\Keycloak\Config; use App\Models\UuidModel; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\App; use Ramsey\Uuid\Uuid; final class KeycloakClientModel extends UuidModel @@ -28,13 +29,16 @@ final class KeycloakClientModel extends UuidModel public function toDomain(): Client { + /** @var Config $config */ + $config = App::get(Config::class); + return new Client( // Trying to use magic getters for eg $this->id gives a 0 back Uuid::fromString($this->id), Uuid::fromString($this->integration_id), Uuid::fromString($this->client_id), $this->client_secret, - RealmCollection::fromPublicName($this->realm) + $config->realms->fromPublicName($this->realm) ); } diff --git a/app/Keycloak/Realm.php b/app/Keycloak/Realm.php index edbcd554a..eb54dad2a 100644 --- a/app/Keycloak/Realm.php +++ b/app/Keycloak/Realm.php @@ -16,18 +16,23 @@ public function __construct( string $baseUrl, public string $clientId, public string $clientSecret, - public Environment $environment) - { + public Environment $environment + ) { $this->baseUrl = $this->addTrailingSlash($baseUrl); } - public static function getMasterRealm(): self + public function getMasterRealm(): self { - return new self('master', 'Master', Environment::Production); + return new self( + 'master', + 'Master', + $this->baseUrl, + $this->clientId, + $this->clientSecret, + $this->environment + ); } - - private function addTrailingSlash(string $uri): string { if (str_ends_with($uri, '/')) { diff --git a/app/Keycloak/RealmCollection.php b/app/Keycloak/RealmCollection.php index 766d18224..08ab157a7 100644 --- a/app/Keycloak/RealmCollection.php +++ b/app/Keycloak/RealmCollection.php @@ -4,7 +4,6 @@ namespace App\Keycloak; -use App\Domain\Integrations\Environment; use Illuminate\Support\Collection; use InvalidArgumentException; @@ -13,35 +12,24 @@ */ final class RealmCollection extends Collection { - private static function getRealms(): RealmCollection + public function fromPublicName(string $publicName): Realm { - //@todo Change this once all Realms have been configured - return new self([new Realm('uitidpoc', 'Acceptance', Environment::Acceptance)]); - } - - public static function fromPublicName(string $publicName): Realm - { - foreach (self::getRealms() as $realm) { + foreach ($this->all() as $realm) { if ($realm->publicName === $publicName) { return $realm; } } - /** @todo remove this later once we support multiple realms, this is only needed to fix some unit tests */ - if ($publicName === Realm::getMasterRealm()->publicName) { - return Realm::getMasterRealm(); - } - throw new InvalidArgumentException('Invalid realm: ' . $publicName); } /** * @return array */ - public static function asArray(): array + public function asArray(): array { $output = []; - foreach (self::getRealms() as $realm) { + foreach ($this->all() as $realm) { $output[$realm->publicName] = $realm->publicName; } return $output; diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index 65c68a099..d2d37082c 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -74,11 +74,11 @@ public function getByIntegrationIds(array $integrationIds): array ->toArray(); } - public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): RealmCollection + public function getMissingRealmsByIntegrationId(UuidInterface $integrationId, RealmCollection $realms): RealmCollection { $clients = $this->getByIntegrationId($integrationId); - if (count($clients) === count(RealmCollection::getRealms())) { + if (count($clients) === $realms->count()) { return new RealmCollection(); } @@ -88,7 +88,7 @@ public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): R ); return new RealmCollection(array_udiff( - RealmCollection::getRealms()->toArray(), + $realms->toArray(), $existingRealms, fn (Client $t1, Client $t2) => strcmp($t1->id->toString(), $t2->id->toString()) )); diff --git a/app/Keycloak/Repositories/KeycloakClientRepository.php b/app/Keycloak/Repositories/KeycloakClientRepository.php index c87003c55..07f9bc702 100644 --- a/app/Keycloak/Repositories/KeycloakClientRepository.php +++ b/app/Keycloak/Repositories/KeycloakClientRepository.php @@ -30,5 +30,5 @@ public function getById(UuidInterface $id): Client; */ public function getByIntegrationIds(array $integrationIds): array; - public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): RealmCollection; + public function getMissingRealmsByIntegrationId(UuidInterface $integrationId, RealmCollection $realms): RealmCollection; } diff --git a/app/Keycloak/TokenStrategy/ClientCredentials.php b/app/Keycloak/TokenStrategy/ClientCredentials.php index f0238af12..3b77f13b8 100644 --- a/app/Keycloak/TokenStrategy/ClientCredentials.php +++ b/app/Keycloak/TokenStrategy/ClientCredentials.php @@ -6,7 +6,6 @@ use App\Json; use App\Keycloak\Client\KeycloakHttpClient; -use App\Keycloak\Config; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\Realm; use GuzzleHttp\Exception\GuzzleException; @@ -23,14 +22,13 @@ final class ClientCredentials implements TokenStrategy private array $accessToken = []; public function __construct( - private readonly Config $config, private readonly LoggerInterface $logger, ) { } public function fetchToken(KeycloakHttpClient $client, Realm $realm): string { - $key = $realm->internalName . $this->config->clientId; + $key = $realm->internalName . $realm->clientId; if (isset($this->accessToken[$key])) { return $this->accessToken[$key]; @@ -43,11 +41,11 @@ public function fetchToken(KeycloakHttpClient $client, Realm $realm): string ['Content-Type' => 'application/x-www-form-urlencoded'], http_build_query([ 'grant_type' => 'client_credentials', - 'client_id' => $this->config->clientId, - 'client_secret' => $this->config->clientSecret, + 'client_id' => $realm->clientId, + 'client_secret' => $realm->clientSecret, ]) ); - $response = $client->sendWithoutBearer($request); + $response = $client->sendWithoutBearer($request, $realm); } catch (GuzzleException $e) { $this->logger->error($e->getMessage()); throw KeyCloakApiFailed::couldNotFetchAccessToken($e->getMessage()); @@ -64,7 +62,7 @@ public function fetchToken(KeycloakHttpClient $client, Realm $realm): string throw KeyCloakApiFailed::unexpectedTokenResponse(); } - $this->logger->info('Fetched token for ' . $this->config->clientId . ', token starts with ' . substr($json['access_token'], 0, 6)); + $this->logger->info('Fetched token for ' . $realm->clientId . ', token starts with ' . substr($json['access_token'], 0, 6)); $this->accessToken[$key] = $json['access_token']; return $this->accessToken[$key]; diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index eedebc1c0..03ed28d8c 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -7,7 +7,6 @@ use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Config; use App\Keycloak\Models\KeycloakClientModel; -use App\Keycloak\RealmCollection; use App\Nova\ActionGuards\ActionGuard; use App\Nova\ActionGuards\Keycloak\BlockKeycloakClientGuard; use App\Nova\ActionGuards\Keycloak\UnblockKeycloakClientGuard; @@ -63,7 +62,7 @@ public function fields(NovaRequest $request): array Select::make('realm') ->readonly() ->filterable() - ->options(RealmCollection::asArray()), + ->options($this->getConfig()->realms->asArray()), Text::make('Status', function (KeycloakClientModel $model) { $client = $model->toDomain(); @@ -80,8 +79,7 @@ public function fields(NovaRequest $request): array Text::make('client_secret') ->readonly(), Text::make('Open', function (KeycloakClientModel $model) { - // I wish I could use my config object, but don't know how to get access to it from here - $baseUrl = config('keycloak.base_url'); + $baseUrl = $model->toDomain()->realm->baseUrl; return sprintf('Open in Keycloak', $model->toDomain()->getKeycloakUrl($baseUrl)); })->asHtml(), @@ -141,4 +139,9 @@ private function getKeycloakClientStatus(): CachedKeycloakClientStatus { return App::get(CachedKeycloakClientStatus::class); } + + private function getConfig(): Config + { + return App::get(Config::class); + } } diff --git a/tests/Keycloak/CachedKeycloakClientStatusTest.php b/tests/Keycloak/CachedKeycloakClientStatusTest.php index 04f000414..3547d34f7 100644 --- a/tests/Keycloak/CachedKeycloakClientStatusTest.php +++ b/tests/Keycloak/CachedKeycloakClientStatusTest.php @@ -7,7 +7,6 @@ use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\Realm; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -18,6 +17,9 @@ final class CachedKeycloakClientStatusTest extends TestCase { use CreatesMockAuth0ClusterSDK; + use ConfigFactory; + use RealmFactory; + private ApiClient&MockObject $apiClient; private CachedKeycloakClientStatus $cachedKeycloakClientStatus; private Client $client; @@ -28,7 +30,7 @@ protected function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->cachedKeycloakClientStatus = new CachedKeycloakClientStatus($this->apiClient, new NullLogger()); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Realm::getMasterRealm()); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $this->givenAcceptanceRealm()); } public function test_does_cache_layer_work(): void diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index 7e411b39a..e6d495180 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -4,14 +4,11 @@ namespace Tests\Keycloak\Client; -use App\Domain\Integrations\Environment; use App\Domain\Integrations\Integration; use App\Keycloak\Client; use App\Keycloak\Client\KeycloakApiClient; -use App\Keycloak\Config; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\Realm; -use App\Keycloak\RealmCollection; use App\Keycloak\ScopeConfig; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\Psr7\Response; @@ -20,12 +17,16 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; +use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; +use Tests\Keycloak\RealmFactory; final class KeycloakApiClientTest extends TestCase { use KeycloakHttpClientFactory; use CreatesIntegration; + use RealmFactory; + use ConfigFactory; private const INTEGRATION_ID = '824c09c0-2f3a-4fa0-bde2-8bf25c9a5b74'; private const UUID = '824c09c0-2f3a-4fa0-bde2-8bf25c9a5b74'; @@ -39,15 +40,8 @@ final class KeycloakApiClientTest extends TestCase protected function setUp(): void { - $this->realm = new Realm('uitidpoc', 'Acceptance', Environment::Acceptance); - - $this->config = new Config( - true, - 'https://keycloak.example.com/', - 'php_client', - 'a_true_secret', - new RealmCollection([$this->realm]) - ); + $this->realm = $this->givenTestRealm(); + $this->config = $this->givenKeycloakConfig($this->realm); $this->integration = $this->givenThereIsAnIntegration(Uuid::fromString(self::INTEGRATION_ID)); $this->logger = $this->createMock(LoggerInterface::class); @@ -127,7 +121,6 @@ public function test_fails_to_create_client(): void public function test_fails_to_add_scope_to_client(): void { - $clientId = Uuid::uuid4(); $scopeId = Uuid::fromString('123ae05d-1c41-40c8-8716-c4654a3bfd98'); $mock = new MockHandler([ @@ -144,9 +137,10 @@ public function test_fails_to_add_scope_to_client(): void $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_ADD_SCOPE_WITH_RESPONSE); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $this->realm); + $apiClient->addScopeToClient( - $this->realm, - $clientId, + $client, $scopeId ); } @@ -224,7 +218,7 @@ public function test_fetch_is_client_enabled(bool $enabled): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), 'my-secret', new Realm('uitidpoc', 'Acceptance', Environment::Acceptance)); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $this->givenAcceptanceRealm()); $this->assertEquals($enabled, $apiClient->fetchIsClientActive($client)); } diff --git a/tests/Keycloak/Client/KeycloakHttpClientTest.php b/tests/Keycloak/Client/KeycloakHttpClientTest.php index ea3921fb5..3ee48722f 100644 --- a/tests/Keycloak/Client/KeycloakHttpClientTest.php +++ b/tests/Keycloak/Client/KeycloakHttpClientTest.php @@ -5,9 +5,6 @@ namespace Tests\Keycloak\Client; use App\Keycloak\Client\KeycloakHttpClient; -use App\Keycloak\Config; -use App\Keycloak\Realm; -use App\Keycloak\RealmCollection; use App\Keycloak\TokenStrategy\TokenStrategy; use GuzzleHttp\ClientInterface; use GuzzleHttp\Psr7\Request; @@ -15,34 +12,31 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; +use Tests\Keycloak\ConfigFactory; +use Tests\Keycloak\RealmFactory; final class KeycloakHttpClientTest extends TestCase { + use ConfigFactory; + use RealmFactory; + public const MY_SECRET_TOKEN = 'my-secret-token'; private ClientInterface&MockObject $clientMock; - private Config $config; private TokenStrategy&MockObject $tokenStrategy; protected function setUp(): void { $this->clientMock = $this->createMock(ClientInterface::class); - $this->config = new Config( - true, - 'https://keycloak.com/api', - 'php_client', - 'dfgopopzjcvijogdrg', - RealmCollection::getRealms(), - ); $this->tokenStrategy = $this->createMock(TokenStrategy::class); } public function test_it_can_send_a_request_with_bearer(): void { - $keycloakClient = new KeycloakHttpClient($this->clientMock, $this->config, $this->tokenStrategy); + $keycloakClient = new KeycloakHttpClient($this->clientMock, $this->tokenStrategy); $this->tokenStrategy->expects($this->once()) ->method('fetchToken') - ->with($keycloakClient, Realm::getMasterRealm()) + ->with($keycloakClient, $this->givenAcceptanceRealm()) ->willReturn(self::MY_SECRET_TOKEN); $request = new Request('GET', '/endpoint'); @@ -52,12 +46,12 @@ public function test_it_can_send_a_request_with_bearer(): void ->expects($this->once()) ->method('send') ->with($this->callback(function (RequestInterface $request) { - return $request->getUri()->__toString() === $this->config->baseUrl . '/endpoint' + return $request->getUri()->__toString() === $this->givenTestRealm()->baseUrl . '/endpoint' && $request->getHeader('Authorization')[0] === 'Bearer ' . self::MY_SECRET_TOKEN; })) ->willReturn($response); - $result = $keycloakClient->sendWithBearer($request); + $result = $keycloakClient->sendWithBearer($request, $this->givenTestRealm()); $this->assertEquals('Response body', $result->getBody()->getContents()); } @@ -71,13 +65,13 @@ public function test_it_can_send_a_request_without_bearer(): void ->expects($this->once()) ->method('send') ->with($this->callback(function (RequestInterface $request) { - return $request->getUri()->__toString() === $this->config->baseUrl . '/endpoint'; + return $request->getUri()->__toString() === $this->givenTestRealm()->baseUrl . '/endpoint'; })) ->willReturn($response); - $keycloakClient = new KeycloakHttpClient($this->clientMock, $this->config, $this->tokenStrategy); + $keycloakClient = new KeycloakHttpClient($this->clientMock, $this->tokenStrategy); - $result = $keycloakClient->sendWithoutBearer($request); + $result = $keycloakClient->sendWithoutBearer($request, $this->givenTestRealm()); $this->assertEquals('Response body', $result->getBody()->getContents()); } diff --git a/tests/Keycloak/ClientTest.php b/tests/Keycloak/ClientTest.php index b3128685e..c555cba6b 100644 --- a/tests/Keycloak/ClientTest.php +++ b/tests/Keycloak/ClientTest.php @@ -5,17 +5,16 @@ namespace Tests\Keycloak; use App\Keycloak\Client; -use App\Keycloak\Realm; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; -use App\Domain\Integrations\Environment; final class ClientTest extends TestCase { + use RealmFactory; + public function test_create_from_json(): void { - $realm = new Realm('uitidpoc', 'acceptance', Environment::Acceptance); $integrationId = Uuid::uuid4(); $clientId = Uuid::uuid4(); $data = [ @@ -23,18 +22,17 @@ public function test_create_from_json(): void 'secret' => 'testSecret', ]; - $client = Client::createFromJson($realm, $integrationId, $clientId, $data); + $client = Client::createFromJson($this->givenTestRealm(), $integrationId, $clientId, $data); $this->assertEquals($data['id'], $client->id->toString()); $this->assertEquals($data['secret'], $client->clientSecret); $this->assertEquals($integrationId, $client->integrationId); $this->assertEquals($clientId, $client->clientId); - $this->assertEquals($realm, $client->realm); + $this->assertEquals($this->givenTestRealm(), $client->realm); } public function test_throws_when_missing_secret(): void { - $realm = new Realm('uitidpoc', 'acceptance', Environment::Acceptance); $integrationId = Uuid::uuid4(); $data = [ 'id' => Uuid::uuid4()->toString(), @@ -43,6 +41,6 @@ public function test_throws_when_missing_secret(): void $this->expectException(InvalidArgumentException::class); - Client::createFromJson($realm, $integrationId, Uuid::uuid4(), $data); + Client::createFromJson($this->givenTestRealm(), $integrationId, Uuid::uuid4(), $data); } } diff --git a/tests/Keycloak/ConfigFactory.php b/tests/Keycloak/ConfigFactory.php new file mode 100644 index 000000000..5c88fff7e --- /dev/null +++ b/tests/Keycloak/ConfigFactory.php @@ -0,0 +1,20 @@ +integrationId, Uuid::uuid4(), 'my-secret', - new Realm('Acc', 'Acc', Environment::Acceptance) + $this->givenAcceptanceRealm() ); } diff --git a/tests/Keycloak/Jobs/BlockClientHandlerTest.php b/tests/Keycloak/Jobs/BlockClientHandlerTest.php index fec89a1ba..dc28db82b 100644 --- a/tests/Keycloak/Jobs/BlockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/BlockClientHandlerTest.php @@ -9,16 +9,20 @@ use App\Keycloak\Events\ClientBlocked; use App\Keycloak\Jobs\BlockClient; use App\Keycloak\Jobs\BlockClientHandler; -use App\Keycloak\Realm; use App\Keycloak\Repositories\KeycloakClientRepository; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Event; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; +use Tests\Keycloak\ConfigFactory; +use Tests\Keycloak\RealmFactory; final class BlockClientHandlerTest extends TestCase { + use ConfigFactory; + use RealmFactory; + public function test_block_client_handler(): void { Event::fake(); @@ -30,7 +34,7 @@ public function test_block_client_handler(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - Realm::getMasterRealm() + $this->givenAcceptanceRealm() ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); @@ -62,7 +66,7 @@ public function test_handler_fails_when_client_does_not_exists(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - Realm::getMasterRealm() + $this->givenAcceptanceRealm() ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); diff --git a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php index 692ad09ff..706d85da5 100644 --- a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php @@ -9,18 +9,21 @@ use App\Keycloak\Events\ClientUnblocked; use App\Keycloak\Jobs\UnblockClient; use App\Keycloak\Jobs\UnblockClientHandler; -use App\Keycloak\Realm; use App\Keycloak\Repositories\KeycloakClientRepository; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Event; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; +use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; +use Tests\Keycloak\RealmFactory; final class UnblockClientHandlerTest extends TestCase { use KeycloakHttpClientFactory; + use ConfigFactory; + use RealmFactory; public function test_unblock_client_handler(): void { @@ -31,7 +34,7 @@ public function test_unblock_client_handler(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - Realm::getMasterRealm() + $this->givenAcceptanceRealm() ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); @@ -63,7 +66,7 @@ public function test_handler_fails_when_client_does_not_exists(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - Realm::getMasterRealm() + $this->givenAcceptanceRealm() ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); diff --git a/tests/Keycloak/KeycloakHttpClientFactory.php b/tests/Keycloak/KeycloakHttpClientFactory.php index 3e951bd58..9ac0edef9 100644 --- a/tests/Keycloak/KeycloakHttpClientFactory.php +++ b/tests/Keycloak/KeycloakHttpClientFactory.php @@ -21,11 +21,7 @@ protected function givenKeycloakHttpClient(LoggerInterface $logger, MockHandler { return new KeycloakHttpClient( $this->givenClient($mock), - $this->config, - new ClientCredentials( - $this->config, - $logger - ) + new ClientCredentials($logger) ); } diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index 3d7c61fd3..ef38669ec 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -9,22 +9,25 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\Config; use App\Keycloak\Listeners\BlockClients; -use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\KeycloakClientRepository; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; +use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; +use Tests\Keycloak\RealmFactory; final class BlockClientsTest extends TestCase { use CreatesIntegration; use KeycloakHttpClientFactory; + use ConfigFactory; + use RealmFactory; + private const SECRET = 'my-secret'; private Integration $integration; @@ -33,13 +36,7 @@ final class BlockClientsTest extends TestCase protected function setUp(): void { - $this->config = new Config( - true, - 'https://example.com/', - 'client_name', - self::SECRET, - RealmCollection::getRealms(), - ); + $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 844a4822f..b5ee2e327 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -21,11 +21,16 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; +use Tests\Keycloak\ConfigFactory; +use Tests\Keycloak\RealmFactory; use Tests\TestCase; final class CreateClientsTest extends TestCase { use CreatesIntegration; + use ConfigFactory; + use RealmFactory; + private const SECRET = 'my-secret'; private const SEARCH_SCOPE_ID = '06059529-74b5-422a-a499-ffcaf065d437'; @@ -40,13 +45,7 @@ final class CreateClientsTest extends TestCase protected function setUp(): void { - $this->config = new Config( - true, - 'https://example.com/', - 'client_name', - self::SECRET, - RealmCollection::getRealms(), - ); + $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); @@ -162,7 +161,7 @@ public function test_failed(): void public function test_handle_creating_missing_clients(): void { $integrationId = Uuid::uuid4(); - $missingRealms = RealmCollection::getRealms(); + $missingRealms = $this->config->realms; $integration = $this->givenThereIsAnIntegration($integrationId); $this->keycloakClientRepository->expects($this->once()) diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index a2de1ca7e..2c2e4b1c0 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -12,9 +12,7 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\Config; use App\Keycloak\Listeners\UpdateClients; -use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; @@ -24,12 +22,17 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; +use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; +use Tests\Keycloak\RealmFactory; final class UpdateClientsTest extends TestCase { use CreatesIntegration; use KeycloakHttpClientFactory; + use ConfigFactory; + use RealmFactory; + private const SECRET = 'my-secret'; private const SEARCH_SCOPE_ID = '06059529-74b5-422a-a499-ffcaf065d437'; @@ -41,13 +44,7 @@ final class UpdateClientsTest extends TestCase protected function setUp(): void { - $this->config = new Config( - true, - 'https://example.com/', - 'client_name', - self::SECRET, - RealmCollection::getRealms(), - ); + $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); diff --git a/tests/Keycloak/RealmFactory.php b/tests/Keycloak/RealmFactory.php new file mode 100644 index 000000000..deb6b7822 --- /dev/null +++ b/tests/Keycloak/RealmFactory.php @@ -0,0 +1,35 @@ +repository = new EloquentKeycloakClientRepository(); + $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); } public function test_it_can_save_one_or_more_clients(): void { - /** @var Realm $realm */ - $realm = RealmCollection::getRealms()->first(); - $integrationId = Uuid::uuid4(); $clientId = Uuid::uuid4(); $clientId2 = Uuid::uuid4(); @@ -40,37 +42,35 @@ public function test_it_can_save_one_or_more_clients(): void $integrationId, $clientId, 'client-secret-1', - Realm::getMasterRealm() + $this->givenAcceptanceRealm() ); $client2 = new Client( Uuid::uuid4(), $integrationId, $clientId2, 'client-secret-2', - $realm + $this->givenTestRealm() ); $this->repository->create($client1, $client2); - $realm = new Realm('uitidpoc', 'Acceptance', Environment::Acceptance); - $this->assertDatabaseHas('keycloak_clients', [ 'integration_id' => $integrationId->toString(), 'client_secret' => 'client-secret-1', 'client_id' => $clientId->toString(), - 'realm' => Realm::getMasterRealm()->publicName, + 'realm' => $this->givenAcceptanceRealm()->publicName, ]); $this->assertDatabaseHas('keycloak_clients', [ 'integration_id' => $integrationId->toString(), 'client_secret' => 'client-secret-2', 'client_id' => $clientId2->toString(), - 'realm' => $realm->publicName, + 'realm' => $this->givenTestRealm()->publicName, ]); } public function test_it_can_get_all_clients_for_an_integration_id(): void { /** @var Realm $realm */ - $realm = RealmCollection::getRealms()->first(); + $realm = $this->config->realms->first(); $integrationId = Uuid::uuid4(); $clientId = Uuid::uuid4(); @@ -88,7 +88,7 @@ public function test_it_can_get_all_clients_for_an_integration_id(): void $integrationId, $clientId2, 'client-secret-1', - Realm::getMasterRealm() + $this->givenAcceptanceRealm() ); $this->repository->create($client1, $client2); @@ -107,9 +107,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void $secondIntegrationId = Uuid::uuid4(); $integrationIds = [$firstIntegrationId, $secondIntegrationId]; - /** @var Realm $realm */ - $realm = RealmCollection::getRealms()->first(); - $realms = [new Realm('uitidpoc', 'Acceptance', Environment::Acceptance), $realm]; + $realms = [$this->givenAcceptanceRealm(), $this->givenTestRealm()]; $clients = []; @@ -146,7 +144,7 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void $integrationIds = [$firstIntegrationId, $secondIntegrationId]; $clients = []; - foreach (RealmCollection::getRealms() as $realm) { + foreach ($this->config->realms as $realm) { foreach ($integrationIds as $integrationId) { $count = count($clients) + 1; @@ -183,7 +181,7 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $clients = []; $missing = new RealmCollection(); - foreach (RealmCollection::getRealms() as $realm) { + foreach ($this->config->realms as $realm) { if ($missing->isEmpty()) { $missing->add($realm); continue; @@ -201,7 +199,7 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $this->assertEquals( $missing, - $this->repository->getMissingRealmsByIntegrationId($integrationId) + $this->repository->getMissingRealmsByIntegrationId($integrationId, $this->config->realms) ); } } diff --git a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php index 905532af2..1467bf952 100644 --- a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php +++ b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php @@ -4,35 +4,30 @@ namespace Tests\Keycloak\TokenStrategy; -use App\Domain\Integrations\Environment; -use App\Keycloak\Config; use App\Keycloak\Exception\KeyCloakApiFailed; -use App\Keycloak\Realm; -use App\Keycloak\RealmCollection; use App\Keycloak\TokenStrategy\ClientCredentials; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; +use Tests\Keycloak\RealmFactory; final class ClientCredentialsTest extends TestCase { use KeycloakHttpClientFactory; + use ConfigFactory; + use RealmFactory; + public const ACCESS_TOKEN = 'pqeaefosdfhbsdq'; private LoggerInterface&MockObject $logger; protected function setUp(): void { - $this->config = new Config( - true, - 'https://keycloak.example.com/', - 'php_client', - 'a_true_secret', - new RealmCollection([new Realm('uitidpoc', 'Acceptance', Environment::Acceptance)]) - ); + $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); $this->logger = $this->createMock(LoggerInterface::class); } @@ -42,13 +37,11 @@ public function test_it_returns_a_valid_token(): void new Response(200, [], json_encode(['access_token' => self::ACCESS_TOKEN], JSON_THROW_ON_ERROR)), ]); - $clientCredentials = new ClientCredentials( - $this->config, - $this->logger - ); + $clientCredentials = new ClientCredentials($this->logger); + $token = $clientCredentials->fetchToken( $this->givenKeycloakHttpClient($this->logger, $mock), - Realm::getMasterRealm() + $this->givenTestRealm() ); $this->assertEquals(self::ACCESS_TOKEN, $token); @@ -60,17 +53,14 @@ public function test_failed_because_response_did_not_contain_token(): void new Response(200, [], json_encode([], JSON_THROW_ON_ERROR)), ]); - $clientCredentials = new ClientCredentials( - $this->config, - $this->logger - ); + $clientCredentials = new ClientCredentials($this->logger); $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::UNEXPECTED_TOKEN_RESPONSE); $token = $clientCredentials->fetchToken( $this->givenKeycloakHttpClient($this->logger, $mock), - Realm::getMasterRealm() + $this->givenTestRealm() ); $this->assertEquals(self::ACCESS_TOKEN, $token); @@ -82,16 +72,13 @@ public function test_failed_because_unauthorized(): void new Response(401, [], json_encode([], JSON_THROW_ON_ERROR)), ]); - $clientCredentials = new ClientCredentials( - $this->config, - $this->logger - ); + $clientCredentials = new ClientCredentials($this->logger); $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::COULD_NOT_FETCH_ACCESS_TOKEN); $clientCredentials->fetchToken( $this->givenKeycloakHttpClient($this->logger, $mock), - Realm::getMasterRealm() + $this->givenTestRealm() ); } } diff --git a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php index 271c09356..6c935ad74 100644 --- a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php @@ -14,12 +14,18 @@ use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Auth0\CreatesMockAuth0ClusterSDK; +use Tests\Keycloak\ConfigFactory; +use Tests\Keycloak\RealmFactory; use Tests\TestCase; final class BlockKeycloakClientGuardTest extends TestCase { use CreatesMockAuth0ClusterSDK; + use ConfigFactory; + use RealmFactory; + + private ApiClient&MockObject $apiClient; private BlockKeycloakClientGuard $guard; private Client $client; @@ -30,7 +36,10 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new BlockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Realm::getMasterRealm()); + + /** @var Realm $realm */ + $realm = $this->givenKeycloakConfig($this->givenTestRealm())->realms->first(); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $realm); } #[DataProvider('dataProvider')] diff --git a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php index ae64dc609..11487c9d9 100644 --- a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php @@ -14,12 +14,17 @@ use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Auth0\CreatesMockAuth0ClusterSDK; +use Tests\Keycloak\ConfigFactory; +use Tests\Keycloak\RealmFactory; use Tests\TestCase; final class UnblockKeycloakClientGuardTest extends TestCase { use CreatesMockAuth0ClusterSDK; + use ConfigFactory; + use RealmFactory; + private ApiClient&MockObject $apiClient; private UnblockKeycloakClientGuard $guard; private Client $client; @@ -30,7 +35,10 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new UnblockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Realm::getMasterRealm()); + + /** @var Realm $realm */ + $realm = $this->givenKeycloakConfig($this->givenTestRealm())->realms->first(); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $realm); } #[DataProvider('dataProvider')] From 434df58dc9cb67e7055de49d77df0c46189aa67a Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Wed, 29 May 2024 16:17:46 +0200 Subject: [PATCH 04/36] continue work fixing tests --- app/Keycloak/ConfigFactory.php | 3 ++- app/Keycloak/Models/KeycloakClientModel.php | 1 - .../Repositories/EloquentKeycloakClientRepository.php | 3 ++- tests/Keycloak/Client/KeycloakApiClientTest.php | 2 +- tests/Keycloak/ConfigFactory.php | 7 ++++--- tests/Keycloak/Listeners/BlockClientsTest.php | 2 +- tests/Keycloak/Listeners/CreateClientsTest.php | 2 +- tests/Keycloak/Listeners/UpdateClientsTest.php | 2 +- tests/Keycloak/RealmFactory.php | 4 ++-- .../Repositories/EloquentKeycloakClientRepositoryTest.php | 2 +- tests/Keycloak/TokenStrategy/ClientCredentialsTest.php | 2 +- .../ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php | 5 +---- .../Keycloak/UnblockKeycloakClientGuardTest.php | 6 +----- 13 files changed, 18 insertions(+), 23 deletions(-) diff --git a/app/Keycloak/ConfigFactory.php b/app/Keycloak/ConfigFactory.php index c21da4ac8..a90f02915 100644 --- a/app/Keycloak/ConfigFactory.php +++ b/app/Keycloak/ConfigFactory.php @@ -27,14 +27,15 @@ private static function buildRealmCollection(): RealmCollection } $realms->add(new Realm( - ucfirst($publicName), $environment['internalName'], + ucfirst($publicName), $environment['base_url'], $environment['client_id'], $environment['client_secret'], Environment::fromString($publicName) )); } + return $realms; } } diff --git a/app/Keycloak/Models/KeycloakClientModel.php b/app/Keycloak/Models/KeycloakClientModel.php index c91abc8ff..7203b63d8 100644 --- a/app/Keycloak/Models/KeycloakClientModel.php +++ b/app/Keycloak/Models/KeycloakClientModel.php @@ -33,7 +33,6 @@ public function toDomain(): Client $config = App::get(Config::class); return new Client( - // Trying to use magic getters for eg $this->id gives a 0 back Uuid::fromString($this->id), Uuid::fromString($this->integration_id), Uuid::fromString($this->client_id), diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index d2d37082c..481859d03 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -6,6 +6,7 @@ use App\Keycloak\Client; use App\Keycloak\Models\KeycloakClientModel; +use App\Keycloak\Realm; use App\Keycloak\RealmCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -90,7 +91,7 @@ public function getMissingRealmsByIntegrationId(UuidInterface $integrationId, Re return new RealmCollection(array_udiff( $realms->toArray(), $existingRealms, - fn (Client $t1, Client $t2) => strcmp($t1->id->toString(), $t2->id->toString()) + fn (Realm $t1, Realm $t2) => strcmp($t1->publicName, $t2->publicName) )); } } diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index e6d495180..af9906a09 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -41,7 +41,7 @@ final class KeycloakApiClientTest extends TestCase protected function setUp(): void { $this->realm = $this->givenTestRealm(); - $this->config = $this->givenKeycloakConfig($this->realm); + $this->config = $this->givenKeycloakConfig(); $this->integration = $this->givenThereIsAnIntegration(Uuid::fromString(self::INTEGRATION_ID)); $this->logger = $this->createMock(LoggerInterface::class); diff --git a/tests/Keycloak/ConfigFactory.php b/tests/Keycloak/ConfigFactory.php index 5c88fff7e..335626945 100644 --- a/tests/Keycloak/ConfigFactory.php +++ b/tests/Keycloak/ConfigFactory.php @@ -5,16 +5,17 @@ namespace Tests\Keycloak; use App\Keycloak\Config; -use App\Keycloak\Realm; use App\Keycloak\RealmCollection; trait ConfigFactory { - public function givenKeycloakConfig(Realm $realm): Config + use RealmFactory; + + public function givenKeycloakConfig(): Config { return new Config( true, - new RealmCollection([$realm]), + new RealmCollection([$this->givenAcceptanceRealm(), $this->givenTestRealm()]), ); } } diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index ef38669ec..fcfc0490e 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -36,7 +36,7 @@ final class BlockClientsTest extends TestCase protected function setUp(): void { - $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); + $this->config = $this->givenKeycloakConfig(); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index b5ee2e327..44f5bd032 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -45,7 +45,7 @@ final class CreateClientsTest extends TestCase protected function setUp(): void { - $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); + $this->config = $this->givenKeycloakConfig(); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index 2c2e4b1c0..ff696186b 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -44,7 +44,7 @@ final class UpdateClientsTest extends TestCase protected function setUp(): void { - $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); + $this->config = $this->givenKeycloakConfig(); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); diff --git a/tests/Keycloak/RealmFactory.php b/tests/Keycloak/RealmFactory.php index deb6b7822..41418ff52 100644 --- a/tests/Keycloak/RealmFactory.php +++ b/tests/Keycloak/RealmFactory.php @@ -13,7 +13,7 @@ public function givenAcceptanceRealm(): Realm { return new Realm( 'uitidpoc', - 'Acceptange', + 'Acceptance', 'https://keycloak.com/api', 'php_client', 'dfgopopzjcvijogdrg', @@ -25,7 +25,7 @@ public function givenTestRealm(): Realm { return new Realm( 'mytestrealm', - 'Test', + 'Testing', 'https://keycloak.com/api', 'php_client', 'dfgopopzjcvijogdrg', diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index c9ce3e1e9..a12609f31 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -28,7 +28,7 @@ protected function setUp(): void { parent::setUp(); $this->repository = new EloquentKeycloakClientRepository(); - $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); + $this->config = $this->givenKeycloakConfig(); } public function test_it_can_save_one_or_more_clients(): void diff --git a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php index 1467bf952..c3f8e855a 100644 --- a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php +++ b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php @@ -27,7 +27,7 @@ final class ClientCredentialsTest extends TestCase protected function setUp(): void { - $this->config = $this->givenKeycloakConfig($this->givenTestRealm()); + $this->config = $this->givenKeycloakConfig(); $this->logger = $this->createMock(LoggerInterface::class); } diff --git a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php index 6c935ad74..11cdce618 100644 --- a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php @@ -7,7 +7,6 @@ use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\Realm; use App\Nova\ActionGuards\Keycloak\BlockKeycloakClientGuard; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; @@ -37,9 +36,7 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new BlockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - /** @var Realm $realm */ - $realm = $this->givenKeycloakConfig($this->givenTestRealm())->realms->first(); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $realm); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $this->givenAcceptanceRealm()); } #[DataProvider('dataProvider')] diff --git a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php index 11487c9d9..676e52f53 100644 --- a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php @@ -7,7 +7,6 @@ use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\Realm; use App\Nova\ActionGuards\Keycloak\UnblockKeycloakClientGuard; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; @@ -35,10 +34,7 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new UnblockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - - /** @var Realm $realm */ - $realm = $this->givenKeycloakConfig($this->givenTestRealm())->realms->first(); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $realm); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $this->givenAcceptanceRealm()); } #[DataProvider('dataProvider')] From 17c7e267543e4e2b2fa622c387831c868263919a Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Wed, 29 May 2024 16:24:47 +0200 Subject: [PATCH 05/36] Pass master realm when fetching token --- tests/Keycloak/Client/KeycloakHttpClientTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Keycloak/Client/KeycloakHttpClientTest.php b/tests/Keycloak/Client/KeycloakHttpClientTest.php index 3ee48722f..f91d7fe16 100644 --- a/tests/Keycloak/Client/KeycloakHttpClientTest.php +++ b/tests/Keycloak/Client/KeycloakHttpClientTest.php @@ -36,7 +36,7 @@ public function test_it_can_send_a_request_with_bearer(): void $this->tokenStrategy->expects($this->once()) ->method('fetchToken') - ->with($keycloakClient, $this->givenAcceptanceRealm()) + ->with($keycloakClient, $this->givenAcceptanceRealm()->getMasterRealm()) ->willReturn(self::MY_SECRET_TOKEN); $request = new Request('GET', '/endpoint'); @@ -46,12 +46,12 @@ public function test_it_can_send_a_request_with_bearer(): void ->expects($this->once()) ->method('send') ->with($this->callback(function (RequestInterface $request) { - return $request->getUri()->__toString() === $this->givenTestRealm()->baseUrl . '/endpoint' + return $request->getUri()->__toString() === $this->givenAcceptanceRealm()->getMasterRealm()->baseUrl . '/endpoint' && $request->getHeader('Authorization')[0] === 'Bearer ' . self::MY_SECRET_TOKEN; })) ->willReturn($response); - $result = $keycloakClient->sendWithBearer($request, $this->givenTestRealm()); + $result = $keycloakClient->sendWithBearer($request, $this->givenAcceptanceRealm()); $this->assertEquals('Response body', $result->getBody()->getContents()); } @@ -65,13 +65,13 @@ public function test_it_can_send_a_request_without_bearer(): void ->expects($this->once()) ->method('send') ->with($this->callback(function (RequestInterface $request) { - return $request->getUri()->__toString() === $this->givenTestRealm()->baseUrl . '/endpoint'; + return $request->getUri()->__toString() === $this->givenAcceptanceRealm()->baseUrl . '/endpoint'; })) ->willReturn($response); $keycloakClient = new KeycloakHttpClient($this->clientMock, $this->tokenStrategy); - $result = $keycloakClient->sendWithoutBearer($request, $this->givenTestRealm()); + $result = $keycloakClient->sendWithoutBearer($request, $this->givenAcceptanceRealm()); $this->assertEquals('Response body', $result->getBody()->getContents()); } From fd663db033414cc4eca8ccc1ef4679b13f78ddcc Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 10:11:09 +0200 Subject: [PATCH 06/36] Fix updateClientsTest --- .../Keycloak/Listeners/UpdateClientsTest.php | 85 ++++++++++++------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index ff696186b..71a6f76a1 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -18,13 +18,14 @@ use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; use App\Keycloak\Converters\IntegrationUrlConverter; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; use Tests\CreatesIntegration; use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; +use Tests\TestCase; final class UpdateClientsTest extends TestCase { @@ -33,7 +34,6 @@ final class UpdateClientsTest extends TestCase use ConfigFactory; use RealmFactory; - private const SECRET = 'my-secret'; private const SEARCH_SCOPE_ID = '06059529-74b5-422a-a499-ffcaf065d437'; @@ -41,10 +41,14 @@ final class UpdateClientsTest extends TestCase private ScopeConfig $scopeConfig; private ApiClient&MockObject $apiClient; private LoggerInterface&MockObject $logger; + private IntegrationRepository&MockObject $integrationRepository; protected function setUp(): void { + parent::setUp(); + $this->config = $this->givenKeycloakConfig(); + $this->configureKeycloakConfigFacade(); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); @@ -66,47 +70,66 @@ protected function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->logger = $this->createMock(LoggerInterface::class); - } + $this->integrationRepository = $this->createMock(IntegrationRepository::class); - public function test_update_client_for_integration(): void - { - $integrationRepository = $this->createMock(IntegrationRepository::class); - $integrationRepository->expects($this->once()) + $this->integrationRepository->expects($this->once()) ->method('getById') ->with($this->integration->id) ->willReturn($this->integration); + } + public function test_update_client_for_integration(): void + { $clients = []; foreach ($this->config->realms as $realm) { - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); - - $this->apiClient->expects($this->once()) - ->method('updateClient') - ->with( - $client, - array_merge( - IntegrationToKeycloakClientConverter::convert($client->id, $this->integration, $client->clientId), - IntegrationUrlConverter::convert($this->integration, $client) - ) + $id = Uuid::uuid4(); + $clients[$id->toString()] = new Client($id, $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); + } + + $activeId = null; // Which client are we updating? + $this->apiClient->expects($this->exactly(2)) + ->method('updateClient') + ->willReturnCallback(function (Client $client, array $body) use (&$activeId) { + $expectedBody = array_merge( + IntegrationToKeycloakClientConverter::convert($client->id, $this->integration, $client->clientId), + IntegrationUrlConverter::convert($this->integration, $client) ); - $this->apiClient->expects($this->once()) - ->method('deleteScopes') - ->with($client); - $this->apiClient->expects($this->once()) - ->method('addScopeToClient') - ->with($client->realm, $client->id, Uuid::fromString(self::SEARCH_SCOPE_ID)); + $this->assertEquals($expectedBody, $body); + + $activeId = $client->id; + }); + + $this->apiClient->expects($this->exactly(2)) + ->method('deleteScopes') + ->willReturnCallback(function (Client $client) use (&$activeId) { + $this->assertEquals($activeId, $client->id); + }); + + $this->apiClient->expects($this->exactly(2)) + ->method('addScopeToClient') + ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { + $this->assertEquals($activeId, $client->id); + $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); + }); + + $this->logger->expects($this->exactly(2)) + ->method('info') + ->willReturnCallback(function (string $message, array $params) use (&$activeId, $clients) { + $this->assertEquals('Keycloak client updated', $message); - $this->logger->expects($this->once()) - ->method('info') - ->with('Keycloak client updated', [ + if ($activeId === null || !isset($clients[$activeId->toString()])) { + $this->fail('Logging client that does not exist'); + } + + $client = $clients[$activeId->toString()]; + + $this->assertEquals([ 'integration_id' => $this->integration->id->toString(), 'realm' => $client->realm->internalName, 'client_id' => $client->clientId->toString(), - ]); - - $clients[] = $client; - } + ], $params); + }); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); $keycloakClientRepository->expects($this->once()) @@ -115,7 +138,7 @@ public function test_update_client_for_integration(): void ->willReturn($clients); $createClients = new UpdateClients( - $integrationRepository, + $this->integrationRepository, $keycloakClientRepository, $this->apiClient, $this->scopeConfig, From f9bb8e1bb484a4cca44fdb0aea911c2fd1cb8ff6 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 10:12:12 +0200 Subject: [PATCH 07/36] Use Tests/TestCase everywhere to make it possible to fake facade App --- tests/Keycloak/CachedKeycloakClientStatusTest.php | 2 +- tests/Keycloak/Client/KeycloakApiClientTest.php | 4 +++- tests/Keycloak/Client/KeycloakHttpClientTest.php | 4 +++- tests/Keycloak/ClientTest.php | 2 +- tests/Keycloak/ConfigFactory.php | 10 ++++++++++ .../IntegrationToKeycloakClientConverterTest.php | 2 +- .../Converters/IntegrationUrlConverterTest.php | 4 +++- tests/Keycloak/Jobs/BlockClientHandlerTest.php | 2 +- tests/Keycloak/Jobs/UnblockClientHandlerTest.php | 2 +- tests/Keycloak/Listeners/BlockClientsTest.php | 4 +++- tests/Keycloak/Listeners/CreateClientsTest.php | 3 +++ .../EloquentKeycloakClientRepositoryTest.php | 2 ++ tests/Keycloak/ScopeConfigTest.php | 4 +++- tests/Keycloak/TokenStrategy/ClientCredentialsTest.php | 4 +++- 14 files changed, 38 insertions(+), 11 deletions(-) diff --git a/tests/Keycloak/CachedKeycloakClientStatusTest.php b/tests/Keycloak/CachedKeycloakClientStatusTest.php index 3547d34f7..66cca4adb 100644 --- a/tests/Keycloak/CachedKeycloakClientStatusTest.php +++ b/tests/Keycloak/CachedKeycloakClientStatusTest.php @@ -8,10 +8,10 @@ use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Auth0\CreatesMockAuth0ClusterSDK; +use Tests\TestCase; final class CachedKeycloakClientStatusTest extends TestCase { diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index af9906a09..494be14d6 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -13,13 +13,13 @@ use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; +use Tests\TestCase; final class KeycloakApiClientTest extends TestCase { @@ -40,6 +40,8 @@ final class KeycloakApiClientTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->realm = $this->givenTestRealm(); $this->config = $this->givenKeycloakConfig(); diff --git a/tests/Keycloak/Client/KeycloakHttpClientTest.php b/tests/Keycloak/Client/KeycloakHttpClientTest.php index f91d7fe16..e5bde7603 100644 --- a/tests/Keycloak/Client/KeycloakHttpClientTest.php +++ b/tests/Keycloak/Client/KeycloakHttpClientTest.php @@ -10,7 +10,7 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Psr\Http\Message\RequestInterface; use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; @@ -26,6 +26,8 @@ final class KeycloakHttpClientTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->clientMock = $this->createMock(ClientInterface::class); $this->tokenStrategy = $this->createMock(TokenStrategy::class); } diff --git a/tests/Keycloak/ClientTest.php b/tests/Keycloak/ClientTest.php index c555cba6b..38c64d73f 100644 --- a/tests/Keycloak/ClientTest.php +++ b/tests/Keycloak/ClientTest.php @@ -6,7 +6,7 @@ use App\Keycloak\Client; use InvalidArgumentException; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Ramsey\Uuid\Uuid; final class ClientTest extends TestCase diff --git a/tests/Keycloak/ConfigFactory.php b/tests/Keycloak/ConfigFactory.php index 335626945..bbefd7e73 100644 --- a/tests/Keycloak/ConfigFactory.php +++ b/tests/Keycloak/ConfigFactory.php @@ -6,6 +6,7 @@ use App\Keycloak\Config; use App\Keycloak\RealmCollection; +use Illuminate\Support\Facades\App; trait ConfigFactory { @@ -18,4 +19,13 @@ public function givenKeycloakConfig(): Config new RealmCollection([$this->givenAcceptanceRealm(), $this->givenTestRealm()]), ); } + + public function configureKeycloakConfigFacade(): void + { + // This is needed because some static calls in the Nova Resources use the Config object, and we don't want to hardcode the actual config values + // Open to more elegant solutions. + App::singleton(Config::class, function () { + return $this->givenKeycloakConfig(); + }); + } } diff --git a/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php b/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php index 9a3122b9b..149ca2e3f 100644 --- a/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php @@ -6,7 +6,7 @@ use App\Domain\Integrations\IntegrationPartnerStatus; use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; diff --git a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php index 1e8c3cbcc..5dfe0eaa5 100644 --- a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php @@ -10,7 +10,7 @@ use App\Domain\Integrations\IntegrationUrlType; use App\Keycloak\Client; use App\Keycloak\Converters\IntegrationUrlConverter; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Tests\CreatesIntegration; @@ -28,6 +28,8 @@ final class IntegrationUrlConverterTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->integrationId = Uuid::uuid4(); $this->client = new Client( Uuid::uuid4(), diff --git a/tests/Keycloak/Jobs/BlockClientHandlerTest.php b/tests/Keycloak/Jobs/BlockClientHandlerTest.php index dc28db82b..6ab1632e5 100644 --- a/tests/Keycloak/Jobs/BlockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/BlockClientHandlerTest.php @@ -12,7 +12,7 @@ use App\Keycloak\Repositories\KeycloakClientRepository; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Event; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Keycloak\ConfigFactory; diff --git a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php index 706d85da5..b37c87303 100644 --- a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php @@ -12,7 +12,7 @@ use App\Keycloak\Repositories\KeycloakClientRepository; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Event; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Keycloak\ConfigFactory; diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index fcfc0490e..47b94dbf9 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -12,13 +12,13 @@ use App\Keycloak\Listeners\BlockClients; use App\Keycloak\Repositories\KeycloakClientRepository; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; +use Tests\TestCase; final class BlockClientsTest extends TestCase { @@ -36,6 +36,8 @@ final class BlockClientsTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->config = $this->givenKeycloakConfig(); // This is a search API integration diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 44f5bd032..43ce98df9 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -45,7 +45,10 @@ final class CreateClientsTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->config = $this->givenKeycloakConfig(); + $this->configureKeycloakConfigFacade(); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index a12609f31..2d3699e04 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -27,8 +27,10 @@ final class EloquentKeycloakClientRepositoryTest extends TestCase protected function setUp(): void { parent::setUp(); + $this->repository = new EloquentKeycloakClientRepository(); $this->config = $this->givenKeycloakConfig(); + $this->configureKeycloakConfigFacade(); } public function test_it_can_save_one_or_more_clients(): void diff --git a/tests/Keycloak/ScopeConfigTest.php b/tests/Keycloak/ScopeConfigTest.php index d0b5abc60..0296f9493 100644 --- a/tests/Keycloak/ScopeConfigTest.php +++ b/tests/Keycloak/ScopeConfigTest.php @@ -6,7 +6,7 @@ use App\Domain\Integrations\IntegrationType; use App\Keycloak\ScopeConfig; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; @@ -23,6 +23,8 @@ final class ScopeConfigTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->scopeConfig = new ScopeConfig( Uuid::fromString(self::SEARCH_API_ID), Uuid::fromString(self::ENTRY_API_ID), diff --git a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php index c3f8e855a..584aa820b 100644 --- a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php +++ b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php @@ -9,7 +9,7 @@ use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; use Psr\Log\LoggerInterface; use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; @@ -27,6 +27,8 @@ final class ClientCredentialsTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->config = $this->givenKeycloakConfig(); $this->logger = $this->createMock(LoggerInterface::class); } From 20c8fac05220c21df66eccce8fa5667e9d7ba699 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 10:12:34 +0200 Subject: [PATCH 08/36] unclear var --- tests/Keycloak/Listeners/UpdateClientsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index 71a6f76a1..4db4e1695 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -137,7 +137,7 @@ public function test_update_client_for_integration(): void ->with($this->integration->id) ->willReturn($clients); - $createClients = new UpdateClients( + $updateClients = new UpdateClients( $this->integrationRepository, $keycloakClientRepository, $this->apiClient, @@ -145,6 +145,6 @@ public function test_update_client_for_integration(): void $this->logger ); - $createClients->handle(new IntegrationUpdated($this->integration->id)); + $updateClients->handle(new IntegrationUpdated($this->integration->id)); } } From fcf43c7b0e986b7044daf339e75545a6a16e479c Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 10:34:31 +0200 Subject: [PATCH 09/36] fixed Create Clients Test --- .../Keycloak/Listeners/CreateClientsTest.php | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 43ce98df9..407bc98da 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; use Tests\CreatesIntegration; use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; @@ -87,23 +88,28 @@ public function test_create_client_for_integration(): void self::SECRET, $realm ); - - $this->apiClient->expects($this->once()) - ->method('addScopeToClient') - ->with($realm, $clients[$realm->internalName]->id, Uuid::fromString(self::SEARCH_SCOPE_ID)); } + $activeId = null; $this->apiClient->expects($this->exactly($this->config->realms->count())) ->method('createClient') ->willReturnCallback( - function (Realm $realm, Integration $integrationArgument) use ($clients) { + function (Realm $realm, Integration $integrationArgument) use ($clients, &$activeId) { $this->assertEquals($this->integration->id, $integrationArgument->id); $this->assertArrayHasKey($realm->internalName, $clients); + $activeId = $clients[$realm->internalName]->id; return $clients[$realm->internalName]->id; } ); + $this->apiClient->expects($this->exactly(2)) + ->method('addScopeToClient') + ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { + $this->assertEquals($activeId, $client->id); + $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); + }); + $this->apiClient->expects($this->exactly($this->config->realms->count())) ->method('fetchClient') ->willReturnCallback( @@ -163,78 +169,85 @@ public function test_failed(): void public function test_handle_creating_missing_clients(): void { - $integrationId = Uuid::uuid4(); - $missingRealms = $this->config->realms; - $integration = $this->givenThereIsAnIntegration($integrationId); + $clients = []; + + $missingRealms = new RealmCollection([$this->givenTestRealm()]); $this->keycloakClientRepository->expects($this->once()) ->method('getMissingRealmsByIntegrationId') - ->with($integrationId) + ->with($this->integration->id, $this->config->realms) ->willReturn($missingRealms); - $this->integrationRepository->expects($this->once()) - ->method('getById') - ->with($integrationId) - ->willReturn($integration); - - $clientIds = []; foreach ($missingRealms as $realm) { - $clientIds[$realm->internalName] = Uuid::uuid4(); - - $this->apiClient->expects($this->once()) - ->method('addScopeToClient') - ->with($realm, $clientIds[$realm->internalName], Uuid::fromString(self::SEARCH_SCOPE_ID)); + $clients[$realm->internalName] = new Client( + Uuid::uuid4(), + $this->integration->id, + Uuid::uuid4(), + self::SECRET, + $realm + ); } + $activeId = null; $this->apiClient->expects($this->exactly($missingRealms->count())) ->method('createClient') ->willReturnCallback( - function (Realm $realm, Integration $integrationArgument) use ($clientIds, $integration) { - $this->assertEquals($integration->id, $integrationArgument->id); - - if (!isset($clientIds[$realm->internalName])) { - $this->fail('Unknown realm, could not match with id: ' . $realm->internalName); - } + function (Realm $realm, Integration $integrationArgument) use ($clients, &$activeId) { + $this->assertEquals($this->integration->id, $integrationArgument->id); + $this->assertArrayHasKey($realm->internalName, $clients); - return $clientIds[$realm->internalName]; + $activeId = $clients[$realm->internalName]->id; + return $clients[$realm->internalName]->id; } ); - $clients = new ClientCollection(); + $this->apiClient->expects($this->exactly($missingRealms->count())) + ->method('addScopeToClient') + ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { + $this->assertEquals($activeId, $client->id); + $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); + }); $this->apiClient->expects($this->exactly($missingRealms->count())) ->method('fetchClient') ->willReturnCallback( - function (Realm $realm, Integration $integration) use ($clients, $clientIds) { - $client = new Client( - $clientIds[$realm->internalName], - $integration->id, - Uuid::uuid4(), - self::SECRET, - $realm - ); - $clients->add($client); - return $client; + function (Realm $realm, Integration $integrationArgument) use ($clients) { + $this->assertEquals($this->integration->id, $integrationArgument->id); + $this->assertArrayHasKey($realm->internalName, $clients); + + return $clients[$realm->internalName]; } ); + $this->integrationRepository->expects($this->once()) + ->method('getById') + ->with($this->integration->id) + ->willReturn($this->integration); + $this->keycloakClientRepository->expects($this->once()) ->method('create') ->with(... $clients); + //Check if clients where created for all realms + $realmHits = []; + $this->logger->expects($this->exactly($missingRealms->count())) ->method('info') - ->willReturnCallback(function ($message, $options) use ($integration, $clientIds) { + ->willReturnCallback(function ($message, $options) use (&$realmHits) { $this->assertEquals('Keycloak client created', $message); $this->assertArrayHasKey('integration_id', $options); $this->assertArrayHasKey('realm', $options); - $this->assertArrayHasKey('client_id', $options); - $this->assertEquals($integration->id->toString(), $options['integration_id']); - $this->assertEquals($clientIds[$options['realm']], $options['client_id']); + $this->assertEquals($this->integration->id->toString(), $options['integration_id']); + + $realmHits[$options['realm']] = true; }); - $this->handler->handleCreatingMissingClients(new MissingClientsDetected($integrationId)); + $this->handler->handleCreatingMissingClients(new MissingClientsDetected($this->integration->id)); + + foreach ($missingRealms as $realm) { + $this->assertArrayHasKey($realm->internalName, $realmHits, 'Client was not created for realm ' . $realm->publicName); + } } public function test_handle_creating_missing_clients_no_missing_realms(): void From 93363ed36cdf7a9e882641bdbdc68bb65d454938 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 10:34:43 +0200 Subject: [PATCH 10/36] Make test realms more testery --- tests/Keycloak/RealmFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Keycloak/RealmFactory.php b/tests/Keycloak/RealmFactory.php index 41418ff52..bfd69bb51 100644 --- a/tests/Keycloak/RealmFactory.php +++ b/tests/Keycloak/RealmFactory.php @@ -12,7 +12,7 @@ trait RealmFactory public function givenAcceptanceRealm(): Realm { return new Realm( - 'uitidpoc', + 'myAcceptanceRealm', 'Acceptance', 'https://keycloak.com/api', 'php_client', @@ -24,7 +24,7 @@ public function givenAcceptanceRealm(): Realm public function givenTestRealm(): Realm { return new Realm( - 'mytestrealm', + 'myTestRealm', 'Testing', 'https://keycloak.com/api', 'php_client', From b553233e36f8fefc44eba10880aa54d89300ab51 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 10:46:16 +0200 Subject: [PATCH 11/36] fix block client test --- tests/Keycloak/Listeners/BlockClientsTest.php | 31 +++++++++++-------- .../Keycloak/Listeners/CreateClientsTest.php | 1 - 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index 47b94dbf9..1d320de35 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -39,6 +39,7 @@ protected function setUp(): void parent::setUp(); $this->config = $this->givenKeycloakConfig(); + $this->configureKeycloakConfigFacade(); // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); @@ -59,21 +60,25 @@ public function test_block_clients_when_integration_is_blocked(): void foreach ($this->config->realms as $realm) { $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); - $this->apiClient->expects($this->once()) - ->method('blockClient') - ->with($client); - - $this->logger->expects($this->once()) - ->method('info') - ->with('Keycloak client blocked', [ - 'integration_id' => $this->integration->id->toString(), - 'client_id' => $client->id->toString(), - 'realm' => $client->realm->internalName, - ]); - - $clients[] = $client; + $clients[$client->id->toString()] = $client; } + $this->apiClient->expects($this->exactly($this->config->realms->count())) + ->method('blockClient') + ->willReturnCallback(function (Client $client) use ($clients) { + $this->assertArrayHasKey($client->id->toString(), $clients); + }); + + $this->logger->expects($this->exactly($this->config->realms->count())) + ->method('info') + ->willReturnCallback(function ($message, $options) { + $this->assertEquals('Keycloak client blocked', $message); + $this->assertArrayHasKey('integration_id', $options); + $this->assertArrayHasKey('realm', $options); + + $this->assertEquals($this->integration->id->toString(), $options['integration_id']); + }); + $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); $keycloakClientRepository->expects($this->once()) ->method('getByIntegrationId') diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 407bc98da..0da243e2c 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -9,7 +9,6 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\ClientCollection; use App\Keycloak\Config; use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Listeners\CreateClients; From f03e96d63aec58605d7ddb2c3528dd1b3aca9a96 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:12:13 +0200 Subject: [PATCH 12/36] Fix sorting in gui --- app/Nova/Resources/KeycloakClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index 03ed28d8c..bb1cb679b 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -43,9 +43,9 @@ public static function defaultOrderings($query): Builder /** @var Builder $query */ return $query->orderByRaw( 'CASE - WHEN realm = \'acc\' THEN 1 + WHEN realm = \'acceptance\' THEN 1 WHEN realm = \'test\' THEN 2 - WHEN realm = \'prod\' THEN 3 + WHEN realm = \'production\' THEN 3 END' ); } From de00fd2b9d8ae9c2b5863edd2e902b95dde89609 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:16:26 +0200 Subject: [PATCH 13/36] making naming of realms consistent with other clients (uitidv1, auth0) --- app/Domain/Integrations/Environment.php | 10 ---------- app/Keycloak/ConfigFactory.php | 2 +- config/keycloak.php | 6 +++--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/Domain/Integrations/Environment.php b/app/Domain/Integrations/Environment.php index 9af1efc6a..86f0f25b7 100644 --- a/app/Domain/Integrations/Environment.php +++ b/app/Domain/Integrations/Environment.php @@ -9,14 +9,4 @@ enum Environment: string case Acceptance = 'acc'; case Testing = 'test'; case Production = 'prod'; - - public static function fromString(string $string): self - { - return match ($string) { - 'acceptance' => self::Acceptance, - 'testing' => self::Testing, - 'production' => self::Production, - default => throw new \InvalidArgumentException(sprintf('Invalid environment: %s', $string)), - }; - } } diff --git a/app/Keycloak/ConfigFactory.php b/app/Keycloak/ConfigFactory.php index a90f02915..9c00e33c1 100644 --- a/app/Keycloak/ConfigFactory.php +++ b/app/Keycloak/ConfigFactory.php @@ -32,7 +32,7 @@ private static function buildRealmCollection(): RealmCollection $environment['base_url'], $environment['client_id'], $environment['client_secret'], - Environment::fromString($publicName) + Environment::from($publicName) )); } diff --git a/config/keycloak.php b/config/keycloak.php index b96884180..d84baf4e6 100644 --- a/config/keycloak.php +++ b/config/keycloak.php @@ -5,19 +5,19 @@ return [ 'enabled' => env('KEYCLOAK_ENABLED', false), 'environments' => [ - 'acceptance' => [ + 'acc' => [ 'internalName' => env('KEYCLOAK_STAG_REALM_NAME', ''), 'base_url' => env('KEYCLOAK_STAG_BASE_URL', ''), 'client_id' => env('KEYCLOAK_STAG_CLIENT_ID', ''), 'client_secret' => env('KEYCLOAK_STAG_CLIENT_SECRET', ''), ], - 'testing' => [ + 'test' => [ 'internalName' => env('KEYCLOAK_TEST_REALM_NAME', ''), 'base_url' => env('KEYCLOAK_TEST_BASE_URL', ''), 'client_id' => env('KEYCLOAK_TEST_CLIENT_ID', ''), 'client_secret' => env('KEYCLOAK_TEST_CLIENT_SECRET', ''), ], - 'production' => [ + 'prod' => [ 'internalName' => env('KEYCLOAK_PROD_REALM_NAME', ''), 'base_url' => env('KEYCLOAK_PROD_BASE_URL', ''), 'client_id' => env('KEYCLOAK_PROD_CLIENT_ID', ''), From 33247381be0cb27c65eb9717e630e9084ef0c0f9 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:17:30 +0200 Subject: [PATCH 14/36] Always use id, not client id --- .../Integrations/Models/IntegrationModel.php | 2 +- app/Keycloak/Client.php | 6 ++++-- app/Keycloak/Client/ApiClient.php | 2 +- app/Keycloak/Client/KeycloakApiClient.php | 21 ++++++++----------- app/Keycloak/KeycloakServiceProvider.php | 2 +- app/Keycloak/Listeners/CreateClients.php | 6 +++--- app/Nova/Resources/KeycloakClient.php | 4 ++-- 7 files changed, 21 insertions(+), 22 deletions(-) diff --git a/app/Domain/Integrations/Models/IntegrationModel.php b/app/Domain/Integrations/Models/IntegrationModel.php index b7b27b970..f5c408508 100644 --- a/app/Domain/Integrations/Models/IntegrationModel.php +++ b/app/Domain/Integrations/Models/IntegrationModel.php @@ -268,7 +268,7 @@ public function hasMissingUiTiDv1Consumers(): bool public function hasMissingKeycloakConsumers(): bool { /** @var Config $config */ - $config = App::get('config'); + $config = App::get(Config::class); return $this->keycloakClients()->count() < $config->realms->count(); } diff --git a/app/Keycloak/Client.php b/app/Keycloak/Client.php index 902d9fda3..d65ef109b 100644 --- a/app/Keycloak/Client.php +++ b/app/Keycloak/Client.php @@ -4,6 +4,7 @@ namespace App\Keycloak; +use Illuminate\Support\Facades\Log; use InvalidArgumentException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -22,17 +23,18 @@ public function __construct( public static function createFromJson( Realm $realm, UuidInterface $integrationId, - UuidInterface $clientId, array $data ): self { if (empty($data['secret'])) { throw new InvalidArgumentException('Missing secret'); } + Log::debug(json_encode($data), $data); + return new self( Uuid::fromString($data['id']), $integrationId, - $clientId, + Uuid::fromString($data['clientId']), $data['secret'], $realm, ); diff --git a/app/Keycloak/Client/ApiClient.php b/app/Keycloak/Client/ApiClient.php index d87f80bcc..bf6dea103 100644 --- a/app/Keycloak/Client/ApiClient.php +++ b/app/Keycloak/Client/ApiClient.php @@ -15,7 +15,7 @@ public function createClient(Realm $realm, Integration $integration, UuidInterfa public function addScopeToClient(Client $client, UuidInterface $scopeId): void; - public function fetchClient(Realm $realm, Integration $integration, UuidInterface $clientId): Client; + public function fetchClient(Realm $realm, Integration $integration, UuidInterface $id): Client; public function fetchIsClientActive(Client $client): bool; diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index f9ffa6091..4d0d21e51 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -31,17 +31,15 @@ public function __construct( /** * @throws KeyCloakApiFailed */ - public function createClient(Realm $realm, Integration $integration, UuidInterface $clientId): void + public function createClient(Realm $realm, Integration $integration, UuidInterface $id): void { - $id = Uuid::uuid4(); - try { $response = $this->client->sendWithBearer( new Request( 'POST', sprintf('admin/realms/%s/clients', $realm->internalName), [], - Json::encode(IntegrationToKeycloakClientConverter::convert($id, $integration, $clientId)) + Json::encode(IntegrationToKeycloakClientConverter::convert($id, $integration, Uuid::uuid4())) ), $realm ); @@ -55,7 +53,7 @@ public function createClient(Realm $realm, Integration $integration, UuidInterfa In this case we do not fail, we will just connect both sides and make sure the scopes are configured correctly. */ - $this->logger->info(sprintf('Client %s already exists', $integration->id->toString())); + $this->logger->info(sprintf('Client %s already exists for realm %s', $integration->name, $realm->publicName)); return; } @@ -64,7 +62,7 @@ public function createClient(Realm $realm, Integration $integration, UuidInterfa throw KeyCloakApiFailed::failedToCreateClientWithResponse($response); } - $this->logger->info(sprintf('Client %s, client id %s created with id %s', $integration->name, $integration->id->toString(), $id->toString())); + $this->logger->info(sprintf('Client %s for realm %s created with id %s', $integration->name, $realm->publicName, $id->toString())); } /** @@ -76,7 +74,7 @@ public function addScopeToClient(Client $client, UuidInterface $scopeId): void $response = $this->client->sendWithBearer( new Request( 'PUT', - sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->realm->internalName, $client->clientId->toString(), $scopeId->toString()) + sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->realm->internalName, $client->id->toString(), $scopeId->toString()) ), $client->realm ); @@ -115,13 +113,13 @@ public function deleteScopes(Client $client): void /** * @throws KeyCloakApiFailed */ - public function fetchClient(Realm $realm, Integration $integration, UuidInterface $clientId): Client + public function fetchClient(Realm $realm, Integration $integration, UuidInterface $id): Client { try { $response = $this->client->sendWithBearer( new Request( 'GET', - sprintf('admin/realms/%s/clients/%s', $realm->internalName, $clientId->toString()) + sprintf('admin/realms/%s/clients/%s', $realm->internalName, $id->toString()) ), $realm ); @@ -132,8 +130,7 @@ public function fetchClient(Realm $realm, Integration $integration, UuidInterfac throw KeyCloakApiFailed::failedToFetchClient($realm, $body); } - $data = Json::decodeAssociatively($body); - return Client::createFromJson($realm, $integration->id, $clientId, $data[0]); + return Client::createFromJson($realm, $integration->id, Json::decodeAssociatively($body)); } catch (Throwable $e) { throw KeyCloakApiFailed::failedToFetchClient($realm, $e->getMessage()); } @@ -148,7 +145,7 @@ public function fetchIsClientActive(Client $client): bool $response = $this->client->sendWithBearer( new Request( 'GET', - 'admin/realms/' . $client->realm->internalName . '/clients?' . http_build_query(['clientId' => $client->clientId->toString()]) + 'admin/realms/' . $client->realm->internalName . '/clients/?' . http_build_query(['clientId' => $client->clientId->toString()]) ), $client->realm ); diff --git a/app/Keycloak/KeycloakServiceProvider.php b/app/Keycloak/KeycloakServiceProvider.php index 3d8a890e7..c7cc7f4d5 100644 --- a/app/Keycloak/KeycloakServiceProvider.php +++ b/app/Keycloak/KeycloakServiceProvider.php @@ -37,7 +37,7 @@ public function register(): void new KeycloakHttpClient( new Client([RequestOptions::HTTP_ERRORS => false]), new ClientCredentials( - $this->app->get(Config::class), + $this->app->get(LoggerInterface::class), ) ), $this->app->get(ScopeConfig::class), diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index c486a853b..569ad54d2 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -82,10 +82,10 @@ private function createClientsInKeycloak(Integration $integration, RealmCollecti foreach ($realms as $realm) { try { - $clientId = Uuid::uuid4(); + $id = Uuid::uuid4(); - $this->client->createClient($realm, $integration, $clientId); - $client = $this->client->fetchClient($realm, $integration, $clientId); + $this->client->createClient($realm, $integration, $id); + $client = $this->client->fetchClient($realm, $integration, $id); $this->client->addScopeToClient($client, $scopeId); $clientCollection->add($client); diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index bb1cb679b..03ed28d8c 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -43,9 +43,9 @@ public static function defaultOrderings($query): Builder /** @var Builder $query */ return $query->orderByRaw( 'CASE - WHEN realm = \'acceptance\' THEN 1 + WHEN realm = \'acc\' THEN 1 WHEN realm = \'test\' THEN 2 - WHEN realm = \'production\' THEN 3 + WHEN realm = \'prod\' THEN 3 END' ); } From f7806847d5029e758c78e08b544b446fd14a8429 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:20:20 +0200 Subject: [PATCH 15/36] rename of enviroment --- .env.ci | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.env.ci b/.env.ci index 5a9dca109..c1bddc02e 100644 --- a/.env.ci +++ b/.env.ci @@ -145,9 +145,21 @@ UITPAS_INTEGRATION_TYPE_ENABLED=true VITE_UITPAS_INTEGRATION_TYPE_ENABLED=${UITPAS_INTEGRATION_TYPE_ENABLED} KEYCLOAK_ENABLED=true -KEYCLOAK_BASE_URL='https://account.kcpoc.lodgon.com/' -KEYCLOAK_CLIENT_ID='not_real_client' -KEYCLOAK_CLIENT_SECRET='not_the_real_secret' + +KEYCLOAK_STAG_BASE_URL='https://account.kcpoc.lodgon.com/' +KEYCLOAK_STAG_REALM_NAME='uitidpoc' +KEYCLOAK_STAG_CLIENT_ID='php_client' +KEYCLOAK_STAG_CLIENT_SECRET='xxx' + +KEYCLOAK_TEST_BASE_URL='https://account.kcpoc.lodgon.com/' +KEYCLOAK_TEST_REALM_NAME='uitidpoc' +KEYCLOAK_TEST_CLIENT_ID='php_client' +KEYCLOAK_TEST_CLIENT_SECRET='xxx' + +KEYCLOAK_PROD_BASE_URL='https://account.kcpoc.lodgon.com/' +KEYCLOAK_PROD_REALM_NAME='uitidpoc' +KEYCLOAK_PROD_CLIENT_ID='php_client' +KEYCLOAK_PROD_CLIENT_SECRET='xxx' # Incorrect values, but need to contain a valid UUID formatted string KEYCLOAK_SCOPE_SEARCH_API_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e' #publiq-api-sapi-scope From 4d4989cda448dcea28b7114c35e2a252a3f5536d Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:21:04 +0200 Subject: [PATCH 16/36] fetchIsClientActive now also used id instead of client id --- app/Keycloak/Client.php | 2 -- app/Keycloak/Client/KeycloakApiClient.php | 7 ++----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/Keycloak/Client.php b/app/Keycloak/Client.php index d65ef109b..fe59769c2 100644 --- a/app/Keycloak/Client.php +++ b/app/Keycloak/Client.php @@ -29,8 +29,6 @@ public static function createFromJson( throw new InvalidArgumentException('Missing secret'); } - Log::debug(json_encode($data), $data); - return new self( Uuid::fromString($data['id']), $integrationId, diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index 4d0d21e51..6fab8825f 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -145,7 +145,7 @@ public function fetchIsClientActive(Client $client): bool $response = $this->client->sendWithBearer( new Request( 'GET', - 'admin/realms/' . $client->realm->internalName . '/clients/?' . http_build_query(['clientId' => $client->clientId->toString()]) + sprintf('admin/realms/%s/clients/%s', $client->realm->internalName, $client->id->toString()) ), $client->realm ); @@ -158,11 +158,8 @@ public function fetchIsClientActive(Client $client): bool $data = Json::decodeAssociatively($body); - $this->logger->info('Response: ' . $body); - - return $data[0]['enabled']; + return $data['enabled']; } catch (Throwable $e) { - Log::error($e->getLine() . '/' . $e->getMessage()); throw KeyCloakApiFailed::failedToFetchClient($client->realm, $e->getMessage()); } } From 802d3ac59ad49c59d42c2b008930a8dc4813619b Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:22:16 +0200 Subject: [PATCH 17/36] style --- app/Keycloak/Client.php | 1 - app/Keycloak/Client/KeycloakApiClient.php | 1 - 2 files changed, 2 deletions(-) diff --git a/app/Keycloak/Client.php b/app/Keycloak/Client.php index fe59769c2..b99d686fd 100644 --- a/app/Keycloak/Client.php +++ b/app/Keycloak/Client.php @@ -4,7 +4,6 @@ namespace App\Keycloak; -use Illuminate\Support\Facades\Log; use InvalidArgumentException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index 6fab8825f..48d91e4e8 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -13,7 +13,6 @@ use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Request; -use Illuminate\Support\Facades\Log; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; From d10efaf2b2d9e24a6545033f7ce4d769e12b17d0 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:33:55 +0200 Subject: [PATCH 18/36] fix tests change from client id to id --- .../Keycloak/Client/KeycloakApiClientTest.php | 28 +++++++------------ tests/Keycloak/ClientTest.php | 8 +++--- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index 494be14d6..cce1f6180 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -57,7 +57,7 @@ protected function setUp(): void public function test_can_create_client(): void { - $clientId = Uuid::uuid4(); + $id = Uuid::uuid4(); $mock = new MockHandler([ new Response(200, [], json_encode(['access_token' => self::TOKEN], JSON_THROW_ON_ERROR)), new Response(201), @@ -72,19 +72,13 @@ public function test_can_create_client(): void $counter = 0; $this->logger->expects($this->exactly(2)) ->method('info') - ->willReturnCallback(function ($message) use (&$counter) { + ->willReturnCallback(function ($message) use (&$counter, $id) { switch ($counter++) { case 0: $this->assertEquals('Fetched token for php_client, token starts with ' . substr(self::TOKEN, 0, 6), $message); break; case 1: - - // use contains because we don't know the generated id - $this->assertStringStartsWith( - sprintf('Client %s, client id %s created with id', $this->integration->name, $this->integration->id), - $message - ); - + $this->assertEquals(sprintf('Client %s for realm %s created with id %s', $this->integration->name, $this->realm->publicName, $id->toString()), $message); break; default: $this->fail('Unknown message logged: ' . $message); @@ -94,7 +88,7 @@ public function test_can_create_client(): void $apiClient->createClient( $this->realm, $this->integration, - $clientId, + $id, ); } @@ -155,13 +149,11 @@ public function test_can_fetch_client(): void new Response(200, [], json_encode(['access_token' => self::TOKEN], JSON_THROW_ON_ERROR)), new Response(200, [], json_encode( [ - [ - 'id' => self::UUID, - 'clientId' => $clientId, - 'name' => 'test client', - 'secret' => self::SECRET, - 'enabled' => true, - ], + 'id' => self::UUID, + 'clientId' => $clientId, + 'name' => 'test client', + 'secret' => self::SECRET, + 'enabled' => true, ], JSON_THROW_ON_ERROR )), @@ -211,7 +203,7 @@ public function test_fetch_is_client_enabled(bool $enabled): void { $mock = new MockHandler([ new Response(200, [], json_encode(['access_token' => self::TOKEN], JSON_THROW_ON_ERROR)), - new Response(200, [], json_encode([['enabled' => $enabled]], JSON_THROW_ON_ERROR)), + new Response(200, [], json_encode(['enabled' => $enabled], JSON_THROW_ON_ERROR)), ]); $apiClient = new KeycloakApiClient( diff --git a/tests/Keycloak/ClientTest.php b/tests/Keycloak/ClientTest.php index 38c64d73f..8f4bedc0b 100644 --- a/tests/Keycloak/ClientTest.php +++ b/tests/Keycloak/ClientTest.php @@ -16,18 +16,18 @@ final class ClientTest extends TestCase public function test_create_from_json(): void { $integrationId = Uuid::uuid4(); - $clientId = Uuid::uuid4(); $data = [ 'id' => Uuid::uuid4()->toString(), 'secret' => 'testSecret', + 'clientId' => Uuid::uuid4()->toString(), ]; - $client = Client::createFromJson($this->givenTestRealm(), $integrationId, $clientId, $data); + $client = Client::createFromJson($this->givenTestRealm(), $integrationId, $data); $this->assertEquals($data['id'], $client->id->toString()); $this->assertEquals($data['secret'], $client->clientSecret); + $this->assertEquals($data['clientId'], $client->clientId); $this->assertEquals($integrationId, $client->integrationId); - $this->assertEquals($clientId, $client->clientId); $this->assertEquals($this->givenTestRealm(), $client->realm); } @@ -41,6 +41,6 @@ public function test_throws_when_missing_secret(): void $this->expectException(InvalidArgumentException::class); - Client::createFromJson($this->givenTestRealm(), $integrationId, Uuid::uuid4(), $data); + Client::createFromJson($this->givenTestRealm(), $integrationId, $data); } } From 2db8a1b12b08d756aae044bc5603e49b943b3f49 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 11:34:15 +0200 Subject: [PATCH 19/36] improve label in gui --- app/Nova/Resources/KeycloakClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index 03ed28d8c..73be94bab 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -105,7 +105,7 @@ public function actions(NovaRequest $request): array App::make(BlockKeycloakClient::class) ->exceptOnIndex() ->confirmText('Are you sure you want to block this client?') - ->confirmButtonText('Disable') + ->confirmButtonText('Block') ->cancelButtonText('Cancel') ->canSee(fn (Request $request) => $this->canDisable($request, $this->resource)) ->canRun(fn (Request $request, KeycloakClientModel $model) => $this->canDisable($request, $model)), From 737b8dced4785c841acf9cd675a5c242c6f914ab Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 14:21:25 +0200 Subject: [PATCH 20/36] Use Enum directly --- app/Domain/Integrations/Models/IntegrationModel.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/Domain/Integrations/Models/IntegrationModel.php b/app/Domain/Integrations/Models/IntegrationModel.php index f5c408508..1f33ad7e6 100644 --- a/app/Domain/Integrations/Models/IntegrationModel.php +++ b/app/Domain/Integrations/Models/IntegrationModel.php @@ -8,6 +8,7 @@ use App\Auth0\Models\Auth0ClientModel; use App\Domain\Contacts\Models\ContactModel; use App\Domain\Coupons\Models\CouponModel; +use App\Domain\Integrations\Environment; use App\Domain\Integrations\Events\IntegrationActivated; use App\Domain\Integrations\Events\IntegrationActivationRequested; use App\Domain\Integrations\Events\IntegrationBlocked; @@ -25,7 +26,6 @@ use App\Domain\Subscriptions\Models\SubscriptionModel; use App\Insightly\Models\InsightlyMappingModel; use App\Insightly\Resources\ResourceType; -use App\Keycloak\Config; use App\Keycloak\Models\KeycloakClientModel; use App\Models\UuidModel; use App\UiTiDv1\Models\UiTiDv1ConsumerModel; @@ -34,7 +34,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Facades\App; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -267,9 +266,7 @@ public function hasMissingUiTiDv1Consumers(): bool public function hasMissingKeycloakConsumers(): bool { - /** @var Config $config */ - $config = App::get(Config::class); - return $this->keycloakClients()->count() < $config->realms->count(); + return $this->keycloakClients()->count() < count(Environment::cases()); } public function toDomain(): Integration From 8b4b0d893c1a513f36aac4438374734ed3c67541 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 14:26:10 +0200 Subject: [PATCH 21/36] Remove ConfigFactory, use isEnabled flag directly --- app/Keycloak/ConfigFactory.php | 41 ------------------------ app/Keycloak/KeycloakServiceProvider.php | 7 ++-- app/Keycloak/RealmCollection.php | 24 ++++++++++++++ app/Nova/Resources/Integration.php | 14 ++------ 4 files changed, 31 insertions(+), 55 deletions(-) delete mode 100644 app/Keycloak/ConfigFactory.php diff --git a/app/Keycloak/ConfigFactory.php b/app/Keycloak/ConfigFactory.php deleted file mode 100644 index 9c00e33c1..000000000 --- a/app/Keycloak/ConfigFactory.php +++ /dev/null @@ -1,41 +0,0 @@ - $environment) { - if (empty($environment['internalName']) || empty($environment['base_url']) || empty($environment['client_id']) || empty($environment['client_secret'])) { - // If any of the fields are missing, do not create that realm. - continue; - } - - $realms->add(new Realm( - $environment['internalName'], - ucfirst($publicName), - $environment['base_url'], - $environment['client_id'], - $environment['client_secret'], - Environment::from($publicName) - )); - } - - return $realms; - } -} diff --git a/app/Keycloak/KeycloakServiceProvider.php b/app/Keycloak/KeycloakServiceProvider.php index c7cc7f4d5..eadb48429 100644 --- a/app/Keycloak/KeycloakServiceProvider.php +++ b/app/Keycloak/KeycloakServiceProvider.php @@ -14,8 +14,8 @@ use App\Keycloak\Client\KeycloakApiClient; use App\Keycloak\Client\KeycloakHttpClient; use App\Keycloak\Events\MissingClientsDetected; -use App\Keycloak\Listeners\CreateClients; use App\Keycloak\Listeners\BlockClients; +use App\Keycloak\Listeners\CreateClients; use App\Keycloak\Listeners\UpdateClients; use App\Keycloak\Repositories\EloquentKeycloakClientRepository; use App\Keycloak\Repositories\KeycloakClientRepository; @@ -46,7 +46,10 @@ public function register(): void }); $this->app->singleton(Config::class, function () { - return ConfigFactory::build(); + return new Config( + config('keycloak.enabled'), + RealmCollection::build() + ); }); $this->app->singleton(ScopeConfig::class, function () { diff --git a/app/Keycloak/RealmCollection.php b/app/Keycloak/RealmCollection.php index 08ab157a7..8774b9d99 100644 --- a/app/Keycloak/RealmCollection.php +++ b/app/Keycloak/RealmCollection.php @@ -4,6 +4,7 @@ namespace App\Keycloak; +use App\Domain\Integrations\Environment; use Illuminate\Support\Collection; use InvalidArgumentException; @@ -12,6 +13,29 @@ */ final class RealmCollection extends Collection { + public static function build(): RealmCollection + { + $realms = new RealmCollection(); + + foreach (config('keycloak.environments') as $publicName => $environment) { + if (empty($environment['internalName']) || empty($environment['base_url']) || empty($environment['client_id']) || empty($environment['client_secret'])) { + // If any of the fields are missing, do not create that realm. + continue; + } + + $realms->add(new Realm( + $environment['internalName'], + ucfirst($publicName), + $environment['base_url'], + $environment['client_id'], + $environment['client_secret'], + Environment::from($publicName) + )); + } + + return $realms; + } + public function fromPublicName(string $publicName): Realm { foreach ($this->all() as $realm) { diff --git a/app/Nova/Resources/Integration.php b/app/Nova/Resources/Integration.php index 37664d35e..8fd748725 100644 --- a/app/Nova/Resources/Integration.php +++ b/app/Nova/Resources/Integration.php @@ -57,16 +57,6 @@ final class Integration extends Resource 'created_at' => 'desc', ]; - private Config $keycloakConfig; - - public function __construct($resource = null) - { - parent::__construct($resource); - - $this->keycloakConfig = App::get(Config::class); - } - - /** * @return array */ @@ -178,7 +168,7 @@ public function fields(NovaRequest $request): array HasMany::make('UiTiD v2 Client Credentials (Auth0)', 'auth0Clients', Auth0Client::class), ]; - if ($this->keycloakConfig->isEnabled) { + if (config('keycloak.enabled')) { $fields[] = HasMany::make('Keycloak client Credentials', 'keycloakClients', KeycloakClient::class); } @@ -241,7 +231,7 @@ public function actions(NovaRequest $request): array ->canRun(fn (Request $request, IntegrationModel $model) => $model->hasMissingUiTiDv1Consumers()), ]; - if ($this->keycloakConfig->isEnabled) { + if (config('keycloak.enabled')) { $actions[] = (new CreateMissingKeycloakClients()) ->withName('Create missing Keycloak clients') ->exceptOnIndex() From 960ccb2bf18180cef96d2d0e10fe823cf5707f6c Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 14:33:24 +0200 Subject: [PATCH 22/36] Rename stag to acc --- .env.ci | 8 ++++---- app/Nova/Resources/Integration.php | 1 - app/Nova/Resources/KeycloakClient.php | 7 ++++++- config/keycloak.php | 8 ++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.env.ci b/.env.ci index c1bddc02e..30d498bda 100644 --- a/.env.ci +++ b/.env.ci @@ -146,10 +146,10 @@ VITE_UITPAS_INTEGRATION_TYPE_ENABLED=${UITPAS_INTEGRATION_TYPE_ENABLED} KEYCLOAK_ENABLED=true -KEYCLOAK_STAG_BASE_URL='https://account.kcpoc.lodgon.com/' -KEYCLOAK_STAG_REALM_NAME='uitidpoc' -KEYCLOAK_STAG_CLIENT_ID='php_client' -KEYCLOAK_STAG_CLIENT_SECRET='xxx' +KEYCLOAK_ACC_BASE_URL='https://account.kcpoc.lodgon.com/' +KEYCLOAK_ACC_REALM_NAME='uitidpoc' +KEYCLOAK_ACC_CLIENT_ID='php_client' +KEYCLOAK_ACC_CLIENT_SECRET='xxx' KEYCLOAK_TEST_BASE_URL='https://account.kcpoc.lodgon.com/' KEYCLOAK_TEST_REALM_NAME='uitidpoc' diff --git a/app/Nova/Resources/Integration.php b/app/Nova/Resources/Integration.php index 8fd748725..dbdb8e121 100644 --- a/app/Nova/Resources/Integration.php +++ b/app/Nova/Resources/Integration.php @@ -10,7 +10,6 @@ use App\Domain\Integrations\KeyVisibility; use App\Domain\Integrations\Models\IntegrationModel; use App\Domain\Integrations\Repositories\IntegrationRepository; -use App\Keycloak\Config; use App\Nova\Actions\ActivateIntegration; use App\Nova\Actions\ApproveIntegration; use App\Nova\Actions\Auth0\CreateMissingAuth0Clients; diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index 73be94bab..f38b8389b 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -4,6 +4,7 @@ namespace App\Nova\Resources; +use App\Domain\Integrations\Environment; use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Config; use App\Keycloak\Models\KeycloakClientModel; @@ -62,7 +63,11 @@ public function fields(NovaRequest $request): array Select::make('realm') ->readonly() ->filterable() - ->options($this->getConfig()->realms->asArray()), + ->options([ + Environment::Acceptance->value => Environment::Acceptance->name, + Environment::Testing->value => Environment::Testing->name, + Environment::Production->value => Environment::Production->name, + ]), Text::make('Status', function (KeycloakClientModel $model) { $client = $model->toDomain(); diff --git a/config/keycloak.php b/config/keycloak.php index d84baf4e6..fa7879c41 100644 --- a/config/keycloak.php +++ b/config/keycloak.php @@ -6,10 +6,10 @@ 'enabled' => env('KEYCLOAK_ENABLED', false), 'environments' => [ 'acc' => [ - 'internalName' => env('KEYCLOAK_STAG_REALM_NAME', ''), - 'base_url' => env('KEYCLOAK_STAG_BASE_URL', ''), - 'client_id' => env('KEYCLOAK_STAG_CLIENT_ID', ''), - 'client_secret' => env('KEYCLOAK_STAG_CLIENT_SECRET', ''), + 'internalName' => env('KEYCLOAK_ACC_REALM_NAME', ''), + 'base_url' => env('KEYCLOAK_ACC_BASE_URL', ''), + 'client_id' => env('KEYCLOAK_ACC_CLIENT_ID', ''), + 'client_secret' => env('KEYCLOAK_ACC_CLIENT_SECRET', ''), ], 'test' => [ 'internalName' => env('KEYCLOAK_TEST_REALM_NAME', ''), From 1af6dd457ce3b7003d25c2f276fe06b6da4658aa Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 15:25:43 +0200 Subject: [PATCH 23/36] remove Config object --- app/Keycloak/Config.php | 14 -------- app/Keycloak/KeycloakConfig.php | 10 ++++++ app/Keycloak/KeycloakServiceProvider.php | 9 +---- app/Keycloak/Listeners/CreateClients.php | 7 ++-- app/Keycloak/Models/KeycloakClientModel.php | 8 ++--- app/Nova/Resources/Integration.php | 5 +-- app/Nova/Resources/KeycloakClient.php | 6 ---- .../CachedKeycloakClientStatusTest.php | 2 +- .../Keycloak/Client/KeycloakApiClientTest.php | 4 +-- .../Client/KeycloakHttpClientTest.php | 2 -- tests/Keycloak/ConfigFactory.php | 31 ---------------- .../IntegrationUrlConverterTest.php | 3 +- .../Keycloak/Jobs/BlockClientHandlerTest.php | 2 -- .../Jobs/UnblockClientHandlerTest.php | 3 +- tests/Keycloak/KeycloakHttpClientFactory.php | 3 -- tests/Keycloak/Listeners/BlockClientsTest.php | 13 +++---- .../Keycloak/Listeners/CreateClientsTest.php | 28 +++++++-------- .../Keycloak/Listeners/UpdateClientsTest.php | 19 +++++----- tests/Keycloak/RealmFactory.php | 27 ++++++++++++-- .../EloquentKeycloakClientRepositoryTest.php | 35 ++++++++++++------- .../TokenStrategy/ClientCredentialsTest.php | 4 +-- .../Keycloak/BlockKeycloakClientGuardTest.php | 3 +- .../UnblockKeycloakClientGuardTest.php | 3 +- 23 files changed, 100 insertions(+), 141 deletions(-) delete mode 100644 app/Keycloak/Config.php create mode 100644 app/Keycloak/KeycloakConfig.php delete mode 100644 tests/Keycloak/ConfigFactory.php diff --git a/app/Keycloak/Config.php b/app/Keycloak/Config.php deleted file mode 100644 index 0b297f613..000000000 --- a/app/Keycloak/Config.php +++ /dev/null @@ -1,14 +0,0 @@ -app->singleton(Config::class, function () { - return new Config( - config('keycloak.enabled'), - RealmCollection::build() - ); - }); - $this->app->singleton(ScopeConfig::class, function () { return new ScopeConfig( Uuid::fromString(config('keycloak.scope.search_api_id')), @@ -77,7 +70,7 @@ public function register(): void private function bootstrapEventHandling(): void { - if (!$this->app->get(Config::class)->isEnabled) { + if (!config(KeycloakConfig::isEnabled->value)) { return; } diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index 569ad54d2..7018735e8 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -9,7 +9,6 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client\ApiClient; use App\Keycloak\ClientCollection; -use App\Keycloak\Config; use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\RealmCollection; @@ -28,7 +27,7 @@ final class CreateClients implements ShouldQueue public function __construct( private readonly IntegrationRepository $integrationRepository, private readonly KeycloakClientRepository $keycloakClientRepository, - private readonly Config $config, + private readonly RealmCollection $realms, private readonly ApiClient $client, private readonly ScopeConfig $scopeConfig, private readonly LoggerInterface $logger @@ -37,14 +36,14 @@ public function __construct( public function handleCreateClients(IntegrationCreated $event): void { - $this->handle($event, $this->config->realms); + $this->handle($event, $this->realms); } public function handleCreatingMissingClients(MissingClientsDetected $event): void { $missingRealms = $this->keycloakClientRepository->getMissingRealmsByIntegrationId( $event->id, - $this->config->realms + $this->realms ); if (count($missingRealms) === 0) { diff --git a/app/Keycloak/Models/KeycloakClientModel.php b/app/Keycloak/Models/KeycloakClientModel.php index 7203b63d8..3d52ac75a 100644 --- a/app/Keycloak/Models/KeycloakClientModel.php +++ b/app/Keycloak/Models/KeycloakClientModel.php @@ -6,11 +6,10 @@ use App\Keycloak\Client; use App\Domain\Integrations\Models\IntegrationModel; -use App\Keycloak\Config; +use App\Keycloak\RealmCollection; use App\Models\UuidModel; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Facades\App; use Ramsey\Uuid\Uuid; final class KeycloakClientModel extends UuidModel @@ -29,15 +28,14 @@ final class KeycloakClientModel extends UuidModel public function toDomain(): Client { - /** @var Config $config */ - $config = App::get(Config::class); + $realms = RealmCollection::build(); return new Client( Uuid::fromString($this->id), Uuid::fromString($this->integration_id), Uuid::fromString($this->client_id), $this->client_secret, - $config->realms->fromPublicName($this->realm) + $realms->fromPublicName($this->realm) ); } diff --git a/app/Nova/Resources/Integration.php b/app/Nova/Resources/Integration.php index dbdb8e121..c66608121 100644 --- a/app/Nova/Resources/Integration.php +++ b/app/Nova/Resources/Integration.php @@ -10,6 +10,7 @@ use App\Domain\Integrations\KeyVisibility; use App\Domain\Integrations\Models\IntegrationModel; use App\Domain\Integrations\Repositories\IntegrationRepository; +use App\Keycloak\KeycloakConfig; use App\Nova\Actions\ActivateIntegration; use App\Nova\Actions\ApproveIntegration; use App\Nova\Actions\Auth0\CreateMissingAuth0Clients; @@ -167,7 +168,7 @@ public function fields(NovaRequest $request): array HasMany::make('UiTiD v2 Client Credentials (Auth0)', 'auth0Clients', Auth0Client::class), ]; - if (config('keycloak.enabled')) { + if (config(KeycloakConfig::isEnabled->value)) { $fields[] = HasMany::make('Keycloak client Credentials', 'keycloakClients', KeycloakClient::class); } @@ -230,7 +231,7 @@ public function actions(NovaRequest $request): array ->canRun(fn (Request $request, IntegrationModel $model) => $model->hasMissingUiTiDv1Consumers()), ]; - if (config('keycloak.enabled')) { + if (config(KeycloakConfig::isEnabled->value)) { $actions[] = (new CreateMissingKeycloakClients()) ->withName('Create missing Keycloak clients') ->exceptOnIndex() diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index f38b8389b..f32d3c1f1 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -6,7 +6,6 @@ use App\Domain\Integrations\Environment; use App\Keycloak\CachedKeycloakClientStatus; -use App\Keycloak\Config; use App\Keycloak\Models\KeycloakClientModel; use App\Nova\ActionGuards\ActionGuard; use App\Nova\ActionGuards\Keycloak\BlockKeycloakClientGuard; @@ -144,9 +143,4 @@ private function getKeycloakClientStatus(): CachedKeycloakClientStatus { return App::get(CachedKeycloakClientStatus::class); } - - private function getConfig(): Config - { - return App::get(Config::class); - } } diff --git a/tests/Keycloak/CachedKeycloakClientStatusTest.php b/tests/Keycloak/CachedKeycloakClientStatusTest.php index 66cca4adb..61a3ec3dc 100644 --- a/tests/Keycloak/CachedKeycloakClientStatusTest.php +++ b/tests/Keycloak/CachedKeycloakClientStatusTest.php @@ -17,7 +17,7 @@ final class CachedKeycloakClientStatusTest extends TestCase { use CreatesMockAuth0ClusterSDK; - use ConfigFactory; + use RealmFactory; private ApiClient&MockObject $apiClient; diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index cce1f6180..50640f67e 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -16,7 +16,6 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; @@ -26,7 +25,7 @@ final class KeycloakApiClientTest extends TestCase use KeycloakHttpClientFactory; use CreatesIntegration; use RealmFactory; - use ConfigFactory; + private const INTEGRATION_ID = '824c09c0-2f3a-4fa0-bde2-8bf25c9a5b74'; private const UUID = '824c09c0-2f3a-4fa0-bde2-8bf25c9a5b74'; @@ -43,7 +42,6 @@ protected function setUp(): void parent::setUp(); $this->realm = $this->givenTestRealm(); - $this->config = $this->givenKeycloakConfig(); $this->integration = $this->givenThereIsAnIntegration(Uuid::fromString(self::INTEGRATION_ID)); $this->logger = $this->createMock(LoggerInterface::class); diff --git a/tests/Keycloak/Client/KeycloakHttpClientTest.php b/tests/Keycloak/Client/KeycloakHttpClientTest.php index e5bde7603..5d643fae4 100644 --- a/tests/Keycloak/Client/KeycloakHttpClientTest.php +++ b/tests/Keycloak/Client/KeycloakHttpClientTest.php @@ -12,12 +12,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Tests\TestCase; use Psr\Http\Message\RequestInterface; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; final class KeycloakHttpClientTest extends TestCase { - use ConfigFactory; use RealmFactory; public const MY_SECRET_TOKEN = 'my-secret-token'; diff --git a/tests/Keycloak/ConfigFactory.php b/tests/Keycloak/ConfigFactory.php deleted file mode 100644 index bbefd7e73..000000000 --- a/tests/Keycloak/ConfigFactory.php +++ /dev/null @@ -1,31 +0,0 @@ -givenAcceptanceRealm(), $this->givenTestRealm()]), - ); - } - - public function configureKeycloakConfigFacade(): void - { - // This is needed because some static calls in the Nova Resources use the Config object, and we don't want to hardcode the actual config values - // Open to more elegant solutions. - App::singleton(Config::class, function () { - return $this->givenKeycloakConfig(); - }); - } -} diff --git a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php index 5dfe0eaa5..21aa99f65 100644 --- a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php @@ -14,13 +14,12 @@ use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Tests\CreatesIntegration; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; final class IntegrationUrlConverterTest extends TestCase { use CreatesIntegration; - use ConfigFactory; + use RealmFactory; private Client $client; diff --git a/tests/Keycloak/Jobs/BlockClientHandlerTest.php b/tests/Keycloak/Jobs/BlockClientHandlerTest.php index 6ab1632e5..a6c12a848 100644 --- a/tests/Keycloak/Jobs/BlockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/BlockClientHandlerTest.php @@ -15,12 +15,10 @@ use Tests\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; final class BlockClientHandlerTest extends TestCase { - use ConfigFactory; use RealmFactory; public function test_block_client_handler(): void diff --git a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php index b37c87303..2ab32bfa0 100644 --- a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php @@ -15,14 +15,13 @@ use Tests\TestCase; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; final class UnblockClientHandlerTest extends TestCase { use KeycloakHttpClientFactory; - use ConfigFactory; + use RealmFactory; public function test_unblock_client_handler(): void diff --git a/tests/Keycloak/KeycloakHttpClientFactory.php b/tests/Keycloak/KeycloakHttpClientFactory.php index 9ac0edef9..cca82d656 100644 --- a/tests/Keycloak/KeycloakHttpClientFactory.php +++ b/tests/Keycloak/KeycloakHttpClientFactory.php @@ -5,7 +5,6 @@ namespace Tests\Keycloak; use App\Keycloak\Client\KeycloakHttpClient; -use App\Keycloak\Config; use App\Keycloak\TokenStrategy\ClientCredentials; use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; @@ -15,8 +14,6 @@ trait KeycloakHttpClientFactory { - private Config $config; - protected function givenKeycloakHttpClient(LoggerInterface $logger, MockHandler $mock): KeycloakHttpClient { return new KeycloakHttpClient( diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index 1d320de35..c5a2c7664 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -15,7 +15,6 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; @@ -25,7 +24,7 @@ final class BlockClientsTest extends TestCase use CreatesIntegration; use KeycloakHttpClientFactory; - use ConfigFactory; + use RealmFactory; private const SECRET = 'my-secret'; @@ -38,9 +37,6 @@ protected function setUp(): void { parent::setUp(); - $this->config = $this->givenKeycloakConfig(); - $this->configureKeycloakConfigFacade(); - // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); @@ -57,19 +53,20 @@ public function test_block_clients_when_integration_is_blocked(): void ->willReturn($this->integration); $clients = []; - foreach ($this->config->realms as $realm) { + foreach ($this->givenAllRealms() + as $realm) { $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); $clients[$client->id->toString()] = $client; } - $this->apiClient->expects($this->exactly($this->config->realms->count())) + $this->apiClient->expects($this->exactly($this->givenAllRealms()->count())) ->method('blockClient') ->willReturnCallback(function (Client $client) use ($clients) { $this->assertArrayHasKey($client->id->toString(), $clients); }); - $this->logger->expects($this->exactly($this->config->realms->count())) + $this->logger->expects($this->exactly($this->givenAllRealms()->count())) ->method('info') ->willReturnCallback(function ($message, $options) { $this->assertEquals('Keycloak client blocked', $message); diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 0da243e2c..f82e70308 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -9,7 +9,6 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; -use App\Keycloak\Config; use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Listeners\CreateClients; use App\Keycloak\Realm; @@ -21,35 +20,30 @@ use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Tests\CreatesIntegration; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; final class CreateClientsTest extends TestCase { use CreatesIntegration; - use ConfigFactory; - use RealmFactory; + use RealmFactory; private const SECRET = 'my-secret'; private const SEARCH_SCOPE_ID = '06059529-74b5-422a-a499-ffcaf065d437'; - private Config $config; private Integration $integration; private CreateClients $handler; private IntegrationRepository&MockObject $integrationRepository; private KeycloakClientRepository&MockObject $keycloakClientRepository; private ApiClient&MockObject $apiClient; private LoggerInterface&MockObject $logger; + private RealmCollection $realms; protected function setUp(): void { parent::setUp(); - $this->config = $this->givenKeycloakConfig(); - $this->configureKeycloakConfigFacade(); - // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); @@ -65,10 +59,12 @@ protected function setUp(): void Uuid::fromString('0743b1c7-0ea2-46af-906e-fbb6c0317514'), ); + $this->realms = $this->givenAllRealms(); + $this->handler = new CreateClients( $this->integrationRepository, $this->keycloakClientRepository, - $this->config, + $this->realms, $this->apiClient, $scopeConfig, $this->logger, @@ -79,7 +75,7 @@ public function test_create_client_for_integration(): void { $clients = []; - foreach ($this->config->realms as $realm) { + foreach ($this->realms as $realm) { $clients[$realm->internalName] = new Client( Uuid::uuid4(), $this->integration->id, @@ -90,7 +86,7 @@ public function test_create_client_for_integration(): void } $activeId = null; - $this->apiClient->expects($this->exactly($this->config->realms->count())) + $this->apiClient->expects($this->exactly($this->realms->count())) ->method('createClient') ->willReturnCallback( function (Realm $realm, Integration $integrationArgument) use ($clients, &$activeId) { @@ -102,14 +98,14 @@ function (Realm $realm, Integration $integrationArgument) use ($clients, &$activ } ); - $this->apiClient->expects($this->exactly(2)) + $this->apiClient->expects($this->exactly($this->realms->count())) ->method('addScopeToClient') ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { $this->assertEquals($activeId, $client->id); $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); }); - $this->apiClient->expects($this->exactly($this->config->realms->count())) + $this->apiClient->expects($this->exactly($this->realms->count())) ->method('fetchClient') ->willReturnCallback( function (Realm $realm, Integration $integrationArgument) use ($clients) { @@ -132,7 +128,7 @@ function (Realm $realm, Integration $integrationArgument) use ($clients) { //Check if clients where created for all realms $realmHits = []; - $this->logger->expects($this->exactly($this->config->realms->count())) + $this->logger->expects($this->exactly($this->realms->count())) ->method('info') ->willReturnCallback(function ($message, $options) use (&$realmHits) { $this->assertEquals('Keycloak client created', $message); @@ -146,7 +142,7 @@ function (Realm $realm, Integration $integrationArgument) use ($clients) { $this->handler->handleCreateClients(new IntegrationCreated($this->integration->id)); - foreach ($this->config->realms as $realm) { + foreach ($this->realms as $realm) { $this->assertArrayHasKey($realm->internalName, $realmHits, 'Client was not created for realm ' . $realm->internalName); } } @@ -174,7 +170,7 @@ public function test_handle_creating_missing_clients(): void $this->keycloakClientRepository->expects($this->once()) ->method('getMissingRealmsByIntegrationId') - ->with($this->integration->id, $this->config->realms) + ->with($this->integration->id, $this->givenAllRealms()) ->willReturn($missingRealms); foreach ($missingRealms as $realm) { diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index 4db4e1695..131fd1406 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -13,6 +13,7 @@ use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; use App\Keycloak\Listeners\UpdateClients; +use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; @@ -22,7 +23,6 @@ use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Tests\CreatesIntegration; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; @@ -31,7 +31,7 @@ final class UpdateClientsTest extends TestCase { use CreatesIntegration; use KeycloakHttpClientFactory; - use ConfigFactory; + use RealmFactory; private const SECRET = 'my-secret'; @@ -42,14 +42,12 @@ final class UpdateClientsTest extends TestCase private ApiClient&MockObject $apiClient; private LoggerInterface&MockObject $logger; private IntegrationRepository&MockObject $integrationRepository; + private RealmCollection $realms; protected function setUp(): void { parent::setUp(); - $this->config = $this->givenKeycloakConfig(); - $this->configureKeycloakConfigFacade(); - // This is a search API integration $this->integration = $this->givenThereIsAnIntegration(Uuid::uuid4()); @@ -76,18 +74,19 @@ protected function setUp(): void ->method('getById') ->with($this->integration->id) ->willReturn($this->integration); + $this->realms = $this->givenAllRealms(); } public function test_update_client_for_integration(): void { $clients = []; - foreach ($this->config->realms as $realm) { + foreach ($this->realms as $realm) { $id = Uuid::uuid4(); $clients[$id->toString()] = new Client($id, $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); } $activeId = null; // Which client are we updating? - $this->apiClient->expects($this->exactly(2)) + $this->apiClient->expects($this->exactly($this->realms->count())) ->method('updateClient') ->willReturnCallback(function (Client $client, array $body) use (&$activeId) { $expectedBody = array_merge( @@ -100,20 +99,20 @@ public function test_update_client_for_integration(): void $activeId = $client->id; }); - $this->apiClient->expects($this->exactly(2)) + $this->apiClient->expects($this->exactly($this->realms->count())) ->method('deleteScopes') ->willReturnCallback(function (Client $client) use (&$activeId) { $this->assertEquals($activeId, $client->id); }); - $this->apiClient->expects($this->exactly(2)) + $this->apiClient->expects($this->exactly($this->realms->count())) ->method('addScopeToClient') ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { $this->assertEquals($activeId, $client->id); $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); }); - $this->logger->expects($this->exactly(2)) + $this->logger->expects($this->exactly($this->realms->count())) ->method('info') ->willReturnCallback(function (string $message, array $params) use (&$activeId, $clients) { $this->assertEquals('Keycloak client updated', $message); diff --git a/tests/Keycloak/RealmFactory.php b/tests/Keycloak/RealmFactory.php index bfd69bb51..7c7089605 100644 --- a/tests/Keycloak/RealmFactory.php +++ b/tests/Keycloak/RealmFactory.php @@ -6,14 +6,24 @@ use App\Domain\Integrations\Environment; use App\Keycloak\Realm; +use App\Keycloak\RealmCollection; trait RealmFactory { + public function givenAllRealms(): RealmCollection + { + return new RealmCollection([ + $this->givenAcceptanceRealm(), + $this->givenTestRealm(), + $this->givenProductionRealm(), + ]); + } + public function givenAcceptanceRealm(): Realm { return new Realm( 'myAcceptanceRealm', - 'Acceptance', + 'Acc', 'https://keycloak.com/api', 'php_client', 'dfgopopzjcvijogdrg', @@ -25,11 +35,24 @@ public function givenTestRealm(): Realm { return new Realm( 'myTestRealm', - 'Testing', + 'Test', 'https://keycloak.com/api', 'php_client', 'dfgopopzjcvijogdrg', Environment::Testing, ); } + + public function givenProductionRealm(): Realm + { + return new Realm( + 'myProductRealm', + 'Prod', + 'https://keycloak.com/api', + 'php_client', + 'dfgopopzjcvijogdrg', + Environment::Production, + ); + } + } diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index 2d3699e04..0e10b27af 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -5,32 +5,29 @@ namespace Tests\Keycloak\Repositories; use App\Keycloak\Client; -use App\Keycloak\Config; use App\Keycloak\Realm; use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\EloquentKeycloakClientRepository; use Illuminate\Foundation\Testing\RefreshDatabase; use Ramsey\Uuid\Uuid; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; final class EloquentKeycloakClientRepositoryTest extends TestCase { use RefreshDatabase; - use ConfigFactory; + use RealmFactory; private EloquentKeycloakClientRepository $repository; - private Config $config; + private RealmCollection $realms; protected function setUp(): void { parent::setUp(); $this->repository = new EloquentKeycloakClientRepository(); - $this->config = $this->givenKeycloakConfig(); - $this->configureKeycloakConfigFacade(); + $this->realms = $this->givenAllRealms(); } public function test_it_can_save_one_or_more_clients(): void @@ -72,7 +69,7 @@ public function test_it_can_save_one_or_more_clients(): void public function test_it_can_get_all_clients_for_an_integration_id(): void { /** @var Realm $realm */ - $realm = $this->config->realms->first(); + $realm = $this->realms->first(); $integrationId = Uuid::uuid4(); $clientId = Uuid::uuid4(); @@ -100,7 +97,11 @@ public function test_it_can_get_all_clients_for_an_integration_id(): void sort($expected); sort($actual); - $this->assertEquals($expected, $actual); + foreach($actual as $i => $client) { + $this->assertEquals($expected[$i]->clientId, $client->clientId); + $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); + $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); + } } public function test_it_can_get_all_clients_for_multiple_integration_ids(): void @@ -136,7 +137,11 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void sort($expected); sort($actual); - $this->assertEquals($expected, $actual); + foreach($actual as $i => $client) { + $this->assertEquals($expected[$i]->clientId, $client->clientId); + $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); + $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); + } } public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void @@ -146,7 +151,7 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void $integrationIds = [$firstIntegrationId, $secondIntegrationId]; $clients = []; - foreach ($this->config->realms as $realm) { + foreach ($this->realms as $realm) { foreach ($integrationIds as $integrationId) { $count = count($clients) + 1; @@ -174,7 +179,11 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void sort($expected); sort($actual); - $this->assertEquals($expected, $actual); + foreach($actual as $i => $client) { + $this->assertEquals($expected[$i]->clientId, $client->clientId); + $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); + $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); + } } public function test_it_can_get_missing_realms_by_integration_id(): void @@ -183,7 +192,7 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $clients = []; $missing = new RealmCollection(); - foreach ($this->config->realms as $realm) { + foreach ($this->realms as $realm) { if ($missing->isEmpty()) { $missing->add($realm); continue; @@ -201,7 +210,7 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $this->assertEquals( $missing, - $this->repository->getMissingRealmsByIntegrationId($integrationId, $this->config->realms) + $this->repository->getMissingRealmsByIntegrationId($integrationId, $this->realms) ); } } diff --git a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php index 584aa820b..255ea3155 100644 --- a/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php +++ b/tests/Keycloak/TokenStrategy/ClientCredentialsTest.php @@ -11,14 +11,13 @@ use PHPUnit\Framework\MockObject\MockObject; use Tests\TestCase; use Psr\Log\LoggerInterface; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\KeycloakHttpClientFactory; use Tests\Keycloak\RealmFactory; final class ClientCredentialsTest extends TestCase { use KeycloakHttpClientFactory; - use ConfigFactory; + use RealmFactory; public const ACCESS_TOKEN = 'pqeaefosdfhbsdq'; @@ -29,7 +28,6 @@ protected function setUp(): void { parent::setUp(); - $this->config = $this->givenKeycloakConfig(); $this->logger = $this->createMock(LoggerInterface::class); } diff --git a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php index 11cdce618..c4e937718 100644 --- a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php @@ -13,7 +13,6 @@ use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Auth0\CreatesMockAuth0ClusterSDK; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; @@ -21,7 +20,7 @@ final class BlockKeycloakClientGuardTest extends TestCase { use CreatesMockAuth0ClusterSDK; - use ConfigFactory; + use RealmFactory; diff --git a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php index 676e52f53..d2b4046be 100644 --- a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php @@ -13,7 +13,6 @@ use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use Tests\Auth0\CreatesMockAuth0ClusterSDK; -use Tests\Keycloak\ConfigFactory; use Tests\Keycloak\RealmFactory; use Tests\TestCase; @@ -21,7 +20,7 @@ final class UnblockKeycloakClientGuardTest extends TestCase { use CreatesMockAuth0ClusterSDK; - use ConfigFactory; + use RealmFactory; private ApiClient&MockObject $apiClient; From 72149c99c4d485472503e70044c2851648ecd5a9 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 30 May 2024 16:02:52 +0200 Subject: [PATCH 24/36] Remove Realms dependency from getMissingRealmsByIntegrationId() --- app/Keycloak/Listeners/CreateClients.php | 5 +--- .../EloquentKeycloakClientRepository.php | 4 ++- .../Repositories/KeycloakClientRepository.php | 2 +- .../Keycloak/Listeners/CreateClientsTest.php | 2 +- .../EloquentKeycloakClientRepositoryTest.php | 28 ++++++++++--------- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index 7018735e8..cddbaf13a 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -41,10 +41,7 @@ public function handleCreateClients(IntegrationCreated $event): void public function handleCreatingMissingClients(MissingClientsDetected $event): void { - $missingRealms = $this->keycloakClientRepository->getMissingRealmsByIntegrationId( - $event->id, - $this->realms - ); + $missingRealms = $this->keycloakClientRepository->getMissingRealmsByIntegrationId($event->id); if (count($missingRealms) === 0) { $this->logger->info($event->id . ' - already has all Keycloak clients'); diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index 481859d03..d50940017 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -75,10 +75,12 @@ public function getByIntegrationIds(array $integrationIds): array ->toArray(); } - public function getMissingRealmsByIntegrationId(UuidInterface $integrationId, RealmCollection $realms): RealmCollection + public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): RealmCollection { $clients = $this->getByIntegrationId($integrationId); + $realms = RealmCollection::build(); + if (count($clients) === $realms->count()) { return new RealmCollection(); } diff --git a/app/Keycloak/Repositories/KeycloakClientRepository.php b/app/Keycloak/Repositories/KeycloakClientRepository.php index 07f9bc702..c87003c55 100644 --- a/app/Keycloak/Repositories/KeycloakClientRepository.php +++ b/app/Keycloak/Repositories/KeycloakClientRepository.php @@ -30,5 +30,5 @@ public function getById(UuidInterface $id): Client; */ public function getByIntegrationIds(array $integrationIds): array; - public function getMissingRealmsByIntegrationId(UuidInterface $integrationId, RealmCollection $realms): RealmCollection; + public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): RealmCollection; } diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index f82e70308..3333dd0a8 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -170,7 +170,7 @@ public function test_handle_creating_missing_clients(): void $this->keycloakClientRepository->expects($this->once()) ->method('getMissingRealmsByIntegrationId') - ->with($this->integration->id, $this->givenAllRealms()) + ->with($this->integration->id) ->willReturn($missingRealms); foreach ($missingRealms as $realm) { diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index 0e10b27af..2b9af0a19 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -4,6 +4,7 @@ namespace Tests\Keycloak\Repositories; +use App\Domain\Integrations\Environment; use App\Keycloak\Client; use App\Keycloak\Realm; use App\Keycloak\RealmCollection; @@ -97,7 +98,7 @@ public function test_it_can_get_all_clients_for_an_integration_id(): void sort($expected); sort($actual); - foreach($actual as $i => $client) { + foreach ($actual as $i => $client) { $this->assertEquals($expected[$i]->clientId, $client->clientId); $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); @@ -137,7 +138,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void sort($expected); sort($actual); - foreach($actual as $i => $client) { + foreach ($actual as $i => $client) { $this->assertEquals($expected[$i]->clientId, $client->clientId); $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); @@ -179,7 +180,7 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void sort($expected); sort($actual); - foreach($actual as $i => $client) { + foreach ($actual as $i => $client) { $this->assertEquals($expected[$i]->clientId, $client->clientId); $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); @@ -191,12 +192,9 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $integrationId = Uuid::uuid4(); $clients = []; - $missing = new RealmCollection(); - foreach ($this->realms as $realm) { - if ($missing->isEmpty()) { - $missing->add($realm); - continue; - } + $missingRealmCollection = new RealmCollection(); + foreach (new RealmCollection([$this->givenAcceptanceRealm()]) as $realm) { + $missingRealmCollection->add($realm); $clients[] = new Client( Uuid::uuid4(), @@ -208,9 +206,13 @@ public function test_it_can_get_missing_realms_by_integration_id(): void } $this->repository->create(...$clients); - $this->assertEquals( - $missing, - $this->repository->getMissingRealmsByIntegrationId($integrationId, $this->realms) - ); + $missingRealms = $this->repository->getMissingRealmsByIntegrationId($integrationId); + + $this->assertCount(2, $missingRealms); + $this->assertInstanceOf(Realm::class, $missingRealms->get(1)); + $this->assertInstanceOf(Realm::class, $missingRealms->get(2)); + + $this->assertEquals(Environment::Testing->value, mb_strtolower($missingRealms->get(1)->publicName)); + $this->assertEquals(Environment::Production->value, mb_strtolower($missingRealms->get(2)->publicName)); } } From 07b74bcbf0891063150ff94093ab25f33da80ce5 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:17:55 +0200 Subject: [PATCH 25/36] Change keycloak Realm to Enviroment --- app/Keycloak/Client.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/Keycloak/Client.php b/app/Keycloak/Client.php index b99d686fd..0793b1555 100644 --- a/app/Keycloak/Client.php +++ b/app/Keycloak/Client.php @@ -4,6 +4,8 @@ namespace App\Keycloak; +use App\Domain\Integrations\Environment; +use Illuminate\Support\Facades\App; use InvalidArgumentException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -15,7 +17,7 @@ public function __construct( public UuidInterface $integrationId, public UuidInterface $clientId, public string $clientSecret, - public Realm $realm, + public Environment $environment, ) { } @@ -33,12 +35,30 @@ public static function createFromJson( $integrationId, Uuid::fromString($data['clientId']), $data['secret'], - $realm, + $realm->environment, ); } - public function getKeycloakUrl(string $baseUrl): string + public function getKeycloakUrl(): string { - return $baseUrl . 'admin/master/console/#/' . $this->realm->internalName . '/clients/' . $this->id->toString() . '/settings'; + $baseUrl = $this->getRealm()->baseUrl; + + return $baseUrl . 'admin/master/console/#/' . $this->getRealm()->internalName . '/clients/' . $this->id->toString() . '/settings'; + } + + public function getRealm(): Realm + { + /** @var RealmCollection $realmCollection */ + $realmCollection = App::get(RealmCollection::class); + + foreach ($realmCollection as $realm) { + if ($realm->environment === $this->environment) { + return $realm; + } + } + + throw new InvalidArgumentException( + sprintf('Could not convert environment %s to realm:', $this->environment->value) + ); } } From 95f82bb6c2db3a31635a3fb2f4c6a5858874d326 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:22:46 +0200 Subject: [PATCH 26/36] Use enviroment instead of realm --- app/Keycloak/Client/KeycloakApiClient.php | 20 +++++++++---------- app/Keycloak/Listeners/BlockClients.php | 2 +- app/Keycloak/Listeners/CreateClients.php | 2 +- .../EloquentKeycloakClientRepository.php | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index 48d91e4e8..a63e03c5e 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -73,9 +73,9 @@ public function addScopeToClient(Client $client, UuidInterface $scopeId): void $response = $this->client->sendWithBearer( new Request( 'PUT', - sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->realm->internalName, $client->id->toString(), $scopeId->toString()) + sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->getRealm()->internalName, $client->id->toString(), $scopeId->toString()) ), - $client->realm + $client->getRealm() ); } catch (GuzzleException $e) { throw KeyCloakApiFailed::failedToAddScopeToClient($e->getMessage()); @@ -94,9 +94,9 @@ public function deleteScopes(Client $client): void $response = $this->client->sendWithBearer( new Request( 'DELETE', - sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->realm->internalName, $client->id->toString(), $scope->toString()), + sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->getRealm()->internalName, $client->id->toString(), $scope->toString()), ), - $client->realm + $client->getRealm() ); // Will throw a 404 when scope not attached to client, but this is no problem. @@ -144,22 +144,22 @@ public function fetchIsClientActive(Client $client): bool $response = $this->client->sendWithBearer( new Request( 'GET', - sprintf('admin/realms/%s/clients/%s', $client->realm->internalName, $client->id->toString()) + sprintf('admin/realms/%s/clients/%s', $client->getRealm()->internalName, $client->id->toString()) ), - $client->realm + $client->getRealm() ); $body = $response->getBody()->getContents(); if (empty($body) || $response->getStatusCode() !== 200) { - throw KeyCloakApiFailed::failedToFetchClient($client->realm, $body); + throw KeyCloakApiFailed::failedToFetchClient($client->getRealm(), $body); } $data = Json::decodeAssociatively($body); return $data['enabled']; } catch (Throwable $e) { - throw KeyCloakApiFailed::failedToFetchClient($client->realm, $e->getMessage()); + throw KeyCloakApiFailed::failedToFetchClient($client->getRealm(), $e->getMessage()); } } @@ -188,11 +188,11 @@ public function updateClient(Client $client, array $body): void $response = $this->client->sendWithBearer( new Request( 'PUT', - 'admin/realms/' . $client->realm->internalName . '/clients/' . $client->id->toString(), + 'admin/realms/' . $client->getRealm()->internalName . '/clients/' . $client->id->toString(), [], Json::encode($body) ), - $client->realm + $client->getRealm() ); if ($response->getStatusCode() !== 204) { diff --git a/app/Keycloak/Listeners/BlockClients.php b/app/Keycloak/Listeners/BlockClients.php index c300cd165..241673feb 100644 --- a/app/Keycloak/Listeners/BlockClients.php +++ b/app/Keycloak/Listeners/BlockClients.php @@ -38,7 +38,7 @@ public function handle(IntegrationBlocked $integrationBlocked): void $this->logger->info('Keycloak client blocked', [ 'integration_id' => $integration->id->toString(), 'client_id' => $keycloakClient->id->toString(), - 'realm' => $keycloakClient->realm->internalName, + 'realm' => $keycloakClient->getRealm()->internalName, ]); } catch (KeyCloakApiFailed $e) { $this->failed($integrationBlocked, $e); diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index cddbaf13a..3a3906d30 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -65,7 +65,7 @@ private function handle(IntegrationCreated|MissingClientsDetected $event, RealmC $this->logger->info('Keycloak client created', [ 'integration_id' => $event->id->toString(), 'client_id' => $client->id->toString(), - 'realm' => $client->realm->internalName, + 'realm' => $client->getRealm()->internalName, ]); } } diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index d50940017..7bc6c1819 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -4,10 +4,10 @@ namespace App\Keycloak\Repositories; +use App\Domain\Integrations\Environment; use App\Keycloak\Client; use App\Keycloak\Models\KeycloakClientModel; -use App\Keycloak\Realm; -use App\Keycloak\RealmCollection; +use App\Models\EnvironmentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\DB; @@ -30,7 +30,7 @@ public function create(Client ...$clients): void 'integration_id' => $client->integrationId->toString(), 'client_id' => $client->clientId->toString(), 'client_secret' => $client->clientSecret, - 'realm' => $client->realm->publicName, + 'realm' => $client->getRealm()->publicName, ] ); } From 5174ba0db6f1c483eaf3c7e8d53f06eaaffde08d Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:23:50 +0200 Subject: [PATCH 27/36] Fix callback uri not saved - problem with indexes --- app/Domain/Integrations/Integration.php | 5 +++-- tests/Keycloak/Converters/IntegrationUrlConverterTest.php | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Domain/Integrations/Integration.php b/app/Domain/Integrations/Integration.php index 840d43370..7d507fa7e 100644 --- a/app/Domain/Integrations/Integration.php +++ b/app/Domain/Integrations/Integration.php @@ -204,10 +204,11 @@ public function urls(): array */ public function urlsForTypeAndEnvironment(IntegrationUrlType $type, Environment $environment): array { - return array_filter( + // Wrapped this with array_values, so we don't retain the indexes + return array_values(array_filter( $this->urls, fn (IntegrationUrl $url) => $url->type->value === $type->value && $url->environment->value === $environment->value - ); + )); } public function toArray(): array diff --git a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php index 21aa99f65..48535f399 100644 --- a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php @@ -35,7 +35,7 @@ protected function setUp(): void $this->integrationId, Uuid::uuid4(), 'my-secret', - $this->givenAcceptanceRealm() + Environment::Acceptance ); } @@ -59,11 +59,11 @@ public function test_convert_for_first_party(): void { $integration = $this->givenThereIsAnIntegration($this->integrationId, ['partnerStatus' => IntegrationPartnerStatus::FIRST_PARTY]); $integration = $integration->withUrls( - new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Callback, 'https://example.com/callback'), new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Login, 'https://example.com/login1'), new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Login, 'https://example.com/login2'), new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Logout, 'https://example.com/logout1'), new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Logout, 'https://example.com/logout2'), + new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Callback, 'https://example.com/callback'), // These urls below should NOT be shown! new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Production, IntegrationUrlType::Logout, 'https://wrong.com/'), From 83e38d0d2b923dd3c2c78a9da507c19e40d100bc Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:25:39 +0200 Subject: [PATCH 28/36] Make getMissingRealmsByIntegrationId() return Envs, not Realms --- app/Keycloak/Listeners/CreateClients.php | 25 +++++-- .../EloquentKeycloakClientRepository.php | 20 +++--- app/Models/EnvironmentCollection.php | 15 +++++ .../Keycloak/Listeners/CreateClientsTest.php | 65 +++++++------------ 4 files changed, 69 insertions(+), 56 deletions(-) create mode 100644 app/Models/EnvironmentCollection.php diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index 3a3906d30..f03bbed7e 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -14,6 +14,7 @@ use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; +use App\Models\EnvironmentCollection; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Psr\Log\LoggerInterface; @@ -41,17 +42,19 @@ public function handleCreateClients(IntegrationCreated $event): void public function handleCreatingMissingClients(MissingClientsDetected $event): void { - $missingRealms = $this->keycloakClientRepository->getMissingRealmsByIntegrationId($event->id); + $missingEnvironments = $this->keycloakClientRepository->getMissingEnvironmentsByIntegrationId($event->id); - if (count($missingRealms) === 0) { + if (count($missingEnvironments) === 0) { $this->logger->info($event->id . ' - already has all Keycloak clients'); return; } - $this->handle($event, $missingRealms); + $this->handle( + $event, + $this->convertMissingEnvironmentsToMissingRealms($missingEnvironments) + ); } - private function handle(IntegrationCreated|MissingClientsDetected $event, RealmCollection $realms): void { $clients = $this->createClientsInKeycloak( @@ -100,4 +103,18 @@ public function failed(IntegrationCreated|MissingClientsDetected $integrationCre 'exception' => $throwable, ]); } + + private function convertMissingEnvironmentsToMissingRealms(EnvironmentCollection $missingEnvironments): RealmCollection + { + $missingEnvValues = array_column($missingEnvironments->toArray(), 'value'); + + $missingRealms = new RealmCollection(); + foreach ($this->realms as $realm) { + if (in_array($realm->environment->value, $missingEnvValues, true)) { + $missingRealms->add($realm); + } + } + + return $missingRealms; + } } diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index 7bc6c1819..d47557bbb 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -75,25 +75,25 @@ public function getByIntegrationIds(array $integrationIds): array ->toArray(); } - public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): RealmCollection + public function getMissingEnvironmentsByIntegrationId(UuidInterface $integrationId): EnvironmentCollection { $clients = $this->getByIntegrationId($integrationId); - $realms = RealmCollection::build(); + $environments = Environment::cases(); - if (count($clients) === $realms->count()) { - return new RealmCollection(); + if (count($clients) === count($environments)) { + return new EnvironmentCollection(); } - $existingRealms = array_map( - static fn (Client $client) => $client->realm, + $existingEnvironments = array_map( + static fn (Client $client) => $client->environment, $clients ); - return new RealmCollection(array_udiff( - $realms->toArray(), - $existingRealms, - fn (Realm $t1, Realm $t2) => strcmp($t1->publicName, $t2->publicName) + return new EnvironmentCollection(array_udiff( + $environments, + $existingEnvironments, + static fn (Environment $t1, Environment $t2) => strcmp($t1->value, $t2->value) )); } } diff --git a/app/Models/EnvironmentCollection.php b/app/Models/EnvironmentCollection.php new file mode 100644 index 000000000..04a5f35d2 --- /dev/null +++ b/app/Models/EnvironmentCollection.php @@ -0,0 +1,15 @@ + + */ +final class EnvironmentCollection extends Collection +{ +} diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 3333dd0a8..85f0c7e91 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -4,6 +4,7 @@ namespace Tests\Keycloak\Listeners; +use App\Domain\Integrations\Environment; use App\Domain\Integrations\Events\IntegrationCreated; use App\Domain\Integrations\Integration; use App\Domain\Integrations\Repositories\IntegrationRepository; @@ -15,6 +16,7 @@ use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; +use App\Models\EnvironmentCollection; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; @@ -81,7 +83,7 @@ public function test_create_client_for_integration(): void $this->integration->id, Uuid::uuid4(), self::SECRET, - $realm + $realm->environment ); } @@ -125,26 +127,17 @@ function (Realm $realm, Integration $integrationArgument) use ($clients) { ->method('create') ->with(... $clients); - //Check if clients where created for all realms - $realmHits = []; - $this->logger->expects($this->exactly($this->realms->count())) ->method('info') - ->willReturnCallback(function ($message, $options) use (&$realmHits) { + ->willReturnCallback(function ($message, $options) { $this->assertEquals('Keycloak client created', $message); $this->assertArrayHasKey('integration_id', $options); $this->assertArrayHasKey('realm', $options); $this->assertEquals($this->integration->id->toString(), $options['integration_id']); - - $realmHits[$options['realm']] = true; }); $this->handler->handleCreateClients(new IntegrationCreated($this->integration->id)); - - foreach ($this->realms as $realm) { - $this->assertArrayHasKey($realm->internalName, $realmHits, 'Client was not created for realm ' . $realm->internalName); - } } public function test_failed(): void @@ -166,51 +159,48 @@ public function test_handle_creating_missing_clients(): void { $clients = []; - $missingRealms = new RealmCollection([$this->givenTestRealm()]); + $missingEnvironments = new EnvironmentCollection([Environment::Testing]); $this->keycloakClientRepository->expects($this->once()) - ->method('getMissingRealmsByIntegrationId') + ->method('getMissingEnvironmentsByIntegrationId') ->with($this->integration->id) - ->willReturn($missingRealms); + ->willReturn($missingEnvironments); - foreach ($missingRealms as $realm) { - $clients[$realm->internalName] = new Client( + foreach ($missingEnvironments as $environment) { + $clients[$environment->value] = new Client( Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, - $realm + $environment ); } - $activeId = null; - $this->apiClient->expects($this->exactly($missingRealms->count())) + $this->apiClient->expects($this->exactly($missingEnvironments->count())) ->method('createClient') ->willReturnCallback( - function (Realm $realm, Integration $integrationArgument) use ($clients, &$activeId) { + function (Realm $realm, Integration $integrationArgument) use ($clients) { $this->assertEquals($this->integration->id, $integrationArgument->id); - $this->assertArrayHasKey($realm->internalName, $clients); - $activeId = $clients[$realm->internalName]->id; - return $clients[$realm->internalName]->id; + $env = $realm->environment->value; + $this->assertArrayHasKey($env, $clients); } ); - $this->apiClient->expects($this->exactly($missingRealms->count())) + $this->apiClient->expects($this->exactly($missingEnvironments->count())) ->method('addScopeToClient') - ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { - $this->assertEquals($activeId, $client->id); + ->willReturnCallback(function (Client $client, UuidInterface $scopeId) { $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); }); - $this->apiClient->expects($this->exactly($missingRealms->count())) + $this->apiClient->expects($this->exactly($missingEnvironments->count())) ->method('fetchClient') ->willReturnCallback( function (Realm $realm, Integration $integrationArgument) use ($clients) { $this->assertEquals($this->integration->id, $integrationArgument->id); - $this->assertArrayHasKey($realm->internalName, $clients); + $this->assertArrayHasKey($realm->environment->value, $clients); - return $clients[$realm->internalName]; + return $clients[$realm->environment->value]; } ); @@ -223,35 +213,26 @@ function (Realm $realm, Integration $integrationArgument) use ($clients) { ->method('create') ->with(... $clients); - //Check if clients where created for all realms - $realmHits = []; - - $this->logger->expects($this->exactly($missingRealms->count())) + $this->logger->expects($this->exactly($missingEnvironments->count())) ->method('info') - ->willReturnCallback(function ($message, $options) use (&$realmHits) { + ->willReturnCallback(function ($message, $options) { $this->assertEquals('Keycloak client created', $message); $this->assertArrayHasKey('integration_id', $options); $this->assertArrayHasKey('realm', $options); $this->assertEquals($this->integration->id->toString(), $options['integration_id']); - - $realmHits[$options['realm']] = true; }); $this->handler->handleCreatingMissingClients(new MissingClientsDetected($this->integration->id)); - - foreach ($missingRealms as $realm) { - $this->assertArrayHasKey($realm->internalName, $realmHits, 'Client was not created for realm ' . $realm->publicName); - } } public function test_handle_creating_missing_clients_no_missing_realms(): void { $integrationId = Uuid::uuid4(); - $this->keycloakClientRepository->method('getMissingRealmsByIntegrationId') + $this->keycloakClientRepository->method('getMissingEnvironmentsByIntegrationId') ->with($integrationId) - ->willReturn(new RealmCollection()); + ->willReturn(new EnvironmentCollection()); $this->logger->expects($this->once()) ->method('info') From 52ab0098e2d3d105a5e5e59e63ef4bda8825c888 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:27:49 +0200 Subject: [PATCH 29/36] Fix constructor Client with Env param --- .../Converters/IntegrationUrlConverter.php | 6 +- app/Keycloak/Listeners/UpdateClients.php | 2 +- app/Keycloak/Models/KeycloakClientModel.php | 6 +- app/Keycloak/RealmCollection.php | 1 - .../Repositories/KeycloakClientRepository.php | 2 +- app/Nova/Resources/KeycloakClient.php | 4 +- .../CachedKeycloakClientStatusTest.php | 3 +- .../Keycloak/Client/KeycloakApiClientTest.php | 11 ++-- tests/Keycloak/ClientTest.php | 2 +- .../Keycloak/Jobs/BlockClientHandlerTest.php | 5 +- .../Jobs/UnblockClientHandlerTest.php | 5 +- tests/Keycloak/Listeners/BlockClientsTest.php | 2 +- .../Keycloak/Listeners/UpdateClientsTest.php | 4 +- .../EloquentKeycloakClientRepositoryTest.php | 66 +++++++++---------- .../Keycloak/BlockKeycloakClientGuardTest.php | 3 +- .../UnblockKeycloakClientGuardTest.php | 3 +- 16 files changed, 61 insertions(+), 64 deletions(-) diff --git a/app/Keycloak/Converters/IntegrationUrlConverter.php b/app/Keycloak/Converters/IntegrationUrlConverter.php index bf739a3a5..2ef4cfbc9 100644 --- a/app/Keycloak/Converters/IntegrationUrlConverter.php +++ b/app/Keycloak/Converters/IntegrationUrlConverter.php @@ -37,7 +37,7 @@ public static function convert(Integration $integration, Client $client): array private static function buildCallbackUrl(Integration $integration, Client $client, array $urls): array { - $callbackUrl = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Callback, $client->realm->environment); + $callbackUrl = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Callback, $client->environment); if (isset($callbackUrl[0]) && $callbackUrl[0] instanceof IntegrationUrl) { $urls['baseUrl'] = $callbackUrl[0]->url; } @@ -47,7 +47,7 @@ private static function buildCallbackUrl(Integration $integration, Client $clien private static function buildLoginUrls(Integration $integration, Client $client, array $urls): array { - $loginUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Login, $client->realm->environment); + $loginUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Login, $client->environment); foreach ($loginUrls as $loginUrl) { $urls['redirectUris'][] = $loginUrl->url; } @@ -57,7 +57,7 @@ private static function buildLoginUrls(Integration $integration, Client $client, private static function buildLogoutUrls(Integration $integration, Client $client, array $urls): array { - $logoutUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Logout, $client->realm->environment); + $logoutUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Logout, $client->environment); $urls['attributes']['post.logout.redirect.uris'] = implode('#', array_map(static fn ($url) => $url->url, $logoutUrls)); return $urls; diff --git a/app/Keycloak/Listeners/UpdateClients.php b/app/Keycloak/Listeners/UpdateClients.php index 0d8cafab4..6588f7797 100644 --- a/app/Keycloak/Listeners/UpdateClients.php +++ b/app/Keycloak/Listeners/UpdateClients.php @@ -78,7 +78,7 @@ private function updateClient(Integration $integration, Client $keycloakClient, $this->logger->info('Keycloak client updated', [ 'integration_id' => $integration->id->toString(), 'client_id' => $keycloakClient->clientId->toString(), - 'realm' => $keycloakClient->realm->internalName, + 'environment' => $keycloakClient->environment->value, ]); } } diff --git a/app/Keycloak/Models/KeycloakClientModel.php b/app/Keycloak/Models/KeycloakClientModel.php index 3d52ac75a..c05e49dee 100644 --- a/app/Keycloak/Models/KeycloakClientModel.php +++ b/app/Keycloak/Models/KeycloakClientModel.php @@ -4,9 +4,9 @@ namespace App\Keycloak\Models; +use App\Domain\Integrations\Environment; use App\Keycloak\Client; use App\Domain\Integrations\Models\IntegrationModel; -use App\Keycloak\RealmCollection; use App\Models\UuidModel; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; @@ -28,14 +28,12 @@ final class KeycloakClientModel extends UuidModel public function toDomain(): Client { - $realms = RealmCollection::build(); - return new Client( Uuid::fromString($this->id), Uuid::fromString($this->integration_id), Uuid::fromString($this->client_id), $this->client_secret, - $realms->fromPublicName($this->realm) + Environment::from(mb_strtolower($this->realm)) ); } diff --git a/app/Keycloak/RealmCollection.php b/app/Keycloak/RealmCollection.php index 8774b9d99..347972d66 100644 --- a/app/Keycloak/RealmCollection.php +++ b/app/Keycloak/RealmCollection.php @@ -6,7 +6,6 @@ use App\Domain\Integrations\Environment; use Illuminate\Support\Collection; -use InvalidArgumentException; /** * @extends Collection diff --git a/app/Keycloak/Repositories/KeycloakClientRepository.php b/app/Keycloak/Repositories/KeycloakClientRepository.php index c87003c55..49c040a22 100644 --- a/app/Keycloak/Repositories/KeycloakClientRepository.php +++ b/app/Keycloak/Repositories/KeycloakClientRepository.php @@ -5,7 +5,7 @@ namespace App\Keycloak\Repositories; use App\Keycloak\Client; -use App\Keycloak\RealmCollection; +use App\Models\EnvironmentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Ramsey\Uuid\UuidInterface; diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index f32d3c1f1..aa063c55d 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -83,9 +83,7 @@ public function fields(NovaRequest $request): array Text::make('client_secret') ->readonly(), Text::make('Open', function (KeycloakClientModel $model) { - $baseUrl = $model->toDomain()->realm->baseUrl; - - return sprintf('Open in Keycloak', $model->toDomain()->getKeycloakUrl($baseUrl)); + return sprintf('Open in Keycloak', $model->toDomain()->getKeycloakUrl()); })->asHtml(), ]; } diff --git a/tests/Keycloak/CachedKeycloakClientStatusTest.php b/tests/Keycloak/CachedKeycloakClientStatusTest.php index 61a3ec3dc..0ad024beb 100644 --- a/tests/Keycloak/CachedKeycloakClientStatusTest.php +++ b/tests/Keycloak/CachedKeycloakClientStatusTest.php @@ -4,6 +4,7 @@ namespace Tests\Keycloak; +use App\Domain\Integrations\Environment; use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; @@ -30,7 +31,7 @@ protected function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->cachedKeycloakClientStatus = new CachedKeycloakClientStatus($this->apiClient, new NullLogger()); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $this->givenAcceptanceRealm()); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Environment::Acceptance); } public function test_does_cache_layer_work(): void diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index 50640f67e..48c83a757 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -4,6 +4,7 @@ namespace Tests\Keycloak\Client; +use App\Domain\Integrations\Environment; use App\Domain\Integrations\Integration; use App\Keycloak\Client; use App\Keycloak\Client\KeycloakApiClient; @@ -131,7 +132,7 @@ public function test_fails_to_add_scope_to_client(): void $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_ADD_SCOPE_WITH_RESPONSE); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $this->realm); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); $apiClient->addScopeToClient( $client, @@ -173,7 +174,7 @@ public function test_can_fetch_client(): void $this->assertEquals(self::INTEGRATION_ID, $client->integrationId->toString()); $this->assertEquals($clientId, $client->clientId->toString()); $this->assertEquals(self::SECRET, $client->clientSecret); - $this->assertEquals($this->realm, $client->realm); + $this->assertEquals($this->realm->environment, $client->environment); } public function test_client_not_found(): void @@ -210,7 +211,7 @@ public function test_fetch_is_client_enabled(bool $enabled): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $this->givenAcceptanceRealm()); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); $this->assertEquals($enabled, $apiClient->fetchIsClientActive($client)); } @@ -236,7 +237,7 @@ public function test_update_client_throws_exception_when_api_call_fails(): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $this->realm); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_UPDATE_CLIENT); @@ -257,7 +258,7 @@ public function test_reset_scopes_throws_exception_when_api_call_fails(): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $this->realm); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_RESET_SCOPE); diff --git a/tests/Keycloak/ClientTest.php b/tests/Keycloak/ClientTest.php index 8f4bedc0b..ceffa834d 100644 --- a/tests/Keycloak/ClientTest.php +++ b/tests/Keycloak/ClientTest.php @@ -28,7 +28,7 @@ public function test_create_from_json(): void $this->assertEquals($data['secret'], $client->clientSecret); $this->assertEquals($data['clientId'], $client->clientId); $this->assertEquals($integrationId, $client->integrationId); - $this->assertEquals($this->givenTestRealm(), $client->realm); + $this->assertEquals($this->givenTestRealm()->environment, $client->environment); } public function test_throws_when_missing_secret(): void diff --git a/tests/Keycloak/Jobs/BlockClientHandlerTest.php b/tests/Keycloak/Jobs/BlockClientHandlerTest.php index a6c12a848..14e1031e6 100644 --- a/tests/Keycloak/Jobs/BlockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/BlockClientHandlerTest.php @@ -4,6 +4,7 @@ namespace Tests\Keycloak\Jobs; +use App\Domain\Integrations\Environment; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; use App\Keycloak\Events\ClientBlocked; @@ -32,7 +33,7 @@ public function test_block_client_handler(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - $this->givenAcceptanceRealm() + Environment::Acceptance ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); @@ -64,7 +65,7 @@ public function test_handler_fails_when_client_does_not_exists(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - $this->givenAcceptanceRealm() + Environment::Acceptance ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); diff --git a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php index 2ab32bfa0..5f55d3f31 100644 --- a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php @@ -4,6 +4,7 @@ namespace Tests\Keycloak\Jobs; +use App\Domain\Integrations\Environment; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; use App\Keycloak\Events\ClientUnblocked; @@ -33,7 +34,7 @@ public function test_unblock_client_handler(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - $this->givenAcceptanceRealm() + Environment::Acceptance ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); @@ -65,7 +66,7 @@ public function test_handler_fails_when_client_does_not_exists(): void Uuid::uuid4(), Uuid::uuid4(), 'client-secret-1', - $this->givenAcceptanceRealm() + Environment::Acceptance ); $keycloakClientRepository = $this->createMock(KeycloakClientRepository::class); diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index c5a2c7664..5426d242d 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -55,7 +55,7 @@ public function test_block_clients_when_integration_is_blocked(): void $clients = []; foreach ($this->givenAllRealms() as $realm) { - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $realm->environment); $clients[$client->id->toString()] = $client; } diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index 131fd1406..441f94b31 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -82,7 +82,7 @@ public function test_update_client_for_integration(): void $clients = []; foreach ($this->realms as $realm) { $id = Uuid::uuid4(); - $clients[$id->toString()] = new Client($id, $this->integration->id, Uuid::uuid4(), self::SECRET, $realm); + $clients[$id->toString()] = new Client($id, $this->integration->id, Uuid::uuid4(), self::SECRET, $realm->environment); } $activeId = null; // Which client are we updating? @@ -125,7 +125,7 @@ public function test_update_client_for_integration(): void $this->assertEquals([ 'integration_id' => $this->integration->id->toString(), - 'realm' => $client->realm->internalName, + 'environment' => $client->environment->value, 'client_id' => $client->clientId->toString(), ], $params); }); diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index 2b9af0a19..398fddfed 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -6,7 +6,6 @@ use App\Domain\Integrations\Environment; use App\Keycloak\Client; -use App\Keycloak\Realm; use App\Keycloak\RealmCollection; use App\Keycloak\Repositories\EloquentKeycloakClientRepository; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -42,14 +41,14 @@ public function test_it_can_save_one_or_more_clients(): void $integrationId, $clientId, 'client-secret-1', - $this->givenAcceptanceRealm() + Environment::Acceptance ); $client2 = new Client( Uuid::uuid4(), $integrationId, $clientId2, 'client-secret-2', - $this->givenTestRealm() + Environment::Testing ); $this->repository->create($client1, $client2); @@ -57,21 +56,18 @@ public function test_it_can_save_one_or_more_clients(): void 'integration_id' => $integrationId->toString(), 'client_secret' => 'client-secret-1', 'client_id' => $clientId->toString(), - 'realm' => $this->givenAcceptanceRealm()->publicName, + 'realm' => Environment::Acceptance->value, ]); $this->assertDatabaseHas('keycloak_clients', [ 'integration_id' => $integrationId->toString(), 'client_secret' => 'client-secret-2', 'client_id' => $clientId2->toString(), - 'realm' => $this->givenTestRealm()->publicName, + 'realm' => Environment::Testing->value, ]); } public function test_it_can_get_all_clients_for_an_integration_id(): void { - /** @var Realm $realm */ - $realm = $this->realms->first(); - $integrationId = Uuid::uuid4(); $clientId = Uuid::uuid4(); $clientId2 = Uuid::uuid4(); @@ -81,14 +77,14 @@ public function test_it_can_get_all_clients_for_an_integration_id(): void $integrationId, $clientId, 'client-secret-1', - $realm + Environment::Acceptance ); $client2 = new Client( Uuid::uuid4(), $integrationId, $clientId2, 'client-secret-1', - $this->givenAcceptanceRealm() + Environment::Testing ); $this->repository->create($client1, $client2); @@ -98,11 +94,7 @@ public function test_it_can_get_all_clients_for_an_integration_id(): void sort($expected); sort($actual); - foreach ($actual as $i => $client) { - $this->assertEquals($expected[$i]->clientId, $client->clientId); - $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); - $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); - } + $this->assertClientMatches($actual, $expected); } public function test_it_can_get_all_clients_for_multiple_integration_ids(): void @@ -111,7 +103,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void $secondIntegrationId = Uuid::uuid4(); $integrationIds = [$firstIntegrationId, $secondIntegrationId]; - $realms = [$this->givenAcceptanceRealm(), $this->givenTestRealm()]; + $realms = new RealmCollection([$this->givenAcceptanceRealm(), $this->givenTestRealm()]); $clients = []; @@ -124,7 +116,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void $integrationId, Uuid::uuid4(), 'client-secret-' . $count, - $realm + $realm->environment ); } } @@ -138,11 +130,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void sort($expected); sort($actual); - foreach ($actual as $i => $client) { - $this->assertEquals($expected[$i]->clientId, $client->clientId); - $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); - $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); - } + $this->assertClientMatches($actual, $expected); } public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void @@ -161,7 +149,7 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void $integrationId, Uuid::uuid4(), 'client-secret-' . $count, - $realm + $realm->environment ); } } @@ -180,11 +168,7 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void sort($expected); sort($actual); - foreach ($actual as $i => $client) { - $this->assertEquals($expected[$i]->clientId, $client->clientId); - $this->assertEquals($expected[$i]->clientSecret, $client->clientSecret); - $this->assertEquals($expected[$i]->realm->publicName, $client->realm->publicName); - } + $this->assertClientMatches($actual, $expected); } public function test_it_can_get_missing_realms_by_integration_id(): void @@ -201,18 +185,30 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $integrationId, Uuid::uuid4(), 'client-secret', - $realm + $realm->environment, ); } $this->repository->create(...$clients); - $missingRealms = $this->repository->getMissingRealmsByIntegrationId($integrationId); + $missingEnvironments = $this->repository->getMissingEnvironmentsByIntegrationId($integrationId); - $this->assertCount(2, $missingRealms); - $this->assertInstanceOf(Realm::class, $missingRealms->get(1)); - $this->assertInstanceOf(Realm::class, $missingRealms->get(2)); + $this->assertCount(2, $missingEnvironments); + $this->assertInstanceOf(Environment::class, $missingEnvironments->get(1)); + $this->assertInstanceOf(Environment::class, $missingEnvironments->get(2)); - $this->assertEquals(Environment::Testing->value, mb_strtolower($missingRealms->get(1)->publicName)); - $this->assertEquals(Environment::Production->value, mb_strtolower($missingRealms->get(2)->publicName)); + $this->assertEquals(Environment::Testing->value, $missingEnvironments->get(1)->value); + $this->assertEquals(Environment::Production->value, $missingEnvironments->get(2)->value); + } + + + private function assertClientMatches(array $actual, array $expected): void + { + foreach ($actual as $i => $client) { + $expectedClient = $expected[$i]; + $this->assertInstanceOf(Client::class, $expectedClient); + $this->assertEquals($expectedClient->clientId, $client->clientId); + $this->assertEquals($expectedClient->clientSecret, $client->clientSecret); + $this->assertEquals($expectedClient->environment, $client->environment); + } } } diff --git a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php index c4e937718..42fb744a6 100644 --- a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php @@ -4,6 +4,7 @@ namespace Tests\Nova\ActionGuards\Keycloak; +use App\Domain\Integrations\Environment; use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; @@ -35,7 +36,7 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new BlockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $this->givenAcceptanceRealm()); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Environment::Acceptance); } #[DataProvider('dataProvider')] diff --git a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php index d2b4046be..41a8f7903 100644 --- a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php @@ -4,6 +4,7 @@ namespace Tests\Nova\ActionGuards\Keycloak; +use App\Domain\Integrations\Environment; use App\Keycloak\CachedKeycloakClientStatus; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; @@ -33,7 +34,7 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new UnblockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', $this->givenAcceptanceRealm()); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Environment::Acceptance); } #[DataProvider('dataProvider')] From fdf141884a257ef3ae25dbd9f9210d5ec509491f Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:28:40 +0200 Subject: [PATCH 30/36] Add RealmCollection to singleton service container, cleanup RealmCollection --- app/Keycloak/KeycloakServiceProvider.php | 4 ++++ app/Keycloak/RealmCollection.php | 23 ----------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/app/Keycloak/KeycloakServiceProvider.php b/app/Keycloak/KeycloakServiceProvider.php index 4db618cab..14a39151d 100644 --- a/app/Keycloak/KeycloakServiceProvider.php +++ b/app/Keycloak/KeycloakServiceProvider.php @@ -58,6 +58,10 @@ public function register(): void return $this->app->get(EloquentKeycloakClientRepository::class); }); + $this->app->singleton(RealmCollection::class, function () { + return RealmCollection::build(); + }); + $this->app->singleton(CachedKeycloakClientStatus::class, function () { return new CachedKeycloakClientStatus( App::get(ApiClient::class), diff --git a/app/Keycloak/RealmCollection.php b/app/Keycloak/RealmCollection.php index 347972d66..69e5a46ce 100644 --- a/app/Keycloak/RealmCollection.php +++ b/app/Keycloak/RealmCollection.php @@ -34,27 +34,4 @@ public static function build(): RealmCollection return $realms; } - - public function fromPublicName(string $publicName): Realm - { - foreach ($this->all() as $realm) { - if ($realm->publicName === $publicName) { - return $realm; - } - } - - throw new InvalidArgumentException('Invalid realm: ' . $publicName); - } - - /** - * @return array - */ - public function asArray(): array - { - $output = []; - foreach ($this->all() as $realm) { - $output[$realm->publicName] = $realm->publicName; - } - return $output; - } } From 752f8151da857164ede6a977bffb4befa66fe45f Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 14:32:25 +0200 Subject: [PATCH 31/36] Change return type RealmCollection --- app/Keycloak/Repositories/KeycloakClientRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Keycloak/Repositories/KeycloakClientRepository.php b/app/Keycloak/Repositories/KeycloakClientRepository.php index 49c040a22..37baab3bd 100644 --- a/app/Keycloak/Repositories/KeycloakClientRepository.php +++ b/app/Keycloak/Repositories/KeycloakClientRepository.php @@ -30,5 +30,5 @@ public function getById(UuidInterface $id): Client; */ public function getByIntegrationIds(array $integrationIds): array; - public function getMissingRealmsByIntegrationId(UuidInterface $integrationId): RealmCollection; + public function getMissingEnvironmentsByIntegrationId(UuidInterface $integrationId): EnvironmentCollection; } From 380ea7a535a9332a6dc691df7829fb530a2d8ea5 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Thu, 6 Jun 2024 15:25:25 +0200 Subject: [PATCH 32/36] Expose keycloak clients to Api --- app/Domain/Integrations/Integration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Domain/Integrations/Integration.php b/app/Domain/Integrations/Integration.php index 7d507fa7e..034a589bf 100644 --- a/app/Domain/Integrations/Integration.php +++ b/app/Domain/Integrations/Integration.php @@ -228,6 +228,7 @@ public function toArray(): array 'organization' => $this->organization, 'authClients' => $this->auth0Clients, 'legacyAuthConsumers' => $this->uiTiDv1Consumers, + 'keycloakClients' => $this->keycloakClients, 'subscription' => $this->subscription, 'website' => $this->website->value ?? null, 'coupon' => $this->coupon ?? null, From 23feed6aefc6953b1505cd90709ffb0f1cadb648 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Fri, 7 Jun 2024 10:13:44 +0200 Subject: [PATCH 33/36] Change Realm & Env Collection name --- .../Integrations/Environments.php} | 5 ++--- app/Keycloak/Client.php | 4 ++-- app/Keycloak/KeycloakServiceProvider.php | 4 ++-- app/Keycloak/Listeners/CreateClients.php | 14 +++++++------- app/Keycloak/{RealmCollection.php => Realms.php} | 11 +++-------- .../EloquentKeycloakClientRepository.php | 8 ++++---- .../Repositories/KeycloakClientRepository.php | 4 ++-- tests/Keycloak/Listeners/CreateClientsTest.php | 10 +++++----- tests/Keycloak/Listeners/UpdateClientsTest.php | 4 ++-- tests/Keycloak/RealmFactory.php | 6 +++--- .../EloquentKeycloakClientRepositoryTest.php | 10 +++++----- 11 files changed, 37 insertions(+), 43 deletions(-) rename app/{Models/EnvironmentCollection.php => Domain/Integrations/Environments.php} (51%) rename app/Keycloak/{RealmCollection.php => Realms.php} (59%) diff --git a/app/Models/EnvironmentCollection.php b/app/Domain/Integrations/Environments.php similarity index 51% rename from app/Models/EnvironmentCollection.php rename to app/Domain/Integrations/Environments.php index 04a5f35d2..0d761e130 100644 --- a/app/Models/EnvironmentCollection.php +++ b/app/Domain/Integrations/Environments.php @@ -2,14 +2,13 @@ declare(strict_types=1); -namespace App\Models; +namespace App\Domain\Integrations; -use App\Domain\Integrations\Environment; use Illuminate\Support\Collection; /** * @extends Collection */ -final class EnvironmentCollection extends Collection +final class Environments extends Collection { } diff --git a/app/Keycloak/Client.php b/app/Keycloak/Client.php index 0793b1555..51ac5cd8e 100644 --- a/app/Keycloak/Client.php +++ b/app/Keycloak/Client.php @@ -48,8 +48,8 @@ public function getKeycloakUrl(): string public function getRealm(): Realm { - /** @var RealmCollection $realmCollection */ - $realmCollection = App::get(RealmCollection::class); + /** @var Realms $realmCollection */ + $realmCollection = App::get(Realms::class); foreach ($realmCollection as $realm) { if ($realm->environment === $this->environment) { diff --git a/app/Keycloak/KeycloakServiceProvider.php b/app/Keycloak/KeycloakServiceProvider.php index 14a39151d..b2d1fc373 100644 --- a/app/Keycloak/KeycloakServiceProvider.php +++ b/app/Keycloak/KeycloakServiceProvider.php @@ -58,8 +58,8 @@ public function register(): void return $this->app->get(EloquentKeycloakClientRepository::class); }); - $this->app->singleton(RealmCollection::class, function () { - return RealmCollection::build(); + $this->app->singleton(Realms::class, function () { + return Realms::build(); }); $this->app->singleton(CachedKeycloakClientStatus::class, function () { diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index f03bbed7e..b18dbff48 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -4,6 +4,7 @@ namespace App\Keycloak\Listeners; +use App\Domain\Integrations\Environments; use App\Domain\Integrations\Events\IntegrationCreated; use App\Domain\Integrations\Integration; use App\Domain\Integrations\Repositories\IntegrationRepository; @@ -11,10 +12,9 @@ use App\Keycloak\ClientCollection; use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Exception\KeyCloakApiFailed; -use App\Keycloak\RealmCollection; +use App\Keycloak\Realms; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; -use App\Models\EnvironmentCollection; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Psr\Log\LoggerInterface; @@ -28,7 +28,7 @@ final class CreateClients implements ShouldQueue public function __construct( private readonly IntegrationRepository $integrationRepository, private readonly KeycloakClientRepository $keycloakClientRepository, - private readonly RealmCollection $realms, + private readonly Realms $realms, private readonly ApiClient $client, private readonly ScopeConfig $scopeConfig, private readonly LoggerInterface $logger @@ -55,7 +55,7 @@ public function handleCreatingMissingClients(MissingClientsDetected $event): voi ); } - private function handle(IntegrationCreated|MissingClientsDetected $event, RealmCollection $realms): void + private function handle(IntegrationCreated|MissingClientsDetected $event, Realms $realms): void { $clients = $this->createClientsInKeycloak( $this->integrationRepository->getById($event->id), @@ -73,7 +73,7 @@ private function handle(IntegrationCreated|MissingClientsDetected $event, RealmC } } - private function createClientsInKeycloak(Integration $integration, RealmCollection $realms): ClientCollection + private function createClientsInKeycloak(Integration $integration, Realms $realms): ClientCollection { $scopeId = $this->scopeConfig->getScopeIdFromIntegrationType($integration); @@ -104,11 +104,11 @@ public function failed(IntegrationCreated|MissingClientsDetected $integrationCre ]); } - private function convertMissingEnvironmentsToMissingRealms(EnvironmentCollection $missingEnvironments): RealmCollection + private function convertMissingEnvironmentsToMissingRealms(Environments $missingEnvironments): Realms { $missingEnvValues = array_column($missingEnvironments->toArray(), 'value'); - $missingRealms = new RealmCollection(); + $missingRealms = new Realms(); foreach ($this->realms as $realm) { if (in_array($realm->environment->value, $missingEnvValues, true)) { $missingRealms->add($realm); diff --git a/app/Keycloak/RealmCollection.php b/app/Keycloak/Realms.php similarity index 59% rename from app/Keycloak/RealmCollection.php rename to app/Keycloak/Realms.php index 69e5a46ce..888be0539 100644 --- a/app/Keycloak/RealmCollection.php +++ b/app/Keycloak/Realms.php @@ -10,18 +10,13 @@ /** * @extends Collection */ -final class RealmCollection extends Collection +final class Realms extends Collection { - public static function build(): RealmCollection + public static function build(): self { - $realms = new RealmCollection(); + $realms = new self(); foreach (config('keycloak.environments') as $publicName => $environment) { - if (empty($environment['internalName']) || empty($environment['base_url']) || empty($environment['client_id']) || empty($environment['client_secret'])) { - // If any of the fields are missing, do not create that realm. - continue; - } - $realms->add(new Realm( $environment['internalName'], ucfirst($publicName), diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index d47557bbb..20e805437 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -5,9 +5,9 @@ namespace App\Keycloak\Repositories; use App\Domain\Integrations\Environment; +use App\Domain\Integrations\Environments; use App\Keycloak\Client; use App\Keycloak\Models\KeycloakClientModel; -use App\Models\EnvironmentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\DB; @@ -75,14 +75,14 @@ public function getByIntegrationIds(array $integrationIds): array ->toArray(); } - public function getMissingEnvironmentsByIntegrationId(UuidInterface $integrationId): EnvironmentCollection + public function getMissingEnvironmentsByIntegrationId(UuidInterface $integrationId): Environments { $clients = $this->getByIntegrationId($integrationId); $environments = Environment::cases(); if (count($clients) === count($environments)) { - return new EnvironmentCollection(); + return new Environments(); } $existingEnvironments = array_map( @@ -90,7 +90,7 @@ public function getMissingEnvironmentsByIntegrationId(UuidInterface $integration $clients ); - return new EnvironmentCollection(array_udiff( + return new Environments(array_udiff( $environments, $existingEnvironments, static fn (Environment $t1, Environment $t2) => strcmp($t1->value, $t2->value) diff --git a/app/Keycloak/Repositories/KeycloakClientRepository.php b/app/Keycloak/Repositories/KeycloakClientRepository.php index 37baab3bd..73716381f 100644 --- a/app/Keycloak/Repositories/KeycloakClientRepository.php +++ b/app/Keycloak/Repositories/KeycloakClientRepository.php @@ -4,8 +4,8 @@ namespace App\Keycloak\Repositories; +use App\Domain\Integrations\Environments; use App\Keycloak\Client; -use App\Models\EnvironmentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Ramsey\Uuid\UuidInterface; @@ -30,5 +30,5 @@ public function getById(UuidInterface $id): Client; */ public function getByIntegrationIds(array $integrationIds): array; - public function getMissingEnvironmentsByIntegrationId(UuidInterface $integrationId): EnvironmentCollection; + public function getMissingEnvironmentsByIntegrationId(UuidInterface $integrationId): Environments; } diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 85f0c7e91..71223cf9f 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -5,6 +5,7 @@ namespace Tests\Keycloak\Listeners; use App\Domain\Integrations\Environment; +use App\Domain\Integrations\Environments; use App\Domain\Integrations\Events\IntegrationCreated; use App\Domain\Integrations\Integration; use App\Domain\Integrations\Repositories\IntegrationRepository; @@ -13,10 +14,9 @@ use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Listeners\CreateClients; use App\Keycloak\Realm; -use App\Keycloak\RealmCollection; +use App\Keycloak\Realms; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; -use App\Models\EnvironmentCollection; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; @@ -40,7 +40,7 @@ final class CreateClientsTest extends TestCase private KeycloakClientRepository&MockObject $keycloakClientRepository; private ApiClient&MockObject $apiClient; private LoggerInterface&MockObject $logger; - private RealmCollection $realms; + private Realms $realms; protected function setUp(): void { @@ -159,7 +159,7 @@ public function test_handle_creating_missing_clients(): void { $clients = []; - $missingEnvironments = new EnvironmentCollection([Environment::Testing]); + $missingEnvironments = new Environments([Environment::Testing]); $this->keycloakClientRepository->expects($this->once()) ->method('getMissingEnvironmentsByIntegrationId') @@ -232,7 +232,7 @@ public function test_handle_creating_missing_clients_no_missing_realms(): void $this->keycloakClientRepository->method('getMissingEnvironmentsByIntegrationId') ->with($integrationId) - ->willReturn(new EnvironmentCollection()); + ->willReturn(new Environments()); $this->logger->expects($this->once()) ->method('info') diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index 441f94b31..95c1041ef 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -13,7 +13,7 @@ use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; use App\Keycloak\Listeners\UpdateClients; -use App\Keycloak\RealmCollection; +use App\Keycloak\Realms; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\ScopeConfig; use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; @@ -42,7 +42,7 @@ final class UpdateClientsTest extends TestCase private ApiClient&MockObject $apiClient; private LoggerInterface&MockObject $logger; private IntegrationRepository&MockObject $integrationRepository; - private RealmCollection $realms; + private Realms $realms; protected function setUp(): void { diff --git a/tests/Keycloak/RealmFactory.php b/tests/Keycloak/RealmFactory.php index 7c7089605..8a6c5ed6f 100644 --- a/tests/Keycloak/RealmFactory.php +++ b/tests/Keycloak/RealmFactory.php @@ -6,13 +6,13 @@ use App\Domain\Integrations\Environment; use App\Keycloak\Realm; -use App\Keycloak\RealmCollection; +use App\Keycloak\Realms; trait RealmFactory { - public function givenAllRealms(): RealmCollection + public function givenAllRealms(): Realms { - return new RealmCollection([ + return new Realms([ $this->givenAcceptanceRealm(), $this->givenTestRealm(), $this->givenProductionRealm(), diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index 398fddfed..3991e9668 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -6,7 +6,7 @@ use App\Domain\Integrations\Environment; use App\Keycloak\Client; -use App\Keycloak\RealmCollection; +use App\Keycloak\Realms; use App\Keycloak\Repositories\EloquentKeycloakClientRepository; use Illuminate\Foundation\Testing\RefreshDatabase; use Ramsey\Uuid\Uuid; @@ -20,7 +20,7 @@ final class EloquentKeycloakClientRepositoryTest extends TestCase use RealmFactory; private EloquentKeycloakClientRepository $repository; - private RealmCollection $realms; + private Realms $realms; protected function setUp(): void { @@ -103,7 +103,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void $secondIntegrationId = Uuid::uuid4(); $integrationIds = [$firstIntegrationId, $secondIntegrationId]; - $realms = new RealmCollection([$this->givenAcceptanceRealm(), $this->givenTestRealm()]); + $realms = new Realms([$this->givenAcceptanceRealm(), $this->givenTestRealm()]); $clients = []; @@ -176,8 +176,8 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $integrationId = Uuid::uuid4(); $clients = []; - $missingRealmCollection = new RealmCollection(); - foreach (new RealmCollection([$this->givenAcceptanceRealm()]) as $realm) { + $missingRealmCollection = new Realms(); + foreach (new Realms([$this->givenAcceptanceRealm()]) as $realm) { $missingRealmCollection->add($realm); $clients[] = new Client( From 87df7a83a19231decc8abe121e44ff1f16d9c506 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Fri, 7 Jun 2024 10:22:31 +0200 Subject: [PATCH 34/36] change client id to string --- app/Keycloak/Client.php | 4 +- app/Keycloak/Client/ApiClient.php | 5 +- app/Keycloak/Client/KeycloakApiClient.php | 41 +++++---- app/Keycloak/ClientId/ClientIdFactory.php | 10 +++ .../ClientId/ClientIdFreeStringStrategy.php | 17 ++++ .../ClientId/ClientIdUuidStrategy.php | 15 ++++ .../IntegrationToKeycloakClientConverter.php | 4 +- app/Keycloak/Listeners/CreateClients.php | 7 +- app/Keycloak/Listeners/UpdateClients.php | 2 +- app/Keycloak/Models/KeycloakClientModel.php | 2 +- .../EloquentKeycloakClientRepository.php | 2 +- app/Nova/Resources/KeycloakClient.php | 2 +- .../CachedKeycloakClientStatusTest.php | 2 +- .../Keycloak/Client/KeycloakApiClientTest.php | 89 +++++-------------- ...tegrationToKeycloakClientConverterTest.php | 4 +- .../IntegrationUrlConverterTest.php | 2 +- .../Keycloak/Jobs/BlockClientHandlerTest.php | 4 +- .../Jobs/UnblockClientHandlerTest.php | 4 +- tests/Keycloak/Listeners/BlockClientsTest.php | 2 +- .../Keycloak/Listeners/CreateClientsTest.php | 43 +++------ .../Keycloak/Listeners/UpdateClientsTest.php | 4 +- .../EloquentKeycloakClientRepositoryTest.php | 18 ++-- .../Keycloak/BlockKeycloakClientGuardTest.php | 2 +- .../UnblockKeycloakClientGuardTest.php | 2 +- 24 files changed, 131 insertions(+), 156 deletions(-) create mode 100644 app/Keycloak/ClientId/ClientIdFactory.php create mode 100644 app/Keycloak/ClientId/ClientIdFreeStringStrategy.php create mode 100644 app/Keycloak/ClientId/ClientIdUuidStrategy.php diff --git a/app/Keycloak/Client.php b/app/Keycloak/Client.php index 51ac5cd8e..eea7a64f3 100644 --- a/app/Keycloak/Client.php +++ b/app/Keycloak/Client.php @@ -15,7 +15,7 @@ public function __construct( public UuidInterface $id, public UuidInterface $integrationId, - public UuidInterface $clientId, + public string $clientId, public string $clientSecret, public Environment $environment, ) { @@ -33,7 +33,7 @@ public static function createFromJson( return new self( Uuid::fromString($data['id']), $integrationId, - Uuid::fromString($data['clientId']), + $data['clientId'], $data['secret'], $realm->environment, ); diff --git a/app/Keycloak/Client/ApiClient.php b/app/Keycloak/Client/ApiClient.php index bf6dea103..2015222ed 100644 --- a/app/Keycloak/Client/ApiClient.php +++ b/app/Keycloak/Client/ApiClient.php @@ -6,17 +6,16 @@ use App\Domain\Integrations\Integration; use App\Keycloak\Client; +use App\Keycloak\ClientId\ClientIdFactory; use App\Keycloak\Realm; use Ramsey\Uuid\UuidInterface; interface ApiClient { - public function createClient(Realm $realm, Integration $integration, UuidInterface $clientId): void; + public function createClient(Realm $realm, Integration $integration, ClientIdFactory $clientIdFactory): Client; public function addScopeToClient(Client $client, UuidInterface $scopeId): void; - public function fetchClient(Realm $realm, Integration $integration, UuidInterface $id): Client; - public function fetchIsClientActive(Client $client): bool; public function unblockClient(Client $client): void; diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index a63e03c5e..4d4e86667 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -7,11 +7,11 @@ use App\Domain\Integrations\Integration; use App\Json; use App\Keycloak\Client; +use App\Keycloak\ClientId\ClientIdFactory; +use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\Realm; use App\Keycloak\ScopeConfig; -use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; -use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Request; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; @@ -30,15 +30,24 @@ public function __construct( /** * @throws KeyCloakApiFailed */ - public function createClient(Realm $realm, Integration $integration, UuidInterface $id): void - { + public function createClient( + Realm $realm, + Integration $integration, + ClientIdFactory $clientIdFactory + ): Client { + $clientId = $clientIdFactory->create(); + try { $response = $this->client->sendWithBearer( new Request( 'POST', sprintf('admin/realms/%s/clients', $realm->internalName), [], - Json::encode(IntegrationToKeycloakClientConverter::convert($id, $integration, Uuid::uuid4())) + Json::encode(IntegrationToKeycloakClientConverter::convert( + Uuid::uuid4(), + $integration, + $clientId + )) ), $realm ); @@ -46,22 +55,13 @@ public function createClient(Realm $realm, Integration $integration, UuidInterfa throw KeyCloakApiFailed::failedToCreateClient($e->getMessage()); } - if ($response->getStatusCode() === 409) { - /* - When using the action "create missing clients" it could be that the client already exists in Keycloak, but not in Publiq Platform. - In this case we do not fail, we will just connect both sides and make sure the scopes are configured correctly. - */ - - $this->logger->info(sprintf('Client %s already exists for realm %s', $integration->name, $realm->publicName)); - - return; - } - if ($response->getStatusCode() !== 201) { throw KeyCloakApiFailed::failedToCreateClientWithResponse($response); } - $this->logger->info(sprintf('Client %s for realm %s created with id %s', $integration->name, $realm->publicName, $id->toString())); + $this->logger->info(sprintf('Client %s for realm %s created with client id %s', $integration->name, $realm->publicName, $clientId)); + + return $this->fetchClient($realm, $integration, $clientId); } /** @@ -77,7 +77,7 @@ public function addScopeToClient(Client $client, UuidInterface $scopeId): void ), $client->getRealm() ); - } catch (GuzzleException $e) { + } catch (Throwable $e) { throw KeyCloakApiFailed::failedToAddScopeToClient($e->getMessage()); } @@ -86,7 +86,6 @@ public function addScopeToClient(Client $client, UuidInterface $scopeId): void } } - public function deleteScopes(Client $client): void { foreach ($this->scopeConfig->getAll() as $scope) { @@ -112,13 +111,13 @@ public function deleteScopes(Client $client): void /** * @throws KeyCloakApiFailed */ - public function fetchClient(Realm $realm, Integration $integration, UuidInterface $id): Client + private function fetchClient(Realm $realm, Integration $integration, string $clientId): Client { try { $response = $this->client->sendWithBearer( new Request( 'GET', - sprintf('admin/realms/%s/clients/%s', $realm->internalName, $id->toString()) + sprintf('admin/realms/%s/clients/%s', $realm->internalName, $clientId) ), $realm ); diff --git a/app/Keycloak/ClientId/ClientIdFactory.php b/app/Keycloak/ClientId/ClientIdFactory.php new file mode 100644 index 000000000..c97c6a43e --- /dev/null +++ b/app/Keycloak/ClientId/ClientIdFactory.php @@ -0,0 +1,10 @@ +string; + } +} diff --git a/app/Keycloak/ClientId/ClientIdUuidStrategy.php b/app/Keycloak/ClientId/ClientIdUuidStrategy.php new file mode 100644 index 000000000..15a65747e --- /dev/null +++ b/app/Keycloak/ClientId/ClientIdUuidStrategy.php @@ -0,0 +1,15 @@ +toString(); + } +} diff --git a/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php b/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php index 46ce5655d..130187425 100644 --- a/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php +++ b/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php @@ -10,12 +10,12 @@ final class IntegrationToKeycloakClientConverter { - public static function convert(UuidInterface $id, Integration $integration, UuidInterface $clientId): array + public static function convert(UuidInterface $id, Integration $integration, string $clientId): array { return [ 'protocol' => 'openid-connect', 'id' => $id->toString(), - 'clientId' => $clientId->toString(), + 'clientId' => $clientId, 'name' => $integration->name, 'description' => $integration->description, 'publicClient' => false, diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index b18dbff48..f2dada0f4 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -10,6 +10,7 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client\ApiClient; use App\Keycloak\ClientCollection; +use App\Keycloak\ClientId\ClientIdUuidStrategy; use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\Realms; @@ -18,7 +19,6 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Psr\Log\LoggerInterface; -use Ramsey\Uuid\Uuid; use Throwable; final class CreateClients implements ShouldQueue @@ -81,10 +81,7 @@ private function createClientsInKeycloak(Integration $integration, Realms $realm foreach ($realms as $realm) { try { - $id = Uuid::uuid4(); - - $this->client->createClient($realm, $integration, $id); - $client = $this->client->fetchClient($realm, $integration, $id); + $client = $this->client->createClient($realm, $integration, new ClientIdUuidStrategy()); $this->client->addScopeToClient($client, $scopeId); $clientCollection->add($client); diff --git a/app/Keycloak/Listeners/UpdateClients.php b/app/Keycloak/Listeners/UpdateClients.php index 6588f7797..425d5d2b1 100644 --- a/app/Keycloak/Listeners/UpdateClients.php +++ b/app/Keycloak/Listeners/UpdateClients.php @@ -77,7 +77,7 @@ private function updateClient(Integration $integration, Client $keycloakClient, $this->logger->info('Keycloak client updated', [ 'integration_id' => $integration->id->toString(), - 'client_id' => $keycloakClient->clientId->toString(), + 'client_id' => $keycloakClient->clientId, 'environment' => $keycloakClient->environment->value, ]); } diff --git a/app/Keycloak/Models/KeycloakClientModel.php b/app/Keycloak/Models/KeycloakClientModel.php index c05e49dee..a786b7b9a 100644 --- a/app/Keycloak/Models/KeycloakClientModel.php +++ b/app/Keycloak/Models/KeycloakClientModel.php @@ -31,7 +31,7 @@ public function toDomain(): Client return new Client( Uuid::fromString($this->id), Uuid::fromString($this->integration_id), - Uuid::fromString($this->client_id), + $this->client_id, $this->client_secret, Environment::from(mb_strtolower($this->realm)) ); diff --git a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php index 20e805437..bc727624f 100644 --- a/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php +++ b/app/Keycloak/Repositories/EloquentKeycloakClientRepository.php @@ -28,7 +28,7 @@ public function create(Client ...$clients): void [ 'id' => $client->id->toString(), 'integration_id' => $client->integrationId->toString(), - 'client_id' => $client->clientId->toString(), + 'client_id' => $client->clientId, 'client_secret' => $client->clientSecret, 'realm' => $client->getRealm()->publicName, ] diff --git a/app/Nova/Resources/KeycloakClient.php b/app/Nova/Resources/KeycloakClient.php index aa063c55d..accee8e65 100644 --- a/app/Nova/Resources/KeycloakClient.php +++ b/app/Nova/Resources/KeycloakClient.php @@ -77,7 +77,7 @@ public function fields(NovaRequest $request): array return 'Active'; })->asHtml(), Text::make('client_id', function (KeycloakClientModel $model) { - return $model->toDomain()->clientId->toString(); + return $model->toDomain()->clientId; }) ->readonly(), Text::make('client_secret') diff --git a/tests/Keycloak/CachedKeycloakClientStatusTest.php b/tests/Keycloak/CachedKeycloakClientStatusTest.php index 0ad024beb..cf356a3d8 100644 --- a/tests/Keycloak/CachedKeycloakClientStatusTest.php +++ b/tests/Keycloak/CachedKeycloakClientStatusTest.php @@ -31,7 +31,7 @@ protected function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->cachedKeycloakClientStatus = new CachedKeycloakClientStatus($this->apiClient, new NullLogger()); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Environment::Acceptance); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4()->toString(), 'client-id-1', Environment::Acceptance); } public function test_does_cache_layer_work(): void diff --git a/tests/Keycloak/Client/KeycloakApiClientTest.php b/tests/Keycloak/Client/KeycloakApiClientTest.php index 48c83a757..d54065875 100644 --- a/tests/Keycloak/Client/KeycloakApiClientTest.php +++ b/tests/Keycloak/Client/KeycloakApiClientTest.php @@ -8,6 +8,8 @@ use App\Domain\Integrations\Integration; use App\Keycloak\Client; use App\Keycloak\Client\KeycloakApiClient; +use App\Keycloak\ClientId\ClientIdFreeStringStrategy; +use App\Keycloak\ClientId\ClientIdUuidStrategy; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\Realm; use App\Keycloak\ScopeConfig; @@ -27,7 +29,6 @@ final class KeycloakApiClientTest extends TestCase use CreatesIntegration; use RealmFactory; - private const INTEGRATION_ID = '824c09c0-2f3a-4fa0-bde2-8bf25c9a5b74'; private const UUID = '824c09c0-2f3a-4fa0-bde2-8bf25c9a5b74'; public const SECRET = 'abra_kadabra'; @@ -56,10 +57,21 @@ protected function setUp(): void public function test_can_create_client(): void { - $id = Uuid::uuid4(); + $clientId = Uuid::uuid4()->toString(); + $mock = new MockHandler([ new Response(200, [], json_encode(['access_token' => self::TOKEN], JSON_THROW_ON_ERROR)), new Response(201), + new Response(200, [], json_encode( + [ + 'id' => self::UUID, + 'clientId' => $clientId, + 'name' => 'test client', + 'secret' => self::SECRET, + 'enabled' => true, + ], + JSON_THROW_ON_ERROR + )), ]); $apiClient = new KeycloakApiClient( @@ -71,13 +83,13 @@ public function test_can_create_client(): void $counter = 0; $this->logger->expects($this->exactly(2)) ->method('info') - ->willReturnCallback(function ($message) use (&$counter, $id) { + ->willReturnCallback(function ($message) use (&$counter, $clientId) { switch ($counter++) { case 0: $this->assertEquals('Fetched token for php_client, token starts with ' . substr(self::TOKEN, 0, 6), $message); break; case 1: - $this->assertEquals(sprintf('Client %s for realm %s created with id %s', $this->integration->name, $this->realm->publicName, $id->toString()), $message); + $this->assertEquals(sprintf('Client %s for realm %s created with client id %s', $this->integration->name, $this->realm->publicName, $clientId), $message); break; default: $this->fail('Unknown message logged: ' . $message); @@ -87,7 +99,7 @@ public function test_can_create_client(): void $apiClient->createClient( $this->realm, $this->integration, - $id, + new ClientIdFreeStringStrategy($clientId) ); } @@ -110,7 +122,7 @@ public function test_fails_to_create_client(): void $apiClient->createClient( $this->realm, $this->integration, - Uuid::uuid4(), + new ClientIdUuidStrategy(), ); } @@ -132,7 +144,7 @@ public function test_fails_to_add_scope_to_client(): void $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_ADD_SCOPE_WITH_RESPONSE); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4()->toString(), self::SECRET, Environment::Acceptance); $apiClient->addScopeToClient( $client, @@ -140,63 +152,6 @@ public function test_fails_to_add_scope_to_client(): void ); } - public function test_can_fetch_client(): void - { - $clientId = Uuid::uuid4(); - - $mock = new MockHandler([ - new Response(200, [], json_encode(['access_token' => self::TOKEN], JSON_THROW_ON_ERROR)), - new Response(200, [], json_encode( - [ - 'id' => self::UUID, - 'clientId' => $clientId, - 'name' => 'test client', - 'secret' => self::SECRET, - 'enabled' => true, - ], - JSON_THROW_ON_ERROR - )), - ]); - - $apiClient = new KeycloakApiClient( - $this->givenKeycloakHttpClient($this->logger, $mock), - $this->scopeConfig, - $this->logger - ); - - $client = $apiClient->fetchClient( - $this->realm, - $this->givenThereIsAnIntegration(Uuid::fromString(self::INTEGRATION_ID)), - $clientId - ); - - $this->assertEquals(self::UUID, $client->id->toString()); - $this->assertEquals(self::INTEGRATION_ID, $client->integrationId->toString()); - $this->assertEquals($clientId, $client->clientId->toString()); - $this->assertEquals(self::SECRET, $client->clientSecret); - $this->assertEquals($this->realm->environment, $client->environment); - } - - public function test_client_not_found(): void - { - $mock = new MockHandler([ - new Response(200, [], json_encode(['access_token' => self::TOKEN], JSON_THROW_ON_ERROR)), - new Response(500, [], 'It is broken'), - ]); - - $this->expectException(KeyCloakApiFailed::class); - $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_FETCH_CLIENT); - - $apiClient = new KeycloakApiClient( - $this->givenKeycloakHttpClient($this->logger, $mock), - $this->scopeConfig, - $this->logger - ); - - $client = $apiClient->fetchClient($this->realm, $this->integration, Uuid::uuid4()); - $this->assertEmpty($client); - } - /** @dataProvider dataProviderIsClientEnabled */ public function test_fetch_is_client_enabled(bool $enabled): void { @@ -211,7 +166,7 @@ public function test_fetch_is_client_enabled(bool $enabled): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4()->toString(), self::SECRET, Environment::Acceptance); $this->assertEquals($enabled, $apiClient->fetchIsClientActive($client)); } @@ -237,7 +192,7 @@ public function test_update_client_throws_exception_when_api_call_fails(): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4()->toString(), self::SECRET, Environment::Acceptance); $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_UPDATE_CLIENT); @@ -258,7 +213,7 @@ public function test_reset_scopes_throws_exception_when_api_call_fails(): void $this->logger ); - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, Environment::Acceptance); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4()->toString(), self::SECRET, Environment::Acceptance); $this->expectException(KeyCloakApiFailed::class); $this->expectExceptionCode(KeyCloakApiFailed::FAILED_TO_RESET_SCOPE); diff --git a/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php b/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php index 149ca2e3f..387b2868b 100644 --- a/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php @@ -20,7 +20,7 @@ final class IntegrationToKeycloakClientConverterTest extends TestCase public function test_integration_converted_to_keycloak_format(IntegrationPartnerStatus $partnerStatus, bool $serviceAccountsEnabled, bool $standardFlowEnabled): void { $id = Uuid::uuid4(); - $clientId = Uuid::uuid4(); + $clientId = Uuid::uuid4()->toString(); $integration = $this->givenThereIsAnIntegration($id, ['partnerStatus' => $partnerStatus]); @@ -29,7 +29,7 @@ public function test_integration_converted_to_keycloak_format(IntegrationPartner $this->assertIsArray($convertedData); $this->assertEquals('openid-connect', $convertedData['protocol']); $this->assertEquals($id->toString(), $convertedData['id']); - $this->assertEquals($clientId->toString(), $convertedData['clientId']); + $this->assertEquals($clientId, $convertedData['clientId']); $this->assertEquals($integration->name, $convertedData['name']); $this->assertEquals($integration->description, $convertedData['description']); $this->assertEquals($serviceAccountsEnabled, $convertedData['serviceAccountsEnabled']); diff --git a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php index 48535f399..f9a60ee3c 100644 --- a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php @@ -33,7 +33,7 @@ protected function setUp(): void $this->client = new Client( Uuid::uuid4(), $this->integrationId, - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'my-secret', Environment::Acceptance ); diff --git a/tests/Keycloak/Jobs/BlockClientHandlerTest.php b/tests/Keycloak/Jobs/BlockClientHandlerTest.php index 14e1031e6..80604cc0f 100644 --- a/tests/Keycloak/Jobs/BlockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/BlockClientHandlerTest.php @@ -31,7 +31,7 @@ public function test_block_client_handler(): void $client = new Client( Uuid::uuid4(), Uuid::uuid4(), - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret-1', Environment::Acceptance ); @@ -63,7 +63,7 @@ public function test_handler_fails_when_client_does_not_exists(): void $client = new Client( Uuid::uuid4(), Uuid::uuid4(), - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret-1', Environment::Acceptance ); diff --git a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php index 5f55d3f31..27c3457a8 100644 --- a/tests/Keycloak/Jobs/UnblockClientHandlerTest.php +++ b/tests/Keycloak/Jobs/UnblockClientHandlerTest.php @@ -32,7 +32,7 @@ public function test_unblock_client_handler(): void $client = new Client( Uuid::uuid4(), Uuid::uuid4(), - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret-1', Environment::Acceptance ); @@ -64,7 +64,7 @@ public function test_handler_fails_when_client_does_not_exists(): void $client = new Client( Uuid::uuid4(), Uuid::uuid4(), - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret-1', Environment::Acceptance ); diff --git a/tests/Keycloak/Listeners/BlockClientsTest.php b/tests/Keycloak/Listeners/BlockClientsTest.php index 5426d242d..76db88e7f 100644 --- a/tests/Keycloak/Listeners/BlockClientsTest.php +++ b/tests/Keycloak/Listeners/BlockClientsTest.php @@ -55,7 +55,7 @@ public function test_block_clients_when_integration_is_blocked(): void $clients = []; foreach ($this->givenAllRealms() as $realm) { - $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4(), self::SECRET, $realm->environment); + $client = new Client(Uuid::uuid4(), $this->integration->id, Uuid::uuid4()->toString(), self::SECRET, $realm->environment); $clients[$client->id->toString()] = $client; } diff --git a/tests/Keycloak/Listeners/CreateClientsTest.php b/tests/Keycloak/Listeners/CreateClientsTest.php index 71223cf9f..b6a53c6ac 100644 --- a/tests/Keycloak/Listeners/CreateClientsTest.php +++ b/tests/Keycloak/Listeners/CreateClientsTest.php @@ -81,43 +81,32 @@ public function test_create_client_for_integration(): void $clients[$realm->internalName] = new Client( Uuid::uuid4(), $this->integration->id, - Uuid::uuid4(), + Uuid::uuid4()->toString(), self::SECRET, $realm->environment ); } - $activeId = null; $this->apiClient->expects($this->exactly($this->realms->count())) ->method('createClient') ->willReturnCallback( - function (Realm $realm, Integration $integrationArgument) use ($clients, &$activeId) { + function (Realm $realm, Integration $integrationArgument) use ($clients) { + $this->assertEquals($this->integration->id, $integrationArgument->id); + $this->assertArrayHasKey($realm->internalName, $clients); + $this->assertEquals($this->integration->id, $integrationArgument->id); $this->assertArrayHasKey($realm->internalName, $clients); - $activeId = $clients[$realm->internalName]->id; - return $clients[$realm->internalName]->id; + return $clients[$realm->internalName]; } ); $this->apiClient->expects($this->exactly($this->realms->count())) ->method('addScopeToClient') - ->willReturnCallback(function (Client $client, UuidInterface $scopeId) use (&$activeId) { - $this->assertEquals($activeId, $client->id); + ->willReturnCallback(function (Client $client, UuidInterface $scopeId) { $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); }); - $this->apiClient->expects($this->exactly($this->realms->count())) - ->method('fetchClient') - ->willReturnCallback( - function (Realm $realm, Integration $integrationArgument) use ($clients) { - $this->assertEquals($this->integration->id, $integrationArgument->id); - $this->assertArrayHasKey($realm->internalName, $clients); - - return $clients[$realm->internalName]; - } - ); - $this->integrationRepository->expects($this->once()) ->method('getById') ->with($this->integration->id) @@ -170,7 +159,7 @@ public function test_handle_creating_missing_clients(): void $clients[$environment->value] = new Client( Uuid::uuid4(), $this->integration->id, - Uuid::uuid4(), + Uuid::uuid4()->toString(), self::SECRET, $environment ); @@ -184,6 +173,11 @@ function (Realm $realm, Integration $integrationArgument) use ($clients) { $env = $realm->environment->value; $this->assertArrayHasKey($env, $clients); + + $this->assertEquals($this->integration->id, $integrationArgument->id); + $this->assertArrayHasKey($realm->environment->value, $clients); + + return $clients[$realm->environment->value]; } ); @@ -193,17 +187,6 @@ function (Realm $realm, Integration $integrationArgument) use ($clients) { $this->assertEquals(Uuid::fromString(self::SEARCH_SCOPE_ID), $scopeId); }); - $this->apiClient->expects($this->exactly($missingEnvironments->count())) - ->method('fetchClient') - ->willReturnCallback( - function (Realm $realm, Integration $integrationArgument) use ($clients) { - $this->assertEquals($this->integration->id, $integrationArgument->id); - $this->assertArrayHasKey($realm->environment->value, $clients); - - return $clients[$realm->environment->value]; - } - ); - $this->integrationRepository->expects($this->once()) ->method('getById') ->with($this->integration->id) diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index 95c1041ef..bf48084ce 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -82,7 +82,7 @@ public function test_update_client_for_integration(): void $clients = []; foreach ($this->realms as $realm) { $id = Uuid::uuid4(); - $clients[$id->toString()] = new Client($id, $this->integration->id, Uuid::uuid4(), self::SECRET, $realm->environment); + $clients[$id->toString()] = new Client($id, $this->integration->id, Uuid::uuid4()->toString(), self::SECRET, $realm->environment); } $activeId = null; // Which client are we updating? @@ -126,7 +126,7 @@ public function test_update_client_for_integration(): void $this->assertEquals([ 'integration_id' => $this->integration->id->toString(), 'environment' => $client->environment->value, - 'client_id' => $client->clientId->toString(), + 'client_id' => $client->clientId, ], $params); }); diff --git a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php index 3991e9668..733831f2c 100644 --- a/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php +++ b/tests/Keycloak/Repositories/EloquentKeycloakClientRepositoryTest.php @@ -33,8 +33,8 @@ protected function setUp(): void public function test_it_can_save_one_or_more_clients(): void { $integrationId = Uuid::uuid4(); - $clientId = Uuid::uuid4(); - $clientId2 = Uuid::uuid4(); + $clientId = Uuid::uuid4()->toString(); + $clientId2 = Uuid::uuid4()->toString(); $client1 = new Client( Uuid::uuid4(), @@ -55,13 +55,13 @@ public function test_it_can_save_one_or_more_clients(): void $this->assertDatabaseHas('keycloak_clients', [ 'integration_id' => $integrationId->toString(), 'client_secret' => 'client-secret-1', - 'client_id' => $clientId->toString(), + 'client_id' => $clientId, 'realm' => Environment::Acceptance->value, ]); $this->assertDatabaseHas('keycloak_clients', [ 'integration_id' => $integrationId->toString(), 'client_secret' => 'client-secret-2', - 'client_id' => $clientId2->toString(), + 'client_id' => $clientId2, 'realm' => Environment::Testing->value, ]); } @@ -69,8 +69,8 @@ public function test_it_can_save_one_or_more_clients(): void public function test_it_can_get_all_clients_for_an_integration_id(): void { $integrationId = Uuid::uuid4(); - $clientId = Uuid::uuid4(); - $clientId2 = Uuid::uuid4(); + $clientId = Uuid::uuid4()->toString(); + $clientId2 = Uuid::uuid4()->toString(); $client1 = new Client( Uuid::uuid4(), @@ -114,7 +114,7 @@ public function test_it_can_get_all_clients_for_multiple_integration_ids(): void $clients[] = new Client( Uuid::uuid4(), $integrationId, - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret-' . $count, $realm->environment ); @@ -147,7 +147,7 @@ public function test_it_doesnt_get_clients_for_unasked_integration_ids(): void $clients[] = new Client( Uuid::uuid4(), $integrationId, - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret-' . $count, $realm->environment ); @@ -183,7 +183,7 @@ public function test_it_can_get_missing_realms_by_integration_id(): void $clients[] = new Client( Uuid::uuid4(), $integrationId, - Uuid::uuid4(), + Uuid::uuid4()->toString(), 'client-secret', $realm->environment, ); diff --git a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php index 42fb744a6..161f3aa2f 100644 --- a/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/BlockKeycloakClientGuardTest.php @@ -36,7 +36,7 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new BlockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Environment::Acceptance); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4()->toString(), 'client-id-1', Environment::Acceptance); } #[DataProvider('dataProvider')] diff --git a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php index 41a8f7903..f839b60e4 100644 --- a/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php +++ b/tests/Nova/ActionGuards/Keycloak/UnblockKeycloakClientGuardTest.php @@ -34,7 +34,7 @@ public function setUp(): void $this->apiClient = $this->createMock(ApiClient::class); $this->guard = new UnblockKeycloakClientGuard(new CachedKeycloakClientStatus($this->apiClient, new NullLogger())); - $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4(), 'client-id-1', Environment::Acceptance); + $this->client = new Client(Uuid::uuid4(), Uuid::uuid4(), Uuid::uuid4()->toString(), 'client-id-1', Environment::Acceptance); } #[DataProvider('dataProvider')] From ada312eb52e850177a744acf12ee820a86c1c8fa Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Fri, 7 Jun 2024 10:25:29 +0200 Subject: [PATCH 35/36] Rename ClientCollection to Client --- app/Keycloak/{ClientCollection.php => Clients.php} | 2 +- app/Keycloak/Listeners/CreateClients.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename app/Keycloak/{ClientCollection.php => Clients.php} (74%) diff --git a/app/Keycloak/ClientCollection.php b/app/Keycloak/Clients.php similarity index 74% rename from app/Keycloak/ClientCollection.php rename to app/Keycloak/Clients.php index 14b268416..d3b3ca26d 100644 --- a/app/Keycloak/ClientCollection.php +++ b/app/Keycloak/Clients.php @@ -9,6 +9,6 @@ /** * @extends Collection */ -final class ClientCollection extends Collection +final class Clients extends Collection { } diff --git a/app/Keycloak/Listeners/CreateClients.php b/app/Keycloak/Listeners/CreateClients.php index f2dada0f4..de8a35817 100644 --- a/app/Keycloak/Listeners/CreateClients.php +++ b/app/Keycloak/Listeners/CreateClients.php @@ -9,7 +9,7 @@ use App\Domain\Integrations\Integration; use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client\ApiClient; -use App\Keycloak\ClientCollection; +use App\Keycloak\Clients; use App\Keycloak\ClientId\ClientIdUuidStrategy; use App\Keycloak\Events\MissingClientsDetected; use App\Keycloak\Exception\KeyCloakApiFailed; @@ -73,11 +73,11 @@ private function handle(IntegrationCreated|MissingClientsDetected $event, Realms } } - private function createClientsInKeycloak(Integration $integration, Realms $realms): ClientCollection + private function createClientsInKeycloak(Integration $integration, Realms $realms): Clients { $scopeId = $this->scopeConfig->getScopeIdFromIntegrationType($integration); - $clientCollection = new ClientCollection(); + $clientCollection = new Clients(); foreach ($realms as $realm) { try { From 62cc202ecf602a5e5767f5bff509c719d78270a3 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Fri, 7 Jun 2024 13:57:13 +0200 Subject: [PATCH 36/36] Send internal id not client id to fetch client --- app/Keycloak/Client/KeycloakApiClient.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index 4d4e86667..bfee18130 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -36,6 +36,7 @@ public function createClient( ClientIdFactory $clientIdFactory ): Client { $clientId = $clientIdFactory->create(); + $id = Uuid::uuid4(); try { $response = $this->client->sendWithBearer( @@ -44,7 +45,7 @@ public function createClient( sprintf('admin/realms/%s/clients', $realm->internalName), [], Json::encode(IntegrationToKeycloakClientConverter::convert( - Uuid::uuid4(), + $id, $integration, $clientId )) @@ -61,7 +62,7 @@ public function createClient( $this->logger->info(sprintf('Client %s for realm %s created with client id %s', $integration->name, $realm->publicName, $clientId)); - return $this->fetchClient($realm, $integration, $clientId); + return $this->fetchClient($realm, $integration, $id->toString()); } /** @@ -111,13 +112,13 @@ public function deleteScopes(Client $client): void /** * @throws KeyCloakApiFailed */ - private function fetchClient(Realm $realm, Integration $integration, string $clientId): Client + private function fetchClient(Realm $realm, Integration $integration, string $id): Client { try { $response = $this->client->sendWithBearer( new Request( 'GET', - sprintf('admin/realms/%s/clients/%s', $realm->internalName, $clientId) + sprintf('admin/realms/%s/clients/%s', $realm->internalName, $id) ), $realm ); @@ -129,7 +130,7 @@ private function fetchClient(Realm $realm, Integration $integration, string $cli } return Client::createFromJson($realm, $integration->id, Json::decodeAssociatively($body)); - } catch (Throwable $e) { + } catch (KeyCloakApiFailed $e) { throw KeyCloakApiFailed::failedToFetchClient($realm, $e->getMessage()); } }