Skip to content

Commit

Permalink
Merge pull request #1213 from cultuurnet/PPF-525/payload-client-keycl…
Browse files Browse the repository at this point in the history
…oak-changes

PPF-525 Changed payload create client Keycloak
  • Loading branch information
grubolsch authored Jun 20, 2024
2 parents 2ab7e0c + 5117b19 commit 9414ea2
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 77 deletions.
3 changes: 2 additions & 1 deletion app/Keycloak/Client/KeycloakApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public function createClient(
Json::encode(IntegrationToKeycloakClientConverter::convert(
$id,
$integration,
$clientId
$clientId,
$realm->environment
))
),
$realm
Expand Down
15 changes: 14 additions & 1 deletion app/Keycloak/Converters/IntegrationToKeycloakClientConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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
],
];
}
}
56 changes: 25 additions & 31 deletions app/Keycloak/Converters/IntegrationUrlConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
17 changes: 7 additions & 10 deletions app/Keycloak/Listeners/UpdateClients.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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']);
Expand All @@ -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
Expand All @@ -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']);
}
}
72 changes: 45 additions & 27 deletions tests/Keycloak/Converters/IntegrationUrlConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}
6 changes: 1 addition & 5 deletions tests/Keycloak/Listeners/UpdateClientsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 9414ea2

Please sign in to comment.