From 5117b1941cf598fd8b2169c2c3f2d08394acc2e2 Mon Sep 17 00:00:00 2001 From: Koen Eelen Date: Wed, 19 Jun 2024 11:35:34 +0200 Subject: [PATCH] changed payload create client --- app/Keycloak/Client/KeycloakApiClient.php | 3 +- .../IntegrationToKeycloakClientConverter.php | 15 +++- .../Converters/IntegrationUrlConverter.php | 56 +++++++-------- app/Keycloak/Listeners/UpdateClients.php | 17 ++--- ...tegrationToKeycloakClientConverterTest.php | 43 ++++++++++- .../IntegrationUrlConverterTest.php | 72 ++++++++++++------- .../Keycloak/Listeners/UpdateClientsTest.php | 6 +- 7 files changed, 135 insertions(+), 77 deletions(-) diff --git a/app/Keycloak/Client/KeycloakApiClient.php b/app/Keycloak/Client/KeycloakApiClient.php index c51651bc0..a96a30941 100644 --- a/app/Keycloak/Client/KeycloakApiClient.php +++ b/app/Keycloak/Client/KeycloakApiClient.php @@ -47,7 +47,8 @@ public function createClient( Json::encode(IntegrationToKeycloakClientConverter::convert( $id, $integration, - $clientId + $clientId, + $realm->environment )) ), $realm diff --git a/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php b/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php index 130187425..f29a30355 100644 --- a/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php +++ b/app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php @@ -4,13 +4,14 @@ namespace App\Keycloak\Converters; +use App\Domain\Integrations\Environment; use App\Domain\Integrations\Integration; use App\Domain\Integrations\IntegrationPartnerStatus; use Ramsey\Uuid\UuidInterface; final class IntegrationToKeycloakClientConverter { - public static function convert(UuidInterface $id, Integration $integration, string $clientId): array + public static function convert(UuidInterface $id, Integration $integration, string $clientId, Environment $environment): array { return [ 'protocol' => 'openid-connect', @@ -26,6 +27,18 @@ public static function convert(UuidInterface $id, Integration $integration, stri 'standardFlowEnabled' => $integration->partnerStatus === IntegrationPartnerStatus::FIRST_PARTY, 'frontchannelLogout' => true, 'alwaysDisplayInConsole' => false, + 'attributes' => [ + 'origin' => 'publiq-platform', + 'use.refresh.tokens' => true, + 'post.logout.redirect.uris' => IntegrationUrlConverter::buildLogoutUrls($integration, $environment), + ], + 'baseUrl' => IntegrationUrlConverter::buildLoginUrl($integration, $environment), + 'redirectUris' => IntegrationUrlConverter::buildCallbackUrls($integration, $environment), + 'webOrigins' => [ + '+', // This permits all origins of Valid Redirect URIs for CORS checks + 'https://docs.publiq.be', // Always add this origin to enable CORS requests from the "Try it out!" functionality in Stoplight + 'https://publiq.stoplight.io', // Always add this origin to enable CORS requests from the "Try it out!" functionality in Stoplight + ], ]; } } diff --git a/app/Keycloak/Converters/IntegrationUrlConverter.php b/app/Keycloak/Converters/IntegrationUrlConverter.php index 2ef4cfbc9..0ffd47831 100644 --- a/app/Keycloak/Converters/IntegrationUrlConverter.php +++ b/app/Keycloak/Converters/IntegrationUrlConverter.php @@ -4,62 +4,56 @@ namespace App\Keycloak\Converters; +use App\Domain\Integrations\Environment; use App\Domain\Integrations\Integration; use App\Domain\Integrations\IntegrationPartnerStatus; use App\Domain\Integrations\IntegrationUrl; use App\Domain\Integrations\IntegrationUrlType; -use App\Keycloak\Client; /* Converts integration urls in the correct Keycloak API format */ + final readonly class IntegrationUrlConverter { - public static function convert(Integration $integration, Client $client): array + public static function buildCallbackUrls(Integration $integration, Environment $environment): array { - // Empty start construct to make all urls empty at the beginning of each update - $urls = [ - 'baseUrl' => '', - 'redirectUris' => [], - 'attributes' => ['post.logout.redirect.uris' => ''], - 'webOrigins' => [ - '+', // This permits all origins of Valid Redirect URIs for CORS checks - ], - ]; - if ($integration->partnerStatus !== IntegrationPartnerStatus::FIRST_PARTY) { // Only first parties can have redirect uri configured. - return $urls; + return []; } - $urls = self::buildCallbackUrl($integration, $client, $urls); - $urls = self::buildLoginUrls($integration, $client, $urls); - return self::buildLogoutUrls($integration, $client, $urls); - } + $urls = []; - private static function buildCallbackUrl(Integration $integration, Client $client, array $urls): array - { - $callbackUrl = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Callback, $client->environment); - if (isset($callbackUrl[0]) && $callbackUrl[0] instanceof IntegrationUrl) { - $urls['baseUrl'] = $callbackUrl[0]->url; + $callbackUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Callback, $environment); + foreach ($callbackUrls as $url) { + $urls[] = $url->url; } return $urls; } - private static function buildLoginUrls(Integration $integration, Client $client, array $urls): array + public static function buildLoginUrl(Integration $integration, Environment $environment): string { - $loginUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Login, $client->environment); - foreach ($loginUrls as $loginUrl) { - $urls['redirectUris'][] = $loginUrl->url; + if ($integration->partnerStatus !== IntegrationPartnerStatus::FIRST_PARTY) { + // Only first parties can have redirect uri configured. + return ''; } - return $urls; + $loginUrl = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Login, $environment); + if (!isset($loginUrl[0]) || !$loginUrl[0] instanceof IntegrationUrl) { + return ''; + } + + return $loginUrl[0]->url; } - private static function buildLogoutUrls(Integration $integration, Client $client, array $urls): array + public static function buildLogoutUrls(Integration $integration, Environment $environment): string { - $logoutUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Logout, $client->environment); - $urls['attributes']['post.logout.redirect.uris'] = implode('#', array_map(static fn ($url) => $url->url, $logoutUrls)); + if ($integration->partnerStatus !== IntegrationPartnerStatus::FIRST_PARTY) { + // Only first parties can have redirect uri configured. + return ''; + } - return $urls; + $logoutUrls = $integration->urlsForTypeAndEnvironment(IntegrationUrlType::Logout, $environment); + return implode('#', array_map(static fn ($url) => $url->url, $logoutUrls)); } } diff --git a/app/Keycloak/Listeners/UpdateClients.php b/app/Keycloak/Listeners/UpdateClients.php index 4d675e47d..4b61e06a3 100644 --- a/app/Keycloak/Listeners/UpdateClients.php +++ b/app/Keycloak/Listeners/UpdateClients.php @@ -12,11 +12,10 @@ use App\Domain\Integrations\Repositories\IntegrationRepository; use App\Keycloak\Client; use App\Keycloak\Client\ApiClient; +use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; use App\Keycloak\Exception\KeyCloakApiFailed; use App\Keycloak\Realms; use App\Keycloak\Repositories\KeycloakClientRepository; -use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; -use App\Keycloak\Converters\IntegrationUrlConverter; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Psr\Log\LoggerInterface; @@ -71,17 +70,15 @@ private function updateClient(Integration $integration, Client $keycloakClient, { $this->client->updateClient( $keycloakClient, - array_merge( - IntegrationToKeycloakClientConverter::convert( - $keycloakClient->id, - $integration, - $keycloakClient->clientId - ), - IntegrationUrlConverter::convert($integration, $keycloakClient) + IntegrationToKeycloakClientConverter::convert( + $keycloakClient->id, + $integration, + $keycloakClient->clientId, + $keycloakClient->environment ) ); $this->client->deleteScopes($keycloakClient); - foreach($scopeIds as $scopeId) { + foreach ($scopeIds as $scopeId) { $this->client->addScopeToClient($keycloakClient, $scopeId); } diff --git a/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php b/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php index 387b2868b..b3948b906 100644 --- a/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationToKeycloakClientConverterTest.php @@ -4,11 +4,14 @@ namespace Tests\Keycloak\Converters; +use App\Domain\Integrations\Environment; use App\Domain\Integrations\IntegrationPartnerStatus; +use App\Domain\Integrations\IntegrationUrl; +use App\Domain\Integrations\IntegrationUrlType; use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; -use Tests\TestCase; use Ramsey\Uuid\Uuid; use Tests\CreatesIntegration; +use Tests\TestCase; final class IntegrationToKeycloakClientConverterTest extends TestCase { @@ -24,7 +27,7 @@ public function test_integration_converted_to_keycloak_format(IntegrationPartner $integration = $this->givenThereIsAnIntegration($id, ['partnerStatus' => $partnerStatus]); - $convertedData = IntegrationToKeycloakClientConverter::convert($id, $integration, $clientId); + $convertedData = IntegrationToKeycloakClientConverter::convert($id, $integration, $clientId, Environment::Acceptance); $this->assertIsArray($convertedData); $this->assertEquals('openid-connect', $convertedData['protocol']); @@ -40,6 +43,18 @@ public function test_integration_converted_to_keycloak_format(IntegrationPartner $this->assertFalse($convertedData['directAccessGrantsEnabled']); $this->assertFalse($convertedData['alwaysDisplayInConsole']); $this->assertTrue($convertedData['frontchannelLogout']); + $this->assertEquals([ + 'origin' => 'publiq-platform', + 'use.refresh.tokens' => true, + 'post.logout.redirect.uris' => '', + ], $convertedData['attributes']); + $this->assertEquals('', $convertedData['baseUrl']); + $this->assertEquals([], $convertedData['redirectUris']); + $this->assertEquals([ + '+', + 'https://docs.publiq.be', + 'https://publiq.stoplight.io', + ], $convertedData['webOrigins']); } public static function integrationDataProvider(): array @@ -49,4 +64,28 @@ public static function integrationDataProvider(): array [IntegrationPartnerStatus::FIRST_PARTY, false, true], ]; } + + public function test_combining_keycloak_convert_with_configured_uris(): void + { + $id = Uuid::uuid4(); + $clientId = Uuid::uuid4()->toString(); + + $integration = $this->givenThereIsAnIntegration($id, ['partnerStatus' => IntegrationPartnerStatus::FIRST_PARTY]); + $integration = $integration->withUrls( + 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/callback1'), + new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Callback, 'https://example.com/callback2'), + new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Login, 'https://example.com/login1'), + ); + + $convertedData = IntegrationToKeycloakClientConverter::convert($id, $integration, $clientId, Environment::Acceptance); + $this->assertEquals([ + 'origin' => 'publiq-platform', + 'use.refresh.tokens' => true, + 'post.logout.redirect.uris' => 'https://example.com/logout1#https://example.com/logout2', + ], $convertedData['attributes']); + $this->assertEquals('https://example.com/login1', $convertedData['baseUrl']); + $this->assertEquals(['https://example.com/callback1', 'https://example.com/callback2'], $convertedData['redirectUris']); + } } diff --git a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php index f9a60ee3c..ba92aabb5 100644 --- a/tests/Keycloak/Converters/IntegrationUrlConverterTest.php +++ b/tests/Keycloak/Converters/IntegrationUrlConverterTest.php @@ -43,46 +43,64 @@ public function test_convert_for_non_first_party(): void { $integration = $this->givenThereIsAnIntegration($this->integrationId, ['partnerStatus' => IntegrationPartnerStatus::THIRD_PARTY]); - $result = IntegrationUrlConverter::convert($integration, $this->client); + $result = IntegrationUrlConverter::buildLoginUrl($integration, $this->client->environment); + $this->assertSame('', $result); - $expected = [ - 'baseUrl' => '', - 'redirectUris' => [], - 'attributes' => ['post.logout.redirect.uris' => ''], - 'webOrigins' => ['+'], - ]; + $result = IntegrationUrlConverter::buildCallbackUrls($integration, $this->client->environment); + $this->assertSame([], $result); - $this->assertSame($expected, $result); + $result = IntegrationUrlConverter::buildLogoutUrls($integration, $this->client->environment); + $this->assertSame('', $result); } - public function test_convert_for_first_party(): void + public function test_convert_for_first_party_login_url(): void { $integration = $this->givenThereIsAnIntegration($this->integrationId, ['partnerStatus' => IntegrationPartnerStatus::FIRST_PARTY]); $integration = $integration->withUrls( new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Login, 'https://example.com/login1'), + // These urls below should NOT be shown! Wrong Realm + new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Testing, IntegrationUrlType::Login, 'https://wrong.com/'), + // You can only have 1 login uri new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Login, 'https://example.com/login2'), + ); + + $this->assertSame( + 'https://example.com/login1', + IntegrationUrlConverter::buildLoginUrl($integration, $this->client->environment) + ); + } + + public function test_convert_for_first_party_callback_urls(): 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/callback1'), + new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Callback, 'https://example.com/callback2'), + + // These urls below should NOT be shown! Wrong Realm + new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Production, IntegrationUrlType::Callback, 'https://wrong.com/'), + ); + $result = IntegrationUrlConverter::buildCallbackUrls($integration, $this->client->environment); + + $this->assertSame([ + 'https://example.com/callback1', + 'https://example.com/callback2', + ], $result); + } + + public function test_convert_for_first_party_logout_urls(): void + { + $integration = $this->givenThereIsAnIntegration($this->integrationId, ['partnerStatus' => IntegrationPartnerStatus::FIRST_PARTY]); + $integration = $integration->withUrls( 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! + // These urls below should NOT be shown! Wrong Realm new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Production, IntegrationUrlType::Logout, 'https://wrong.com/'), - new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Testing, IntegrationUrlType::Login, 'https://wrong.com/'), - // You can only have 1 callback uri - new IntegrationUrl(Uuid::uuid4(), $integration->id, Environment::Acceptance, IntegrationUrlType::Callback, 'https://wrong.com/') ); - $result = IntegrationUrlConverter::convert($integration, $this->client); - - $expected = [ - 'baseUrl' => 'https://example.com/callback', - 'redirectUris' => [ - 'https://example.com/login1', - 'https://example.com/login2', - ], - 'attributes' => ['post.logout.redirect.uris' => 'https://example.com/logout1#https://example.com/logout2'], - 'webOrigins' => ['+'], - ]; - - $this->assertSame($expected, $result); + $this->assertSame( + 'https://example.com/logout1#https://example.com/logout2', + IntegrationUrlConverter::buildLogoutUrls($integration, $this->client->environment) + ); } } diff --git a/tests/Keycloak/Listeners/UpdateClientsTest.php b/tests/Keycloak/Listeners/UpdateClientsTest.php index c61990081..24dce3789 100644 --- a/tests/Keycloak/Listeners/UpdateClientsTest.php +++ b/tests/Keycloak/Listeners/UpdateClientsTest.php @@ -16,7 +16,6 @@ use App\Keycloak\Realms; use App\Keycloak\Repositories\KeycloakClientRepository; use App\Keycloak\Converters\IntegrationToKeycloakClientConverter; -use App\Keycloak\Converters\IntegrationUrlConverter; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Ramsey\Uuid\Uuid; @@ -79,10 +78,7 @@ public function test_update_client_for_integration(): void $this->apiClient->expects($this->exactly($this->realms->count())) ->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) - ); + $expectedBody = IntegrationToKeycloakClientConverter::convert($client->id, $this->integration, $client->clientId, $client->environment); $this->assertEquals($expectedBody, $body);