Skip to content

Commit

Permalink
Merge pull request #1193 from cultuurnet/PPF-510/make-ScopeConfig-dep…
Browse files Browse the repository at this point in the history
…endent-on-chosen-realm

PPF-510 Make scopeConfig dependent on chosen realm
  • Loading branch information
grubolsch authored Jun 12, 2024
2 parents 9b53283 + e4ce561 commit 45d1f29
Show file tree
Hide file tree
Showing 19 changed files with 241 additions and 128 deletions.
32 changes: 22 additions & 10 deletions .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -153,25 +153,37 @@ VITE_UITPAS_INTEGRATION_TYPE_ENABLED=${UITPAS_INTEGRATION_TYPE_ENABLED}
KEYCLOAK_ENABLED=true

KEYCLOAK_ACC_BASE_URL='https://account.kcpoc.lodgon.com/'
KEYCLOAK_ACC_REALM_NAME='uitidpoc'
KEYCLOAK_ACC_REALM_NAME='myrealm'
KEYCLOAK_ACC_CLIENT_ID='php_client'
KEYCLOAK_ACC_CLIENT_SECRET='xxx'
KEYCLOAK_ACC_CLIENT_SECRET='super-secret'

# Incorrect values, but need to contain a valid UUID formatted string
KEYCLOAK_ACC_SCOPE_SEARCH_API_ID='06059529-74b5-422a-a499-ffcaf065d437' #publiq-api-sapi-scope
KEYCLOAK_ACC_SCOPE_ENTRY_API_ID='d8a54568-26da-412b-a441-d5e2fad84478' #publiq-api-entry-scope
KEYCLOAK_ACC_SCOPE_WIDGETS_ID='123ae05d-1c41-40c8-8716-c4654a3bfd98'#publiq-widget-scope
KEYCLOAK_ACC_SCOPE_UITPAS_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e'#uitpas-scope

KEYCLOAK_TEST_BASE_URL='https://account.kcpoc.lodgon.com/'
KEYCLOAK_TEST_REALM_NAME='uitidpoc'
KEYCLOAK_TEST_REALM_NAME='myrealm'
KEYCLOAK_TEST_CLIENT_ID='php_client'
KEYCLOAK_TEST_CLIENT_SECRET='xxx'
KEYCLOAK_TEST_CLIENT_SECRET='super-secret'

# Incorrect values, but need to contain a valid UUID formatted string
KEYCLOAK_TEST_SCOPE_SEARCH_API_ID='06059529-74b5-422a-a499-ffcaf065d437' #publiq-api-sapi-scope
KEYCLOAK_TEST_SCOPE_ENTRY_API_ID='d8a54568-26da-412b-a441-d5e2fad84478' #publiq-api-entry-scope
KEYCLOAK_TEST_SCOPE_WIDGETS_ID='123ae05d-1c41-40c8-8716-c4654a3bfd98'#publiq-widget-scope
KEYCLOAK_TEST_SCOPE_UITPAS_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e'#uitpas-scope

KEYCLOAK_PROD_BASE_URL='https://account.kcpoc.lodgon.com/'
KEYCLOAK_PROD_REALM_NAME='uitidpoc'
KEYCLOAK_PROD_REALM_NAME='myrealm'
KEYCLOAK_PROD_CLIENT_ID='php_client'
KEYCLOAK_PROD_CLIENT_SECRET='xxx'
KEYCLOAK_PROD_CLIENT_SECRET='super-secret'

# 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
KEYCLOAK_SCOPE_ENTRY_API_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e' #publiq-api-entry-scope
KEYCLOAK_SCOPE_WIDGETS_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e'#publiq-widget-scope
KEYCLOAK_SCOPE_UITPAS_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e'#uitpas-scope
KEYCLOAK_PROD_SCOPE_SEARCH_API_ID='06059529-74b5-422a-a499-ffcaf065d437' #publiq-api-sapi-scope
KEYCLOAK_PROD_SCOPE_ENTRY_API_ID='d8a54568-26da-412b-a441-d5e2fad84478' #publiq-api-entry-scope
KEYCLOAK_PROD_SCOPE_WIDGETS_ID='123ae05d-1c41-40c8-8716-c4654a3bfd98'#publiq-widget-scope
KEYCLOAK_PROD_SCOPE_UITPAS_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e'#uitpas-scope

SEARCH_BASE_URI=https://search-acc.uitdatabank.be/
SEARCH_API_KEY=
24 changes: 0 additions & 24 deletions app/Keycloak/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace App\Keycloak;

use App\Domain\Integrations\Environment;
use Illuminate\Support\Facades\App;
use InvalidArgumentException;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
Expand Down Expand Up @@ -38,27 +37,4 @@ public static function createFromJson(
$realm->environment,
);
}

public function getKeycloakUrl(): string
{
$baseUrl = $this->getRealm()->baseUrl;

return $baseUrl . 'admin/master/console/#/' . $this->getRealm()->internalName . '/clients/' . $this->id->toString() . '/settings';
}

public function getRealm(): Realm
{
/** @var Realms $realmCollection */
$realmCollection = App::get(Realms::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)
);
}
}
34 changes: 21 additions & 13 deletions app/Keycloak/Client/KeycloakApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use App\Keycloak\Converters\IntegrationToKeycloakClientConverter;
use App\Keycloak\Exception\KeyCloakApiFailed;
use App\Keycloak\Realm;
use App\Keycloak\ScopeConfig;
use App\Keycloak\Realms;
use GuzzleHttp\Psr7\Request;
use Psr\Log\LoggerInterface;
use Ramsey\Uuid\Uuid;
Expand All @@ -22,7 +22,7 @@
{
public function __construct(
private KeycloakHttpClient $client,
private ScopeConfig $scopeConfig,
private Realms $realms,
private LoggerInterface $logger,
) {
}
Expand Down Expand Up @@ -70,13 +70,15 @@ public function createClient(
*/
public function addScopeToClient(Client $client, UuidInterface $scopeId): void
{
$realm = $this->realms->getRealmByEnvironment($client->environment);

try {
$response = $this->client->sendWithBearer(
new Request(
'PUT',
sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->getRealm()->internalName, $client->id->toString(), $scopeId->toString())
sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $realm->internalName, $client->id->toString(), $scopeId->toString())
),
$client->getRealm()
$realm
);
} catch (Throwable $e) {
throw KeyCloakApiFailed::failedToAddScopeToClient($e->getMessage());
Expand All @@ -89,14 +91,16 @@ public function addScopeToClient(Client $client, UuidInterface $scopeId): void

public function deleteScopes(Client $client): void
{
foreach ($this->scopeConfig->getAll() as $scope) {
$realm = $this->realms->getRealmByEnvironment($client->environment);

foreach ($realm->scopeConfig->getAll() as $scope) {
try {
$response = $this->client->sendWithBearer(
new Request(
'DELETE',
sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $client->getRealm()->internalName, $client->id->toString(), $scope->toString()),
sprintf('admin/realms/%s/clients/%s/default-client-scopes/%s', $realm->internalName, $client->id->toString(), $scope->toString()),
),
$client->getRealm()
$realm
);

// Will throw a 404 when scope not attached to client, but this is no problem.
Expand Down Expand Up @@ -140,26 +144,28 @@ private function fetchClient(Realm $realm, Integration $integration, string $id)
*/
public function fetchIsClientActive(Client $client): bool
{
$realm = $this->realms->getRealmByEnvironment($client->environment);

try {
$response = $this->client->sendWithBearer(
new Request(
'GET',
sprintf('admin/realms/%s/clients/%s', $client->getRealm()->internalName, $client->id->toString())
sprintf('admin/realms/%s/clients/%s', $realm->internalName, $client->id->toString())
),
$client->getRealm()
$realm
);

$body = $response->getBody()->getContents();

if (empty($body) || $response->getStatusCode() !== 200) {
throw KeyCloakApiFailed::failedToFetchClient($client->getRealm(), $body);
throw KeyCloakApiFailed::failedToFetchClient($realm, $body);
}

$data = Json::decodeAssociatively($body);

return $data['enabled'];
} catch (Throwable $e) {
throw KeyCloakApiFailed::failedToFetchClient($client->getRealm(), $e->getMessage());
throw KeyCloakApiFailed::failedToFetchClient($realm, $e->getMessage());
}
}

Expand All @@ -184,15 +190,17 @@ public function blockClient(Client $client): void
*/
public function updateClient(Client $client, array $body): void
{
$realm = $this->realms->getRealmByEnvironment($client->environment);

try {
$response = $this->client->sendWithBearer(
new Request(
'PUT',
'admin/realms/' . $client->getRealm()->internalName . '/clients/' . $client->id->toString(),
'admin/realms/' . $realm->internalName . '/clients/' . $client->id->toString(),
[],
Json::encode($body)
),
$client->getRealm()
$realm
);

if ($response->getStatusCode() !== 204) {
Expand Down
18 changes: 4 additions & 14 deletions app/Keycloak/KeycloakServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Psr\Log\LoggerInterface;
use Ramsey\Uuid\Uuid;

final class KeycloakServiceProvider extends ServiceProvider
{
Expand All @@ -40,26 +39,17 @@ public function register(): void
$this->app->get(LoggerInterface::class),
)
),
$this->app->get(ScopeConfig::class),
$this->app->get(Realms::class),
$this->app->get(LoggerInterface::class),
);
});

$this->app->singleton(ScopeConfig::class, function () {
return new ScopeConfig(
Uuid::fromString(config('keycloak.scope.search_api_id')),
Uuid::fromString(config('keycloak.scope.entry_api_id')),
Uuid::fromString(config('keycloak.scope.widgets_id')),
Uuid::fromString(config('keycloak.scope.uitpas_id')),
);
$this->app->singleton(Realms::class, function () {
return Realms::build();
});

$this->app->singleton(KeycloakClientRepository::class, function () {
return $this->app->get(EloquentKeycloakClientRepository::class);
});

$this->app->singleton(Realms::class, function () {
return Realms::build();
return new EloquentKeycloakClientRepository($this->app->get(Realms::class));
});

$this->app->singleton(CachedKeycloakClientStatus::class, function () {
Expand Down
2 changes: 1 addition & 1 deletion app/Keycloak/Listeners/BlockClients.php
Original file line number Diff line number Diff line change
Expand Up @@ -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->getRealm()->internalName,
'environment' => $keycloakClient->environment->value,
]);
} catch (KeyCloakApiFailed $e) {
$this->failed($integrationBlocked, $e);
Expand Down
11 changes: 5 additions & 6 deletions app/Keycloak/Listeners/CreateClients.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use App\Keycloak\Exception\KeyCloakApiFailed;
use App\Keycloak\Realms;
use App\Keycloak\Repositories\KeycloakClientRepository;
use App\Keycloak\ScopeConfig;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Psr\Log\LoggerInterface;
Expand All @@ -30,7 +29,6 @@ public function __construct(
private readonly KeycloakClientRepository $keycloakClientRepository,
private readonly Realms $realms,
private readonly ApiClient $client,
private readonly ScopeConfig $scopeConfig,
private readonly LoggerInterface $logger
) {
}
Expand Down Expand Up @@ -68,21 +66,22 @@ private function handle(IntegrationCreated|MissingClientsDetected $event, Realms
$this->logger->info('Keycloak client created', [
'integration_id' => $event->id->toString(),
'client_id' => $client->id->toString(),
'realm' => $client->getRealm()->internalName,
'environment' => $client->environment->value,
]);
}
}

private function createClientsInKeycloak(Integration $integration, Realms $realms): Clients
{
$scopeId = $this->scopeConfig->getScopeIdFromIntegrationType($integration);

$clientCollection = new Clients();

foreach ($realms as $realm) {
try {
$client = $this->client->createClient($realm, $integration, new ClientIdUuidStrategy());
$this->client->addScopeToClient($client, $scopeId);
$this->client->addScopeToClient(
$client,
$realm->scopeConfig->getScopeIdFromIntegrationType($integration)
);

$clientCollection->add($client);
} catch (KeyCloakApiFailed $e) {
Expand Down
13 changes: 9 additions & 4 deletions app/Keycloak/Listeners/UpdateClients.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
use App\Keycloak\Client;
use App\Keycloak\Client\ApiClient;
use App\Keycloak\Exception\KeyCloakApiFailed;
use App\Keycloak\Realms;
use App\Keycloak\Repositories\KeycloakClientRepository;
use App\Keycloak\ScopeConfig;
use App\Keycloak\Converters\IntegrationToKeycloakClientConverter;
use App\Keycloak\Converters\IntegrationUrlConverter;
use Illuminate\Bus\Queueable;
Expand All @@ -31,7 +31,7 @@ public function __construct(
private readonly IntegrationRepository $integrationRepository,
private readonly KeycloakClientRepository $keycloakClientRepository,
private readonly ApiClient $client,
private readonly ScopeConfig $scopeConfig,
private readonly Realms $realms,
private readonly LoggerInterface $logger
) {
}
Expand All @@ -40,11 +40,16 @@ public function handle(IntegrationUpdated|IntegrationUrlCreated|IntegrationUrlUp
{
$integration = $this->integrationRepository->getById($event->id);
$keycloakClients = $this->keycloakClientRepository->getByIntegrationId($event->id);
$scopeId = $this->scopeConfig->getScopeIdFromIntegrationType($integration);

foreach ($keycloakClients as $keycloakClient) {
try {
$this->updateClient($integration, $keycloakClient, $scopeId);
$realm = $this->realms->getRealmByEnvironment($keycloakClient->environment);

$this->updateClient(
$integration,
$keycloakClient,
$realm->scopeConfig->getScopeIdFromIntegrationType($integration)
);
} catch (KeyCloakApiFailed $e) {
$this->failed($event, $e);
}
Expand Down
6 changes: 4 additions & 2 deletions app/Keycloak/Realm.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public function __construct(
string $baseUrl,
public string $clientId,
public string $clientSecret,
public Environment $environment
public Environment $environment,
public ScopeConfig $scopeConfig,
) {
$this->baseUrl = $this->addTrailingSlash($baseUrl);
}
Expand All @@ -29,7 +30,8 @@ public function getMasterRealm(): self
$this->baseUrl,
$this->clientId,
$this->clientSecret,
$this->environment
$this->environment,
$this->scopeConfig,
);
}

Expand Down
25 changes: 24 additions & 1 deletion app/Keycloak/Realms.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@

use App\Domain\Integrations\Environment;
use Illuminate\Support\Collection;
use Ramsey\Uuid\Uuid;

/**
* @extends Collection<int, Realm>
*/
final class Realms extends Collection
{
public function __construct(array $realms=[])
{
parent::__construct($realms);
}

public static function build(): self
{
$realms = new self();
Expand All @@ -23,10 +29,27 @@ public static function build(): self
$environment['base_url'],
$environment['client_id'],
$environment['client_secret'],
Environment::from($publicName)
Environment::from($publicName),
new ScopeConfig(
Uuid::fromString($environment['scope']['search_api_id']),
Uuid::fromString($environment['scope']['entry_api_id']),
Uuid::fromString($environment['scope']['widgets_id']),
Uuid::fromString($environment['scope']['uitpas_id'])
)
));
}

return $realms;
}

public function getRealmByEnvironment(Environment $environment): Realm
{
foreach ($this->items as $realm) {
if ($realm->environment === $environment) {
return $realm;
}
}

throw new \InvalidArgumentException(sprintf('Could not determine realm with the provided environment %s', $environment->value));
}
}
Loading

0 comments on commit 45d1f29

Please sign in to comment.