Skip to content

fix(sso): let LastaFlute own the SAML SLO redirect - #3219

Merged
marevol merged 1 commit into
masterfrom
saml-slo-response
Aug 10, 2026
Merged

fix(sso): let LastaFlute own the SAML SLO redirect#3219
marevol merged 1 commit into
masterfrom
saml-slo-response

Conversation

@marevol

@marevol marevol commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3217 (which is stacked on #3214) — this PR targets saml-sp-url-settings. Merge #3214, then #3217, then this one.

1. The IdP-initiated logout response was written twice

getLogoutResponse() called the no-arg auth.processSLO(), which delegates to processSLO(false, null, false). When the incoming message is a SAMLRequest (IdP-initiated logout), java-saml builds the LogoutResponse and, with stay=false, sends it itself:

// ServletUtils#sendRedirect
if (!stay) {
    response.sendRedirect(targetUrl);   // commits the servlet response
}

Fess then threw SsoMessageException, and SsoAction#logout caught it, called saveInfo(...) and returned redirect(LoginAction.class) — a second redirect on an already committed response.

The call is now processSLO(false, null, true), so the URL comes back to Fess and the method returns HtmlResponse.fromRedirectPathAsIs(redirectUrl). LastaFlute stays in charge of the response. A SAMLResponse from the IdP produces no URL and still reports success through SsoMessageException, exactly as before.

2. NPE when SLO is not configured

Auth#getSLOResponseUrl() is

return settings.getIdpSingleLogoutServiceResponseUrl().toString();

and getIdpSingleLogoutServiceResponseUrl() returns null when neither the response URL nor the SLO URL is set. A LogoutRequest arriving in that state raised a NullPointerException, which the catch-all wrapped into a user-facing message built from e.getMessage() — literally the string null.

getLogoutResponse() now checks the URL up front and reports it, matching the guard logout(FessUserBean) already had.

3. Smaller items

  • The trailing return null; was unreachable, because the mapped lambda always threw. The method now returns the mapped optional's value.
  • SP metadata is served as application/samlmetadata+xml with a metadata.xml file name rather than application/xhtml+xml.

Test

test_getLogoutResponse_withoutIdpSingleLogoutServiceUrl covers the new guard. It is falsifiable: with the guard removed the request instead fails inside the Auth constructor with Invalid settings: idp_entityId_not_found, ..., and the test catches that difference.

Tests run: 130, Failures: 0, Errors: 0, Skipped: 0   (org.codelibs.fess.sso.**)

Not covered here

SP-initiated SLO still calls processSLO without a request ID, so the LogoutResponse is not bound to the LogoutRequest Fess sent — the equivalent of what #3214 fixes for the login flow. LogoutAction invalidates the session before the response comes back, so binding it needs a different place to keep the ID. Left for a follow-up.

@marevol
marevol force-pushed the saml-slo-response branch from fa7f830 to a8c23ea Compare August 10, 2026 00:51
@marevol
marevol force-pushed the saml-slo-response branch from a8c23ea to b581bf0 Compare August 10, 2026 01:34
@marevol
marevol force-pushed the saml-slo-response branch from b581bf0 to ed49baf Compare August 10, 2026 01:57
Base automatically changed from saml-sp-url-settings to master August 10, 2026 02:12
getLogoutResponse() called the no-arg auth.processSLO(), which delegates to
processSLO(false, null, false). With stay=false, java-saml answers an
IdP-initiated LogoutRequest by calling response.sendRedirect() itself, which
commits the servlet response. Fess then threw SsoMessageException, so
SsoAction.logout() went on to saveInfo() and return a second redirect on the
already committed response.

processSLO(false, null, true) is now used so the URL is returned instead of
being sent, and the method returns HtmlResponse.fromRedirectPathAsIs() for it.
A LogoutResponse from the IdP still produces no URL and keeps reporting
success through SsoMessageException.

Also:
- Guard against a missing IdP single logout service URL. Auth.getSLOResponseUrl()
  dereferences it without a null check, so a LogoutRequest arriving while SLO
  was unconfigured raised a NullPointerException whose null message was then
  shown to the user. logout(FessUserBean) already had this guard.
- The unreachable "return null" at the end is gone; the method now returns the
  value of the mapped optional.
- SP metadata is served as application/samlmetadata+xml with a .xml file name
  instead of application/xhtml+xml.
@marevol
marevol force-pushed the saml-slo-response branch from ed49baf to 3d7274d Compare August 10, 2026 02:12
@marevol marevol self-assigned this Aug 10, 2026
@marevol marevol added this to the 15.8.0 milestone Aug 10, 2026
@marevol
marevol merged commit 9f86140 into master Aug 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant