deploy
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -62,7 +62,10 @@ final class KeycloakAuth
|
||||
return $this->allowDesktopPreview() && isset($_GET[$previewKey]);
|
||||
}
|
||||
|
||||
public function loginUrl(): ?string
|
||||
/**
|
||||
* @param array<string, string> $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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -289,13 +289,33 @@ ob_start();
|
||||
<td><?= $h((string) ($app['app_scope'] ?? '')) ?></td>
|
||||
<td><?= $h(!empty($app['installable']) ? 'Custom App' : 'System') ?></td>
|
||||
<td>
|
||||
<form class="aa-inline-access-form" method="post" action="/admin/apps/">
|
||||
<div class="aa-inline-access-form">
|
||||
<p class="aa-inline-access-current"><?= $h((string) ($app['required_groups_display'] ?? '')) ?></p>
|
||||
<button
|
||||
class="window-app-button aa-inline-access-trigger"
|
||||
type="button"
|
||||
data-aa-open-modal="access-<?= $h((string) ($app['app_id'] ?? '')) ?>"
|
||||
>Berechtigungen</button>
|
||||
</div>
|
||||
|
||||
<dialog class="aa-modal" data-aa-modal="access-<?= $h((string) ($app['app_id'] ?? '')) ?>">
|
||||
<form class="aa-modal-card" method="post" action="/admin/apps/">
|
||||
<input type="hidden" name="csrf_token" value="<?= $h($csrfToken) ?>">
|
||||
<input type="hidden" name="action" value="save-app-access">
|
||||
<input type="hidden" name="active_section" value="overview">
|
||||
<input type="hidden" name="app_id" value="<?= $h((string) ($app['app_id'] ?? '')) ?>">
|
||||
|
||||
<p class="aa-inline-access-current"><?= $h((string) ($app['required_groups_display'] ?? '')) ?></p>
|
||||
<div class="aa-modal-head">
|
||||
<div>
|
||||
<p class="window-app-kicker aa-kicker">Berechtigungen</p>
|
||||
<h4 class="aa-modal-title"><?= $h((string) ($app['title'] ?? '')) ?></h4>
|
||||
<p class="aa-copy">LDAP-Gruppen und Freigabe fuer den Logoff-Desktop zentral festlegen.</p>
|
||||
</div>
|
||||
<button class="aa-modal-close" type="button" data-aa-close-modal>×</button>
|
||||
</div>
|
||||
|
||||
<div class="aa-modal-body">
|
||||
<p class="aa-inline-access-current">Aktuell: <?= $h((string) ($app['required_groups_display'] ?? '')) ?></p>
|
||||
<label class="aa-inline-access-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -307,8 +327,6 @@ ob_start();
|
||||
</label>
|
||||
|
||||
<?php if ($availableGroups !== []): ?>
|
||||
<details class="aa-inline-access-details">
|
||||
<summary>Bearbeiten</summary>
|
||||
<div class="aa-inline-access-groups">
|
||||
<?php foreach ($availableGroups as $group): ?>
|
||||
<label class="aa-inline-access-option">
|
||||
@@ -325,12 +343,17 @@ ob_start();
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button class="window-app-button aa-primary aa-inline-access-submit" type="submit">Speichern</button>
|
||||
</details>
|
||||
<?php else: ?>
|
||||
<p class="aa-table-copy">Keine LDAP-Gruppen gefunden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="aa-actions">
|
||||
<button class="window-app-button aa-secondary" type="button" data-aa-close-modal>Abbrechen</button>
|
||||
<button class="window-app-button aa-primary aa-inline-access-submit" type="submit">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</td>
|
||||
<td><code><?= $h((string) ($app['entry_route'] ?? '')) ?></code></td>
|
||||
<td>
|
||||
|
||||
Reference in New Issue
Block a user