Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 13 additions & 20 deletions login.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ function _addAnchor($url, $type, $vars, $url_anchor = null)
}

if ($logout_reason) {
$postLogoutData = ['redirect' => null];
if ($is_auth) {
try {
$tokenService = $injector->getInstance(Token::class);
Expand All @@ -144,36 +145,28 @@ function _addAnchor($url, $type, $vars, $url_anchor = null)
require HORDE_BASE . '/index.php';
exit;
}
$is_auth = null;

$logger->notice(sprintf(
'User %s logged out of Horde (%s)%s',
$registry->getAuth(),
$_SERVER['REMOTE_ADDR'],
empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : ' (forwarded for [' . $_SERVER['HTTP_X_FORWARDED_FOR'] . '])'
$loginService = $injector->getInstance(\Horde\Horde\Service\LoginService::class);
$postLogoutData = $loginService->performLogout(new \Horde\Horde\ValueObject\LogoutRequest(
csrfToken: null, // already verified above
reason: $logout_reason,
remoteAddr: $_SERVER['REMOTE_ADDR'],
forwardedFor: $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
));
$is_auth = null;
} else {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to reset the notification handler in this path, too?

$registry->clearAuth();
$session->setup();
}

$registry->clearAuth();

/* Reset notification handler now, since it may still be using a status
* handler that is no longer valid. */
$notification->detach('status');
$notification->attach('status');

/* Redirect the user on logout if redirection is enabled and this is an
* an intended logout. */
if (($logout_reason == Horde_Auth::REASON_LOGOUT)
&& !empty($conf['auth']['redirect_on_logout'])) {
$logout_url = new Horde_Url($conf['auth']['redirect_on_logout'], true);
if (!empty($postLogoutData['redirect'])) {
$logout_url = new Horde_Url($postLogoutData['redirect'], true);
if (!isset($_COOKIE[session_name()])) {
$logout_url->add(session_name(), session_id());
}
_addAnchor($logout_url, 'url', $vars, $url_anchor)->redirect();
}

$session->setup();

/* Explicitly set language in un-authenticated session. */
$registry->setLanguage($GLOBALS['language']);

Expand Down
29 changes: 29 additions & 0 deletions src/Service/LoginService.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,24 @@ public function performLogout(LogoutRequest $request): array
);
}

// Run pre-logout handlers before the session is destroyed.
// A failing handler must never prevent the logout from completing.
$postLogoutRedirect = null;
if ($currentUser) {
foreach ($this->preLogoutHandlers as $handler) {
Comment thread
jcdelepine marked this conversation as resolved.
try {
$data = $handler->onBeforeLogout($currentUser, $request->reason);
if (!empty($data['redirect']) && $postLogoutRedirect === null) {
$postLogoutRedirect = $data['redirect'];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we have multiple preLogoutHandlers with different redirect attributes? Only the first one will be honored (which is probably what we want OR notice we have contradicting setups and just crash with a useful error message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the first non-null redirect is honoured — subsequent handlers
can still perform their cleanup (token revocation etc.) but their
redirect is ignored. This is intentional: in practice a deployment
will have at most one handler that produces a redirect (the OIDC SLO
handler). If contradicting redirects were a real concern, a more
explicit conflict-detection mechanism would be warranted, but I felt
that was premature for now.

}
} catch (Throwable $e) {
$this->logger->warning(
'PreLogoutHandler ' . $handler::class . ' failed: ' . $e->getMessage()
);
}
}
}

// Clear authentication
$this->registry->clearAuth();

Expand Down Expand Up @@ -453,6 +471,17 @@ public function performLogout(LogoutRequest $request): array
// Ignore - theme will use system default
}

// Handler redirect takes priority over redirect_on_logout config
if ($postLogoutRedirect === null
&& $request->reason === Horde_Auth::REASON_LOGOUT
&& !empty($this->conf['auth']['redirect_on_logout'])) {
$postLogoutRedirect = $this->conf['auth']['redirect_on_logout'];
}

if ($postLogoutRedirect !== null) {
return ['redirect' => $postLogoutRedirect, 'reason' => $request->reason];
}

// Default redirect to login page
$webroot = $this->registry->get('webroot', 'horde');
$redirect = $webroot . '/auth/login?logout_reason=logout';
Expand Down