From 260dec933a7129069a6ca716a025323cdcc08131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Mon, 25 Nov 2024 15:31:48 +0100 Subject: [PATCH] WIP Move to SSP UI --- public/assets/css/src/default.css | 2 + public/assets/js/src/default.js | 22 + routing/routes/routes.php | 3 + src/Codebooks/ParametersEnum.php | 10 + src/Codebooks/RoutesEnum.php | 1 + src/Controllers/Admin/ClientController.php | 40 +- src/Controllers/Admin/ConfigController.php | 7 +- src/Controllers/Client/ShowController.php | 2 +- src/Utils/Routes.php | 20 + templates/base.twig | 7 +- templates/clients.twig | 5 +- templates/clients/show-old.twig | 231 +++++++++ templates/clients/show-ssp.twig | 99 ---- templates/clients/show.twig | 461 +++++++++--------- .../Controller/Client/ShowControllerTest.php | 2 +- 15 files changed, 584 insertions(+), 328 deletions(-) create mode 100644 public/assets/js/src/default.js create mode 100644 src/Codebooks/ParametersEnum.php create mode 100644 templates/clients/show-old.twig delete mode 100644 templates/clients/show-ssp.twig diff --git a/public/assets/css/src/default.css b/public/assets/css/src/default.css index 612e4f20..cc42a760 100644 --- a/public/assets/css/src/default.css +++ b/public/assets/css/src/default.css @@ -112,3 +112,5 @@ table.client-table { width: 25%; font-weight: bolder; } + +.confirm-action {} diff --git a/public/assets/js/src/default.js b/public/assets/js/src/default.js new file mode 100644 index 00000000..e6eb33fc --- /dev/null +++ b/public/assets/js/src/default.js @@ -0,0 +1,22 @@ + +(function() { + + // Attach `confirm-action` click event to all elements with the `confirm-action` class. + document.querySelectorAll('.confirm-action').forEach(button => { + button.addEventListener('click', function (event) { + // Get custom confirmation text + const confirmText = this.getAttribute('data-confirm-text') ?? 'Are you sure?'; + // Optional: Retrieve additional data + const itemId = this.getAttribute('data-confirm-id') ?? 'N/A'; + + if (!confirm(confirmText)) { + // Prevent the default action if the user cancels + event.preventDefault(); + } else { + // Optional: Handle confirmed action + console.log( + `Confirmed action "${confirmText}" for item with ID "${itemId}"`); + } + }); + }); +})(); diff --git a/routing/routes/routes.php b/routing/routes/routes.php index 3bf3469a..9c89c99c 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -43,6 +43,9 @@ ->controller([ClientController::class, 'index']); $routes->add(RoutesEnum::AdminClientsShow->name, RoutesEnum::AdminClientsShow->value) ->controller([ClientController::class, 'show']); + $routes->add(RoutesEnum::AdminClientsResetSecret->name, RoutesEnum::AdminClientsResetSecret->value) + ->controller([ClientController::class, 'resetSecret']) + ->methods([HttpMethodsEnum::POST->value]); /***************************************************************************************************************** * OpenID Connect diff --git a/src/Codebooks/ParametersEnum.php b/src/Codebooks/ParametersEnum.php new file mode 100644 index 00000000..bb8630ad --- /dev/null +++ b/src/Codebooks/ParametersEnum.php @@ -0,0 +1,10 @@ +authorization->requireAdminOrUserWithPermission(AuthContextService::PERM_CLIENT); } @@ -33,7 +41,7 @@ public function __construct( */ protected function getClientFromRequest(Request $request): ClientEntityInterface { - ($clientId = $request->query->getString('client_id')) + ($clientId = $request->query->getString(ParametersEnum::ClientId->value)) || throw new OidcException('Client ID not provided.'); $authedUserId = $this->authorization->isAdmin() ? null : $this->authorization->getUserId(); @@ -50,7 +58,6 @@ public function index(Request $request): Response $pagination = $this->clientRepository->findPaginated($page, $query, $authedUserId); - return $this->templateFactory->build( 'oidc:clients.twig', [ @@ -71,9 +78,8 @@ public function show(Request $request): Response $client = $this->getClientFromRequest($request); $allowedOrigins = $this->allowedOriginRepository->get($client->getIdentifier()); - // TODO mivanci rename *-ssp.twig templates after removing old ones. return $this->templateFactory->build( - 'oidc:clients/show-ssp.twig', + 'oidc:clients/show.twig', [ 'client' => $client, 'allowedOrigins' => $allowedOrigins, @@ -81,4 +87,30 @@ public function show(Request $request): Response RoutesEnum::AdminClients->value, ); } + + /** + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException + */ + public function resetSecret(Request $request): Response + { + $client = $this->getClientFromRequest($request); + + $oldSecret = $request->request->get('secret'); + + if ($oldSecret !== $client->getSecret()) { + throw new OidcException('Client secret does not match.'); + } + + $client->restoreSecret($this->sspBridge->utils()->random()->generateID()); + $authedUserId = $this->authorization->isAdmin() ? null : $this->authorization->getUserId(); + $this->clientRepository->update($client, $authedUserId); + + $message = Translate::noop('Client secret has been reset.'); + $this->sessionMessagesService->addMessage($message); + + return $this->routes->getRedirectResponseToModuleUrl( + RoutesEnum::AdminClientsShow->value, + [ParametersEnum::ClientId->value => $client->getIdentifier()], + ); + } } diff --git a/src/Controllers/Admin/ConfigController.php b/src/Controllers/Admin/ConfigController.php index f87fde1c..e5100a1c 100644 --- a/src/Controllers/Admin/ConfigController.php +++ b/src/Controllers/Admin/ConfigController.php @@ -11,8 +11,8 @@ use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\DatabaseMigration; use SimpleSAML\Module\oidc\Services\SessionMessagesService; +use SimpleSAML\Module\oidc\Utils\Routes; use SimpleSAML\OpenID\Federation; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; class ConfigController @@ -24,6 +24,7 @@ public function __construct( protected readonly DatabaseMigration $databaseMigration, protected readonly SessionMessagesService $sessionMessagesService, protected readonly Federation $federation, + protected readonly Routes $routes, ) { $this->authorization->requireAdmin(true); } @@ -44,14 +45,14 @@ public function runMigrations(): Response if ($this->databaseMigration->isMigrated()) { $message = Translate::noop('Database is already migrated.'); $this->sessionMessagesService->addMessage($message); - return new RedirectResponse($this->moduleConfig->getModuleUrl(RoutesEnum::AdminMigrations->value)); + return $this->routes->getRedirectResponseToModuleUrl(RoutesEnum::AdminMigrations->value); } $this->databaseMigration->migrate(); $message = Translate::noop('Database migrated successfully.'); $this->sessionMessagesService->addMessage($message); - return new RedirectResponse($this->moduleConfig->getModuleUrl(RoutesEnum::AdminMigrations->value)); + return $this->routes->getRedirectResponseToModuleUrl(RoutesEnum::AdminMigrations->value); } public function protocolSettings(): Response diff --git a/src/Controllers/Client/ShowController.php b/src/Controllers/Client/ShowController.php index c5e2efd1..ed96c22e 100644 --- a/src/Controllers/Client/ShowController.php +++ b/src/Controllers/Client/ShowController.php @@ -49,7 +49,7 @@ public function __invoke(ServerRequest $request): Template $client = $this->getClientFromRequest($request); $allowedOrigins = $this->allowedOriginRepository->get($client->getIdentifier()); - return $this->templateFactory->build('oidc:clients/show.twig', [ + return $this->templateFactory->build('oidc:clients/show-old.twig', [ 'client' => $client, 'allowedOrigins' => $allowedOrigins, ]); diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index a75d2f80..a5d18061 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -7,6 +7,7 @@ use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Codebooks\RoutesEnum; use SimpleSAML\Module\oidc\ModuleConfig; +use Symfony\Component\HttpFoundation\RedirectResponse; class Routes { @@ -23,6 +24,19 @@ public function getModuleUrl(string $resource = '', array $parameters = []): str return $this->sspBridge->module()->getModuleUrl($resource, $parameters); } + public function getRedirectResponseToModuleUrl( + string $resource = '', + array $parameters = [], + int $status = 302, + array $headers = [], + ): RedirectResponse { + return new RedirectResponse( + $this->getModuleUrl($resource, $parameters), + $status, + $headers, + ); + } + /***************************************************************************************************************** * Admin area ****************************************************************************************************************/ @@ -60,6 +74,12 @@ public function urlAdminClientsShow(string $clientId, array $parameters = []): s return $this->getModuleUrl(RoutesEnum::AdminClientsShow->value, $parameters); } + public function urlAdminClientsResetSecret(string $clientId, array $parameters = []): string + { + $parameters['client_id'] = $clientId; + return $this->getModuleUrl(RoutesEnum::AdminClientsResetSecret->value, $parameters); + } + /***************************************************************************************************************** * OpenID Connect ****************************************************************************************************************/ diff --git a/templates/base.twig b/templates/base.twig index 4df64ea1..7fd9eafe 100644 --- a/templates/base.twig +++ b/templates/base.twig @@ -38,6 +38,11 @@ {% endblock content -%} -{% block postload %}{% endblock postload %} +{% block postload %} + + {{ parent() }} + + +{% endblock postload %} {% block oidcPostload %}{% endblock %} diff --git a/templates/clients.twig b/templates/clients.twig index c071e7a2..3c0753fe 100644 --- a/templates/clients.twig +++ b/templates/clients.twig @@ -67,7 +67,10 @@ - + diff --git a/templates/clients/show-old.twig b/templates/clients/show-old.twig new file mode 100644 index 00000000..3992a13f --- /dev/null +++ b/templates/clients/show-old.twig @@ -0,0 +1,231 @@ +{% extends "@oidc/oidc_base.twig" %} + +{% set pagetitle = 'Show OpenID Connect Client' | trans %} + +{% block pre_breadcrump %} + / + {{ 'OpenID Connect Client Registry'|trans }} +{% endblock %} + +{% block content %} +

{{ pagetitle }}

+ + + +
+ {{ 'Registration:'|trans }} {{ client.registrationType.description }} | + {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | + {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | + {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }} +
+ + + + + + + + + + + + + + + + + + + + + + + + {% if client.isConfidential %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + {% if client.isConfidential == false %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ '{oidc:client:name}'|trans }} + {{ client.name }} +
{{ '{oidc:client:description}'|trans }}{{ client.description }}
{{ '{oidc:client:state}'|trans }} + + {{ (client.isEnabled ? '{oidc:client:is_enabled}' : '{oidc:client:deactivated}')|trans }} + +
{{ '{oidc:client:type}'|trans }}{{ (client.isConfidential ? '{oidc:client:confidential}' : '{oidc:client:public}')|trans }}
{{ '{oidc:client:identifier}'|trans }}{{ client.identifier }} + +
{{ '{oidc:client:secret}'|trans }} + {{ client.secret }} + +
{{ '{oidc:client:auth_source}'|trans }}{{ client.authSourceId }}
{{ '{oidc:client:redirect_uri}'|trans }} +
    + {% for uri in client.redirectUri %} +
  • {{ uri }}
  • + {% endfor %} +
+
{{ '{oidc:client:scopes}'|trans }} +
    + {% for key, scope in client.scopes %} +
  • {{ scope }}
  • + {% endfor %} +
+
{{ '{oidc:client:backchannel_logout_uri}'|trans }}{{ client.backChannelLogoutUri }}
{{ '{oidc:client:post_logout_redirect_uri}'|trans }} +
    + {% for uri in client.postLogoutRedirectUri %} +
  • {{ uri }}
  • + {% endfor %} +
+
{{ '{oidc:client:allowed_origin}'|trans }} +
    + {% for allowedOrigin in allowedOrigins %} +
  • {{ allowedOrigin }}
  • + {% endfor %} +
+
{{ 'Signed JWKS URI'|trans }}{{ client.signedJwksUri }}
{{ 'JWKS URI'|trans }}{{ client.jwksUri }}
{{ 'JWKS'|trans }} + {% if client.jwks %} +
+
+
+ {{ client.jwks|json_encode(constant('JSON_PRETTY_PRINT')) }} +
+
+
+ {% endif %} +
{{ '{oidc:client:owner}'|trans }}{{ client.owner }}
OpenID Federation related properties
{{ 'Is federated'|trans }}{{ (client.isFederated ? 'Yes' : 'No')|trans }}
{{ 'Entity Identifier'|trans }}{{ client.entityIdentifier }}
{{ 'Client registration types'|trans }} +
    + {% for clientRegistrationType in client.clientRegistrationTypes %} +
  • {{ clientRegistrationType }}
  • + {% endfor %} +
+
{{ 'Federation JWKS'|trans }} + {% if client.federationJwks %} +
+
+
+ {{ client.federationJwks|json_encode(constant('JSON_PRETTY_PRINT')) }} +
+
+
+ {% endif %} +
+
+ + {{ '{oidc:return}'|trans }} + + + {{ '{oidc:edit}'|trans }} + + {% if client.isConfidential %} +
+ {{ '{oidc:client:reset_secret}'|trans }} +
+ + {% endif %} +
+
+ +{% endblock %} + +{% block postload %} + {{ parent() }} + + +{% endblock %} diff --git a/templates/clients/show-ssp.twig b/templates/clients/show-ssp.twig deleted file mode 100644 index 9673b222..00000000 --- a/templates/clients/show-ssp.twig +++ /dev/null @@ -1,99 +0,0 @@ -{% set subPageTitle = 'Client '|trans ~ client.getIdentifier %} - -{% extends "@oidc/base.twig" %} - -{% block oidcContent %} - -
-
- - - {{ client.enabled ? 'enabled'|trans : 'disabled'|trans }} - -
- -
- -
- {{ 'Registration:'|trans }} {{ client.registrationType.description }} | - {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | - {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | - {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }} -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{ 'Name and description'|trans }} - - {{ client.name }}
- {{ client.description }} -
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
-
-{% endblock oidcContent -%} diff --git a/templates/clients/show.twig b/templates/clients/show.twig index 3992a13f..9925c0aa 100644 --- a/templates/clients/show.twig +++ b/templates/clients/show.twig @@ -1,231 +1,256 @@ -{% extends "@oidc/oidc_base.twig" %} +{% set subPageTitle = 'Client '|trans ~ client.getIdentifier %} -{% set pagetitle = 'Show OpenID Connect Client' | trans %} +{% extends "@oidc/base.twig" %} -{% block pre_breadcrump %} - / - {{ 'OpenID Connect Client Registry'|trans }} -{% endblock %} +{% block oidcContent %} -{% block content %} -

{{ pagetitle }}

- - +
+
+ + + {{ client.enabled ? 'enabled'|trans : 'disabled'|trans }} + +
+ +
-
+
{{ 'Registration:'|trans }} {{ client.registrationType.description }} | {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }}
- - - - - - - - - - - - - - - - - - - - - - - {% if client.isConfidential %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - {% if client.isConfidential == false %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ '{oidc:client:name}'|trans }} - {{ client.name }} -
{{ '{oidc:client:description}'|trans }}{{ client.description }}
{{ '{oidc:client:state}'|trans }} - - {{ (client.isEnabled ? '{oidc:client:is_enabled}' : '{oidc:client:deactivated}')|trans }} - -
{{ '{oidc:client:type}'|trans }}{{ (client.isConfidential ? '{oidc:client:confidential}' : '{oidc:client:public}')|trans }}
{{ '{oidc:client:identifier}'|trans }}{{ client.identifier }} - -
{{ '{oidc:client:secret}'|trans }} - {{ client.secret }} - -
{{ '{oidc:client:auth_source}'|trans }}{{ client.authSourceId }}
{{ '{oidc:client:redirect_uri}'|trans }} -
    - {% for uri in client.redirectUri %} -
  • {{ uri }}
  • - {% endfor %} -
-
{{ '{oidc:client:scopes}'|trans }} -
    - {% for key, scope in client.scopes %} -
  • {{ scope }}
  • - {% endfor %} -
-
{{ '{oidc:client:backchannel_logout_uri}'|trans }}{{ client.backChannelLogoutUri }}
{{ '{oidc:client:post_logout_redirect_uri}'|trans }} -
    - {% for uri in client.postLogoutRedirectUri %} -
  • {{ uri }}
  • - {% endfor %} -
-
{{ '{oidc:client:allowed_origin}'|trans }} -
    - {% for allowedOrigin in allowedOrigins %} -
  • {{ allowedOrigin }}
  • - {% endfor %} -
-
{{ 'Signed JWKS URI'|trans }}{{ client.signedJwksUri }}
{{ 'JWKS URI'|trans }}{{ client.jwksUri }}
{{ 'JWKS'|trans }} - {% if client.jwks %} -
-
-
- {{ client.jwks|json_encode(constant('JSON_PRETTY_PRINT')) }} -
-
-
- {% endif %} -
{{ '{oidc:client:owner}'|trans }}{{ client.owner }}
OpenID Federation related properties
{{ 'Is federated'|trans }}{{ (client.isFederated ? 'Yes' : 'No')|trans }}
{{ 'Entity Identifier'|trans }}{{ client.entityIdentifier }}
{{ 'Client registration types'|trans }} -
    - {% for clientRegistrationType in client.clientRegistrationTypes %} -
  • {{ clientRegistrationType }}
  • - {% endfor %} -
-
{{ 'Federation JWKS'|trans }} - {% if client.federationJwks %} -
-
-
- {{ client.federationJwks|json_encode(constant('JSON_PRETTY_PRINT')) }} -
-
-
- {% endif %} -
-
- - {{ '{oidc:return}'|trans }} - - - {{ '{oidc:edit}'|trans }} - - {% if client.isConfidential %} -
- {{ '{oidc:client:reset_secret}'|trans }} -
- +
+
+ + + + + + + + + + + + + + + + + + + {% if client.isConfidential %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + - -
+ {{ 'Name and description'|trans }} + + {{ client.name }}
+ {{ client.description }} +
+ {{ 'Type' }} + + {{ (client.isConfidential ? 'Confidential' : 'Public')|trans }} +
+ {{ 'Identifier'|trans }} + + {{ client.identifier }} +
+ {{ 'Secret'|trans }} + +
+ {{- client.secret -}} + + +
+
+ {{ 'Authentication Source'|trans }} + + {{ client.authSourceId|default('N/A'|trans) }} +
+ {{ 'Redirect URIs'|trans }} + +
    + {% for uri in client.redirectUri %} +
  • {{ uri }}
  • + {% endfor %} +
+
+ {{ 'Scopes'|trans }} + +
    + {% for key, scope in client.scopes %} +
  • {{ scope }}
  • + {% endfor %} +
+
+ {{ 'Back-channel Logout URI'|trans }} + + {{ client.backChannelLogoutUri|default('N/A') }} +
+ {{ 'Post-logout Redirect URIs'|trans }} + + {% if client.postLogoutRedirectUri is not empty %} +
    + {% for uri in client.postLogoutRedirectUri %} +
  • {{ uri }}
  • + {% endfor %} +
+ {% else %} + {{ 'N/A'|trans }} {% endif %} - - -
- -{% endblock %} - -{% block postload %} - {{ parent() }} - - -{% endblock %} +
+

{{ 'OpenID Federation Related Properties'|trans }}

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'Is Federated'|trans }} + + {{ (client.isFederated ? 'Yes' : 'No')|trans }} +
+ {{ 'Entity Identifier'|trans }} + + {{ client.entityIdentifier|default('N/A'|trans) }} +
+ {{ 'Client Registration Types'|trans }} + +
    + {% for clientRegistrationType in client.clientRegistrationTypes %} +
  • {{ clientRegistrationType }}
  • + {% endfor %} +
+
+ {{ 'Federation JWKS'|trans }} + + {% if client.federationJwks %} + + {{- client.federationJwks|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'N/A'|trans }} + {% endif %} +
+
+{% endblock oidcContent -%} diff --git a/tests/unit/src/Controller/Client/ShowControllerTest.php b/tests/unit/src/Controller/Client/ShowControllerTest.php index 52878ebd..1516fa1a 100644 --- a/tests/unit/src/Controller/Client/ShowControllerTest.php +++ b/tests/unit/src/Controller/Client/ShowControllerTest.php @@ -92,7 +92,7 @@ public function testItShowsClientDescription(): void ->expects($this->once()) ->method('build') ->with( - 'oidc:clients/show.twig', + 'oidc:clients/show-old.twig', [ 'client' => $this->clientEntityMock, 'allowedOrigins' => [],