Skip to content

fix: do not warn about a rejection the caller is already given - #12

Merged
marevol merged 1 commit into
mainfrom
fix/do-not-log-what-the-caller-receives
Aug 13, 2026
Merged

fix: do not warn about a rejection the caller is already given#12
marevol merged 1 commit into
mainfrom
fix/do-not-log-what-the-caller-receives

Conversation

@marevol

@marevol marevol commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

Three places report the same rejection twice — once by returning it to the caller, and once by
logging it at warn, at a level the caller does not control.

Site Handed to the caller Also logged
SamlResponse#isValid validationException + false warn(message)
Auth#processResponse getErrors(), getLastErrorReason(), getLastValidationException() warn("processResponse error. …")
Util#loadXML nothing — returns null, drops the exception warn(message, e) — full stack trace

Two consequences show up in practice.

A caller with several candidate request ids logs errors on a login that succeeds. The
InResponseTo comparison is the one failure worth retrying with the next candidate, so a service
provider whose user has two browser tabs mid-login rules candidates out as a matter of course. At
warn, every ruled-out candidate produced

The InResponseTo of the Response: …, does not match the ID of the AuthNRequest sent by the SP: …
processResponse error. invalid_response

on the way to a successful authentication.

An anonymous client can write a full stack trace into the log on every request. The assertion
consumer service is unauthenticated by nature, so anything posted there reaches Util#loadXML. A
SAMLResponse that is not parsable produced a 94-line trace per request.

Change

SamlResponse#isValid and Auth#processResponse move their lines to debug. Everything they
said is still reachable through the public API, so nothing is lost — the caller decides what an
operator sees.

Util#loadXML is the opposite case and keeps its message at warn, because it answers with
null and drops the exception, so nothing else would record it. Only the stack trace moves to
debug.

Verification

Measured against Fess 15.8 with Keycloak 26.4, before and after:

Scenario Before After
Unparsable SAMLResponse posted to the ACS 94 stack-trace lines 0, one message-only line
Two-tab login, older tab answered first succeeds, 2 warnings succeeds, silent

The consuming service provider still reports both cases itself, so no diagnosis was lost — only
the duplicate.

core     415/415
toolkit   92/92

Note

The repository has no log-capturing test infrastructure, so the level change is not pinned by a
unit test; adding an appender harness for it seemed disproportionate. Say the word if you would
rather have one — the behaviour is covered end to end in the measurement above.

Three places report the same rejection twice: once by returning it, and once
by logging it at warn, at a level the caller does not control.

SamlResponse#isValid stores the failure in validationException and returns
false, then logs the message at warn. Auth#processResponse adds
"invalid_response" or "response_not_success" to getErrors(), keeps the detail
in getLastErrorReason() and getLastValidationException(), then logs at warn as
well. Both move to debug: what reaches an operator is for the caller to
decide, and everything these lines said is still available through the public
API.

This also matters for a caller that has more than one candidate request id to
try -- the InResponseTo comparison is the one failure worth retrying with the
next candidate, so a service provider with two browser tabs mid-login rules
out candidates as a matter of course. At warn, every ruled-out candidate
logged an error, including on the login that then succeeded.

Util#loadXML is the opposite case: it answers with null and drops the
exception, so nothing else records it and the message stays at warn. Only the
stack trace moves to debug. The input there is whatever was posted to an
assertion consumer service, which is anonymous, so an unauthenticated client
could otherwise write a full trace into the log on every request.

Measured against Fess 15.8 with Keycloak: an unparsable SAMLResponse went from
94 stack-trace lines to none, and a two-tab login that answers the older tab
first now completes without logging anything. core 415/415 and toolkit 92/92
still pass.
@marevol
marevol merged commit 5803302 into main Aug 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant