Skip to content

fix(sso): derive SAML SP URLs per request and ignore blank overrides - #3217

Merged
marevol merged 1 commit into
masterfrom
saml-sp-url-settings
Aug 10, 2026
Merged

fix(sso): derive SAML SP URLs per request and ignore blank overrides#3217
marevol merged 1 commit into
masterfrom
saml-sp-url-settings

Conversation

@marevol

@marevol marevol commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3214 — this PR targets saml-response-validation. Merge #3214 first.

Summary

Two ways the SP endpoint settings could end up wrong, plus cleanup of dead configuration and a test file that could not detect any of it.

1. saml.sp.base.url required a restart

buildDefaultUrl() was called only from @PostConstruct init(), so sp.entityid, sp.assertion_consumer_service.url and sp.single_logout_service.url were computed once at startup and cached in defaultSettings.

But saml.sp.base.url is writable at runtime: AdminGeneralAction writes it via setSystemProperty when the "SP Base URL" field on the admin General page is saved. getSettings() re-reads every other saml.* property on every call, so an admin who follows the documented "Option 1: set the base URL" path saw http://localhost:8080/... in the SP metadata and in AuthnRequests until Fess was restarted — and with strict=true the resulting Destination/Audience mismatches fail the login.

The three URLs are now built inside getSettings().

2. A blank saml.* property wiped out the default

The overlay loop copied values into the settings map without checking for blanks:

params.put("onelogin.saml2." + key.substring(SAML_PREFIX.length()), e.getValue());

SettingsBuilder#isString requires isNotBlank, so a blank value is not "no override" — it replaces a good default with something the builder then treats as absent. A stray saml.sp.entityid= in system.properties silently yields:

checkSPSettings() = [sp_entityId_not_found, sp_acs_not_found]

which surfaces as a SettingsException from the Auth constructor. Blank values are now skipped.

(The admin UI does not produce these: LastaFlute maps empty text parameters to null and setSystemProperty(key, null) removes the key. This is a guard against hand-edited or migrated system.properties.)

3. Dead configuration

  • onelogin.saml2.security.onelogin.saml2.security.requested_authncontextcomparison had a duplicated prefix, so SettingsBuilder never read it. Verified against the library: setting that key to minimum leaves getRequestedAuthnContextComparison() at exact. The value it intended to set is also the library default, so fixing the key is behaviour-neutral.
  • Five defaults were the empty string — sp.x509cert, sp.privatekey, security.sign_metadata, idp.single_logout_service.response.url, organization.lang. isString rejects blanks, so none of them did anything. Removed.

4. The tests could not fail

SamlAuthenticatorTest had a private createDefaultSettings() that hand-copied the production map, and six tests asserted against that copy without ever touching SamlAuthenticator. The copy had already drifted — it still declared

"onelogin.saml2.security.signature_algorithm" -> "http://www.w3.org/2000/09/xmldsig#rsa-sha1"

long after the production default moved to rsa-sha256, and stayed green. The private setDefaultSettings() helper was never called at all.

The map is now extracted into a protected createDefaultSettings() on the authenticator, and the tests call the real methods. New coverage:

  • test_getSettings_spUrlsFollowBaseUrlChangedAfterStartup — fix 1
  • test_getSettings_blankPropertyKeepsDefault — fix 2
  • test_createDefaultSettings_hasNoBlankValues — keeps fix 3 from regressing
  • test_createDefaultSettings_security — pins the signature algorithm and the corrected comparison key

Both regression tests were confirmed to fail when the corresponding fix is reverted.

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

@marevol
marevol force-pushed the saml-sp-url-settings branch from 5ba8c7e to 9b211f4 Compare August 10, 2026 00:51
@marevol
marevol force-pushed the saml-sp-url-settings branch from 9b211f4 to 0006d9d Compare August 10, 2026 01:34
Base automatically changed from saml-response-validation to master August 10, 2026 01:57
The SP endpoint defaults were frozen at startup and a blank property could
wipe them out.

- buildDefaultUrl() was called only from init(), so sp.entityid,
  sp.assertion_consumer_service.url and sp.single_logout_service.url were
  fixed at startup. saml.sp.base.url, however, is writable at runtime from
  the admin UI, so changing "SP Base URL" there left http://localhost:8080
  in the metadata and in AuthnRequests until Fess was restarted. The three
  URLs are now built in getSettings(), which already re-reads every other
  saml.* property per call.
- The saml.* overlay in getSettings() copied blank values over the defaults.
  SettingsBuilder treats a blank value as absent, so a stray
  "saml.sp.entityid=" in system.properties silently produced
  sp_entityId_not_found / sp_acs_not_found. Blank values are now skipped.
- The requested_authncontextcomparison key carried a duplicated
  "onelogin.saml2.security." prefix and was therefore ignored by
  SettingsBuilder. The value it set ("exact") is also the library default,
  so correcting the key does not change behaviour.
- Dropped five defaults whose value was the empty string (sp.x509cert,
  sp.privatekey, security.sign_metadata, idp.single_logout_service.response.url
  and organization.lang). SettingsBuilder requires a non-blank value, so none
  of them had any effect.
- Extracted the default map into createDefaultSettings() so tests can assert
  against the production values.

SamlAuthenticatorTest previously hand-copied the default map and asserted
against its own copy, which had already drifted (it still declared rsa-sha1
after the production default moved to rsa-sha256) without failing. The tests
now call the real methods and cover the two fixes above; both new regression
tests were confirmed to fail against the previous behaviour.
@marevol
marevol force-pushed the saml-sp-url-settings branch from 0006d9d to 87f1bf0 Compare August 10, 2026 01:57
@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 33afb3b 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