Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PPF-493 Make realm contain the api uri #1162

Merged
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5b76990
add method to convert string to env enum value
May 24, 2024
0e83bb1
move baseulr, secret, etc to Realm level
May 24, 2024
2c1cc9f
Change Realm to contain the URI - phpstan is ok
May 29, 2024
434df58
continue work fixing tests
May 29, 2024
17c7e26
Pass master realm when fetching token
May 29, 2024
fd663db
Fix updateClientsTest
May 30, 2024
f9bb8e1
Use Tests/TestCase everywhere to make it possible to fake facade App
May 30, 2024
20c8fac
unclear var
May 30, 2024
fcf43c7
fixed Create Clients Test
May 30, 2024
93363ed
Make test realms more testery
May 30, 2024
b553233
fix block client test
May 30, 2024
f03e96d
Fix sorting in gui
May 30, 2024
de00fd2
making naming of realms consistent with other clients (uitidv1, auth0)
May 30, 2024
3324738
Always use id, not client id
May 30, 2024
f780684
rename of enviroment
May 30, 2024
4d4989c
fetchIsClientActive now also used id instead of client id
May 30, 2024
802d3ac
style
May 30, 2024
d10efaf
fix tests change from client id to id
May 30, 2024
2db8a1b
improve label in gui
May 30, 2024
737b8dc
Use Enum directly
May 30, 2024
8b4b0d8
Remove ConfigFactory, use isEnabled flag directly
May 30, 2024
960ccb2
Rename stag to acc
May 30, 2024
1af6dd4
remove Config object
May 30, 2024
72149c9
Remove Realms dependency from getMissingRealmsByIntegrationId()
May 30, 2024
07b74bc
Change keycloak Realm to Enviroment
Jun 6, 2024
95f82bb
Use enviroment instead of realm
Jun 6, 2024
5174ba0
Fix callback uri not saved - problem with indexes
Jun 6, 2024
83e38d0
Make getMissingRealmsByIntegrationId() return Envs, not Realms
Jun 6, 2024
52ab009
Fix constructor Client with Env param
Jun 6, 2024
fdf1418
Add RealmCollection to singleton service container, cleanup RealmColl…
Jun 6, 2024
752f815
Change return type RealmCollection
Jun 6, 2024
380ea7a
Expose keycloak clients to Api
Jun 6, 2024
23feed6
Change Realm & Env Collection name
Jun 7, 2024
5842daf
Merge pull request #1181 from cultuurnet/PPF-493/integration-expose-k…
grubolsch Jun 7, 2024
87df7a8
change client id to string
Jun 7, 2024
810569b
Merge branch 'PPF-493/Make-Realm-contain-the-API-uri' of github.com:c…
Jun 7, 2024
ada312e
Rename ClientCollection to Client
Jun 7, 2024
62cc202
Send internal id not client id to fetch client
Jun 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -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_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'
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
Expand Down
5 changes: 3 additions & 2 deletions app/Domain/Integrations/Integration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/Domain/Integrations/Models/IntegrationModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +27,6 @@
use App\Insightly\Models\InsightlyMappingModel;
use App\Insightly\Resources\ResourceType;
use App\Keycloak\Models\KeycloakClientModel;
use App\Keycloak\RealmCollection;
use App\Models\UuidModel;
use App\UiTiDv1\Models\UiTiDv1ConsumerModel;
use App\UiTiDv1\UiTiDv1Environment;
Expand Down Expand Up @@ -266,7 +266,7 @@ public function hasMissingUiTiDv1Consumers(): bool

public function hasMissingKeycloakConsumers(): bool
{
return $this->keycloakClients()->count() < count(RealmCollection::getRealms());
return $this->keycloakClients()->count() < count(Environment::cases());
}

public function toDomain(): Integration
Expand Down
31 changes: 25 additions & 6 deletions app/Keycloak/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,14 +17,13 @@ public function __construct(
public UuidInterface $integrationId,
public UuidInterface $clientId,
public string $clientSecret,
public Realm $realm,
public Environment $environment,
) {
}

public static function createFromJson(
Realm $realm,
UuidInterface $integrationId,
UuidInterface $clientId,
array $data
): self {
if (empty($data['secret'])) {
Expand All @@ -32,14 +33,32 @@ public static function createFromJson(
return new self(
Uuid::fromString($data['id']),
$integrationId,
$clientId,
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)
);
}
}
4 changes: 2 additions & 2 deletions app/Keycloak/Client/ApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ 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;
public function fetchClient(Realm $realm, Integration $integration, UuidInterface $id): Client;

public function fetchIsClientActive(Client $client): bool;

Expand Down
55 changes: 27 additions & 28 deletions app/Keycloak/Client/KeycloakApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,18 +30,17 @@ 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
grubolsch marked this conversation as resolved.
Show resolved Hide resolved
{
$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
);
} catch (Throwable $e) {
throw KeyCloakApiFailed::failedToCreateClient($e->getMessage());
Expand All @@ -54,7 +52,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;
}
Expand All @@ -63,20 +61,21 @@ 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()));
}

/**
* @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->getRealm()->internalName, $client->id->toString(), $scopeId->toString())
),
$client->getRealm()
);
} catch (GuzzleException $e) {
grubolsch marked this conversation as resolved.
Show resolved Hide resolved
throw KeyCloakApiFailed::failedToAddScopeToClient($e->getMessage());
Expand All @@ -95,8 +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->getRealm()
);

// Will throw a 404 when scope not attached to client, but this is no problem.
Expand All @@ -112,14 +112,15 @@ 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',
'admin/realms/' . $realm->internalName . '/clients?' . http_build_query(['clientId' => $clientId->toString()])
)
sprintf('admin/realms/%s/clients/%s', $realm->internalName, $id->toString())
),
$realm
);

$body = $response->getBody()->getContents();
Expand All @@ -128,8 +129,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());
}
Expand All @@ -144,24 +144,22 @@ 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->getRealm()->internalName, $client->id->toString())
),
$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);

$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());
throw KeyCloakApiFailed::failedToFetchClient($client->getRealm(), $e->getMessage());
}
}

Expand Down Expand Up @@ -190,10 +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->getRealm()
);

if ($response->getStatusCode() !== 204) {
Expand Down
16 changes: 9 additions & 7 deletions app/Keycloak/Client/KeycloakHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace App\Keycloak\Client;

use App\Keycloak\Config;
use App\Keycloak\Realm;
use App\Keycloak\TokenStrategy\TokenStrategy;
use GuzzleHttp\ClientInterface;
Expand All @@ -15,29 +14,32 @@

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);
}

/**
* @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);
}
}
29 changes: 0 additions & 29 deletions app/Keycloak/Config.php

This file was deleted.

10 changes: 10 additions & 0 deletions app/Keycloak/KeycloakConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace App\Keycloak;

enum KeycloakConfig: string
{
case isEnabled = 'keycloak.enabled';
}
Loading