From 2d46b05b1e4149be64df2c248b35c8058f37f944 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Sat, 27 Jun 2026 23:59:21 +0200 Subject: [PATCH] deploy --- public/auth/callback/index.php | 15 +++- public/auth/keycloak/index.php | 3 +- public/index.php | 10 +++ src/App/KeycloakAuth.php | 7 +- system/apps/admin-apps/assets/app.css | 63 +++++++++++++++++ system/apps/admin-apps/assets/app.js | 53 ++++++++++++++ system/apps/admin-apps/page.php | 99 +++++++++++++++++---------- 7 files changed, 209 insertions(+), 41 deletions(-) diff --git a/public/auth/callback/index.php b/public/auth/callback/index.php index 2f027a60..23de336b 100644 --- a/public/auth/callback/index.php +++ b/public/auth/callback/index.php @@ -16,7 +16,9 @@ $auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); $accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); $state = (string) ($_GET['state'] ?? ''); $expectedState = (string) ($_SESSION['keycloak_oauth_state'] ?? ''); +$oauthMode = (string) ($_SESSION['keycloak_oauth_mode'] ?? 'interactive'); $code = (string) ($_GET['code'] ?? ''); +$error = trim((string) ($_GET['error'] ?? '')); $title = 'Keycloak Callback'; $message = 'Anmeldung wird verarbeitet.'; @@ -25,11 +27,21 @@ $redirectTarget = '/'; if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $state)) { http_response_code(400); $message = 'State-Pruefung fehlgeschlagen. Bitte den Login erneut starten.'; +} elseif ($error !== '') { + unset($_SESSION['keycloak_oauth_state'], $_SESSION['keycloak_oauth_mode']); + + if ($oauthMode === 'silent' && in_array($error, ['login_required', 'interaction_required', 'consent_required'], true)) { + header('Location: ' . $redirectTarget, true, 302); + exit; + } + + http_response_code(400); + $message = 'Keycloak hat die Anmeldung abgelehnt: ' . $error . '.'; } elseif ($code === '') { http_response_code(400); $message = 'Kein Authorization Code von Keycloak erhalten.'; } else { - unset($_SESSION['keycloak_oauth_state']); + unset($_SESSION['keycloak_oauth_state'], $_SESSION['keycloak_oauth_mode']); $tokenExchange = $auth->exchangeAuthorizationCode($code); @@ -67,6 +79,7 @@ if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $stat exit; } else { $auth->establishSession($tokenPayload, $userPayload); + unset($_SESSION['desktop_silent_sso_attempted']); header('Location: ' . $redirectTarget, true, 302); exit; } diff --git a/public/auth/keycloak/index.php b/public/auth/keycloak/index.php index ce8b1cef..9437fa19 100644 --- a/public/auth/keycloak/index.php +++ b/public/auth/keycloak/index.php @@ -11,7 +11,8 @@ session_start(); $projectRoot = dirname(__DIR__, 3); $auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); -$loginUrl = $auth->loginUrl(); +$mode = trim((string) ($_GET['mode'] ?? '')); +$loginUrl = $auth->loginUrl($mode === 'silent' ? ['prompt' => 'none'] : []); if ($loginUrl === null) { http_response_code(503); diff --git a/public/index.php b/public/index.php index 9bdbbf15..2cfeeb12 100644 --- a/public/index.php +++ b/public/index.php @@ -41,6 +41,16 @@ if ($auth->isAuthenticated()) { } } +if ( + !$auth->isAuthenticated() + && $auth->isConfigured() + && empty($_SESSION['desktop_silent_sso_attempted']) +) { + $_SESSION['desktop_silent_sso_attempted'] = true; + header('Location: /auth/keycloak/?mode=silent', true, 302); + exit; +} + $app = new App($projectRoot); $desktopPayload = $app->desktopPayload(); diff --git a/src/App/KeycloakAuth.php b/src/App/KeycloakAuth.php index eae94d3d..be7bdf15 100644 --- a/src/App/KeycloakAuth.php +++ b/src/App/KeycloakAuth.php @@ -62,7 +62,10 @@ final class KeycloakAuth return $this->allowDesktopPreview() && isset($_GET[$previewKey]); } - public function loginUrl(): ?string + /** + * @param array $authorizeOverrides + */ + public function loginUrl(array $authorizeOverrides = []): ?string { if (!$this->isConfigured()) { return null; @@ -70,6 +73,7 @@ final class KeycloakAuth $state = bin2hex(random_bytes(16)); $_SESSION['keycloak_oauth_state'] = $state; + $_SESSION['keycloak_oauth_mode'] = (string) ($authorizeOverrides['prompt'] ?? '') === 'none' ? 'silent' : 'interactive'; $params = array_merge( [ @@ -83,6 +87,7 @@ final class KeycloakAuth ? $this->config['extra_authorize_params'] : [] ); + $params = array_merge($params, $authorizeOverrides); return rtrim($this->baseUrl(), '/') . '/realms/' . rawurlencode($this->realm()) diff --git a/system/apps/admin-apps/assets/app.css b/system/apps/admin-apps/assets/app.css index 4a987dfa..f62fe8a6 100644 --- a/system/apps/admin-apps/assets/app.css +++ b/system/apps/admin-apps/assets/app.css @@ -257,6 +257,10 @@ min-width: 220px; } +#admin-apps-app .aa-inline-access-trigger { + justify-self: start; +} + #admin-apps-app .aa-inline-access-current { margin: 0; font-size: 14px; @@ -326,6 +330,65 @@ justify-self: start; } +#admin-apps-app .aa-secondary { + background: rgba(226, 232, 240, 0.9); + color: #0f172a; +} + +#admin-apps-app .aa-modal { + width: min(680px, calc(100vw - 40px)); + max-height: calc(100vh - 40px); + padding: 0; + border: 0; + border-radius: 22px; + background: transparent; + box-shadow: 0 22px 70px rgba(15, 23, 42, 0.35); +} + +#admin-apps-app .aa-modal::backdrop { + background: rgba(15, 23, 42, 0.42); + backdrop-filter: blur(4px); +} + +#admin-apps-app .aa-modal-card { + display: grid; + gap: 18px; + padding: 22px; + border-radius: 22px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.96)); +} + +#admin-apps-app .aa-modal-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; +} + +#admin-apps-app .aa-modal-title { + margin: 0 0 6px; + font-size: 22px; + line-height: 1.15; + color: #0f172a; +} + +#admin-apps-app .aa-modal-body { + display: grid; + gap: 14px; +} + +#admin-apps-app .aa-modal-close { + width: 38px; + height: 38px; + border: 0; + border-radius: 999px; + background: rgba(226, 232, 240, 0.9); + color: #0f172a; + font-size: 22px; + line-height: 1; + cursor: pointer; +} + #admin-apps-app .aa-message.is-success { background: rgba(34, 197, 94, 0.12); color: #166534; diff --git a/system/apps/admin-apps/assets/app.js b/system/apps/admin-apps/assets/app.js index 903fb9d5..c9d8799c 100644 --- a/system/apps/admin-apps/assets/app.js +++ b/system/apps/admin-apps/assets/app.js @@ -22,6 +22,54 @@ }; const bindInteractions = (host, loadRoute) => { + host.querySelectorAll('[data-aa-open-modal]').forEach((button) => { + if (button.dataset.aaBound === '1') { + return; + } + + button.dataset.aaBound = '1'; + button.addEventListener('click', () => { + const modalId = button.getAttribute('data-aa-open-modal') || ''; + const modal = host.querySelector(`[data-aa-modal="${CSS.escape(modalId)}"]`); + if (modal instanceof HTMLDialogElement) { + modal.showModal(); + } + }); + }); + + host.querySelectorAll('[data-aa-close-modal]').forEach((button) => { + if (button.dataset.aaBound === '1') { + return; + } + + button.dataset.aaBound = '1'; + button.addEventListener('click', () => { + const modal = button.closest('dialog'); + if (modal instanceof HTMLDialogElement) { + modal.close(); + } + }); + }); + + host.querySelectorAll('dialog[data-aa-modal]').forEach((dialog) => { + if (dialog.dataset.aaBound === '1') { + return; + } + + dialog.dataset.aaBound = '1'; + dialog.addEventListener('click', (event) => { + const rect = dialog.getBoundingClientRect(); + const inside = event.clientX >= rect.left + && event.clientX <= rect.right + && event.clientY >= rect.top + && event.clientY <= rect.bottom; + + if (!inside) { + dialog.close(); + } + }); + }); + host.querySelectorAll('a[href]').forEach((link) => { if (link.dataset.aaBound === '1') { return; @@ -66,6 +114,11 @@ throw new Error(`HTTP ${response.status}`); } + const modal = form.closest('dialog'); + if (modal instanceof HTMLDialogElement) { + modal.close(); + } + host.innerHTML = html; bindInteractions(host, loadRoute); } catch (error) { diff --git a/system/apps/admin-apps/page.php b/system/apps/admin-apps/page.php index 38e2421a..86dea8dd 100644 --- a/system/apps/admin-apps/page.php +++ b/system/apps/admin-apps/page.php @@ -289,48 +289,71 @@ ob_start(); -
- - - - - +

- + +
- -
- Bearbeiten -
- - - + + + + + + + +
+
+

Berechtigungen

+

+

LDAP-Gruppen und Freigabe fuer den Logoff-Desktop zentral festlegen.

+ +
+ +
+

Aktuell:

+ + + +
+ + + +
+ +

Keine LDAP-Gruppen gefunden.

+ +
+ +
+ -
- -

Keine LDAP-Gruppen gefunden.

- -
+ + +