fix(sso): correct SPNEGO logging, config fallback and dead settings - #3216
Open
marevol wants to merge 1 commit into
Open
fix(sso): correct SPNEGO logging, config fallback and dead settings#3216marevol wants to merge 1 commit into
marevol wants to merge 1 commit into
Conversation
Fixes several defects found while auditing the SPNEGO authenticator against the underlying spnego library. - Mask the Authorization header down to its scheme. The previous mask kept the first 10 characters, which for "Basic dXNl..." retains four base64 characters and therefore the first three plain bytes of "user:password". SsoAction logs this message at WARN. - Treat a blank spnego.* system property as unset. The admin screen writes every key on save, so clearing an input stores an empty string; that empty string reached the library and turned a simple misconfiguration into an opaque "Failed to initialize SPNEGO." with the specific cause lost. - Stop advertising spnego.exclude.dirs. Only SpnegoHttpFilter consumes it, and Fess calls SpnegoAuthenticator#authenticate directly instead of installing that filter, so the value never excluded anything. - Return "7" (SEVERE) instead of "0" when no log level is enabled. The library's setLogLevel switch has no case for 0, so it fell through to INFO and the branch meant to silence the library actually raised its level. - Log the 401 challenge at debug level. The library has already written and flushed the 401 with its WWW-Authenticate header, so LastaFlute reported "Cannot send error ... because of already committed" at INFO on every single SPNEGO handshake. - Warn at initialization when spnego.allow.localhost or spnego.allow.unsecure.basic is enabled. The hardened defaults only apply when the key is absent, so an instance that stored the old permissive values keeps using them silently. - Make the authenticator field volatile and synchronize destroy(), which read and cleared it outside the monitor held by getAuthenticator(). - Name SpnegoConfig, not SpnegoFilterConfig, in the two UnsupportedOperationException messages thrown by SpnegoConfig itself. Replaces two tautological tests with coverage for the header mask, the blank property fallback and the exclude.dirs mapping. Tests run: 17, Failures: 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Defects found while auditing
org.codelibs.fess.sso.spnegoagainst the underlyingorg.codelibs:spnegolibrary. All of them are self-contained fixes inSpnegoAuthenticator; no behavior that a working SPNEGO deployment depends on is changed.Changes
Credential prefix leaks through the Authorization-header mask
The mask kept the first 10 characters of the header.
"Negotiate "is exactly 10 characters, so a Negotiate token was masked correctly — but"Basic dXNl"retains four base64 characters, which decode to the first three plain bytes ofuser:password, i.e. the first three characters of the user name.SsoAction#indexlogs this message at WARN.Now masked to the scheme token only:
Basic ***,Negotiate ***.A blank spnego.* property is no longer passed through as ""
Properties#getProperty(key, default)returns""for a key that is present but empty, not the default.AdminGeneralAction#doUpdatewrites everyspnego.*key on save, so clearing an input field in System → General persists an empty string. That empty string then reached the library, and for the file-path keys it surfaced asEmptyArgumentExceptionswallowed into the generic"Failed to initialize SPNEGO."— losing the specific "configuration file not found" message.SpnegoConfig#getPropertynow treats blank as unset.spnego.exclude.dirs is no longer advertised
excludeDirsis consumed only bySpnegoHttpFilter. Fess does not install that filter — it callsSpnegoAuthenticator#authenticatedirectly fromgetLoginCredential— so an operator listing/api/got no exclusion and no warning. The constant and the mapping are removed; the docs are updated separately in codelibs/fess-docs.spnego.logger.level "0" raised the level instead of silencing it
The library's
setLogLevelswitch has cases for 1/2/3/4/6/7 anddefault: Level.INFO— no case for0.getInitParameterreturned"0"when none of the Log4j levels were enabled, so the branch intended to silence the library set it to INFO. It now returns"7"(SEVERE), the quietest level the switch actually understands.The 401 challenge no longer logs on the normal path
The library's
SpnegoHttpServletResponse#setStatus(401, true)sets content-length 0 and flushes, so the response is already committed whenRequestClientErrorExceptionis thrown. LastaFlute'shandleClientErrorseesbeforeHandlingCommitted == trueand logs*Cannot send error as '401 ...' because of already committedat INFO — on every SPNEGO handshake, i.e. on the normal login path.Functionally harmless (LastaFlute guards
isCommitted()), so this keeps the existing control flow and just marks the exception.asLogging(DelicateErrorLoggingLevel.DEBUG). ReturningActionResponse.undefined()instead would be cleaner still, but that changes the response path and is left as a follow-up.Warn when the permissive settings are actually in effect
spnego.allow.localhostandspnego.allow.unsecure.basicwere hardened tofalse, but a coded default only applies when the key is absent.FessProp#setSystemPropertyremoves a key only onnull, and the admin screen always writes a concrete"true"/"false". An instance where an admin saved System → General before the hardening therefore hasspnego.allow.localhost=truepersisted insystem.properties, and the new default never fires.warnInsecureSettingsnow logs a WARN at initialization for both, so the condition is visible in the log instead of silent.Lifecycle and message fixes
authenticatorisvolatileanddestroy()issynchronized.getAuthenticator()holds the monitor, but@PreDestroy destroy()read and cleared the same field without it.UnsupportedOperationExceptionmessages said "not supported in SpnegoFilterConfig"; the class that throws them isSpnegoConfig.Tests
SpnegoAuthenticatorTest: Tests run: 17, Failures: 0, Errors: 0.Two tautological tests are replaced (
test_constantsExistandtest_innerClassNamingasserted thatSpnegoAuthenticator.class.getSimpleName()equals"SpnegoAuthenticator"). New coverage: the header mask across null / Negotiate / Basic / no-separator inputs, the blank-property fallback, theexclude.dirsmapping, and the exception messages.Not included
krb5.confencryption types,spnego.allowed.realms) — codelibs/fess-docs.spnego.allowed.realmsfield — separate PR.