From b0cfac8ba3bf4c2dee0f9c41c9a11e0cfb5be9f4 Mon Sep 17 00:00:00 2001 From: piotrsulkowski-elastic Date: Wed, 5 Nov 2025 02:04:04 +0000 Subject: [PATCH 1/3] in-response-to in saml successful response Return in-response-to field in all successful authentication attempts, as this information is useful to the client part of #128179 --- .../TransportSamlInvalidateSessionAction.java | 2 +- .../security/authc/saml/SamlAttributes.java | 27 +++++++++++++++++-- .../authc/saml/SamlAuthenticator.java | 6 ++--- .../xpack/security/authc/saml/SamlRealm.java | 8 ++++-- ...sportSamlInvalidateSessionActionTests.java | 2 +- .../saml/TransportSamlLogoutActionTests.java | 3 ++- .../authc/saml/SamlAttributesTests.java | 4 +++ .../security/authc/saml/SamlRealmTests.java | 14 ++++++++++ 8 files changed, 56 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionAction.java index f151cf2c5afe7..0a5cee063183a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionAction.java @@ -120,7 +120,7 @@ private static String buildLogoutResponseUrl(SamlRealm realm, SamlLogoutRequestH } private void findAndInvalidateTokens(SamlRealm realm, SamlLogoutRequestHandler.Result result, ActionListener listener) { - final Map tokenMetadata = realm.createTokenMetadata(result.getNameId(), result.getSession()); + final Map tokenMetadata = realm.createTokenMetadata(result.getNameId(), result.getSession(), null); if (Strings.isNullOrEmpty((String) tokenMetadata.get(SamlRealm.TOKEN_METADATA_NAMEID_VALUE))) { // If we don't have a valid name-id to match against, don't do anything LOGGER.debug("Logout request [{}] has no NameID value, so cannot invalidate any sessions", result); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributes.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributes.java index 3cecb6ae7c880..a8ccc5c91b488 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributes.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributes.java @@ -27,12 +27,20 @@ public class SamlAttributes implements Releasable { private final SamlNameId name; private final String session; + private final String inResponseTo; private final List attributes; private final List privateAttributes; - SamlAttributes(SamlNameId name, String session, List attributes, List privateAttributes) { + SamlAttributes( + SamlNameId name, + String session, + String inResponseTo, + List attributes, + List privateAttributes + ) { this.name = name; this.session = session; + this.inResponseTo = inResponseTo; this.attributes = attributes; this.privateAttributes = privateAttributes; } @@ -89,9 +97,24 @@ String session() { return session; } + String inResponseTo() { + return inResponseTo; + } + @Override public String toString() { - return getClass().getSimpleName() + "(" + name + ")[" + session + "]{" + attributes + "}{" + privateAttributes + "}"; + return getClass().getSimpleName() + + "(" + + name + + ")[" + + session + + "][" + + inResponseTo + + "]{" + + attributes + + "}{" + + privateAttributes + + "}"; } @Override diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticator.java index fa5b95bd2902f..2b68fb165e5bc 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticator.java @@ -117,7 +117,7 @@ private SamlAttributes authenticateResponse(Element element, Collection final Assertion assertion = details.v1(); final SamlNameId nameId = SamlNameId.forSubject(assertion.getSubject()); final String session = getSessionIndex(assertion); - final SamlAttributes samlAttributes = buildSamlAttributes(nameId, session, details.v2()); + final SamlAttributes samlAttributes = buildSamlAttributes(nameId, session, response.getInResponseTo(), details.v2()); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder(); sb.append("The SAML Assertion contained the following attributes: \n"); @@ -141,7 +141,7 @@ private SamlAttributes authenticateResponse(Element element, Collection return samlAttributes; } - private SamlAttributes buildSamlAttributes(SamlNameId nameId, String session, List attributes) { + private SamlAttributes buildSamlAttributes(SamlNameId nameId, String session, String inResponseTo, List attributes) { List samlAttributes = new ArrayList<>(); List samlPrivateAttributes = new ArrayList<>(); for (Attribute attribute : attributes) { @@ -151,7 +151,7 @@ private SamlAttributes buildSamlAttributes(SamlNameId nameId, String session, Li samlAttributes.add(new SamlAttributes.SamlAttribute(attribute)); } } - return new SamlAttributes(nameId, session, samlAttributes, samlPrivateAttributes); + return new SamlAttributes(nameId, session, inResponseTo, samlAttributes, samlPrivateAttributes); } private static String getSessionIndex(Assertion assertion) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java index 5cc2fa5fa2f66..c56ea6d795835 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java @@ -159,6 +159,7 @@ public final class SamlRealm extends Realm implements Releasable { public static final String TOKEN_METADATA_NAMEID_SP_QUALIFIER = "saml_nameid_sp_qual"; public static final String TOKEN_METADATA_NAMEID_SP_PROVIDED_ID = "saml_nameid_sp_id"; public static final String TOKEN_METADATA_SESSION = "saml_session"; + public static final String TOKEN_METADATA_IN_RESPONSE_TO = "saml_in_response_to"; public static final String TOKEN_METADATA_REALM = "saml_realm"; public static final String PRIVATE_ATTRIBUTES_METADATA = "saml_private_attributes"; @@ -588,7 +589,7 @@ private void buildUser(SamlAttributes attributes, ActionListener tokenMetadata = createTokenMetadata(attributes.name(), attributes.session()); + final Map tokenMetadata = createTokenMetadata(attributes.name(), attributes.session(), attributes.inResponseTo()); final Map> privateAttributesMetadata = attributes.privateAttributes() .stream() .collect(Collectors.toMap(SamlPrivateAttribute::name, SamlPrivateAttribute::values)); @@ -639,7 +640,7 @@ private void buildUser(SamlAttributes attributes, ActionListener createTokenMetadata(SamlNameId nameId, String session) { + public Map createTokenMetadata(SamlNameId nameId, String session, String inResponseTo) { final Map tokenMeta = new HashMap<>(); if (nameId != null) { tokenMeta.put(TOKEN_METADATA_NAMEID_VALUE, nameId.value); @@ -655,6 +656,9 @@ public Map createTokenMetadata(SamlNameId nameId, String session tokenMeta.put(TOKEN_METADATA_NAMEID_SP_PROVIDED_ID, null); } tokenMeta.put(TOKEN_METADATA_SESSION, session); + if (inResponseTo != null) { + tokenMeta.put(TOKEN_METADATA_IN_RESPONSE_TO, inResponseTo); + } tokenMeta.put(TOKEN_METADATA_REALM, name()); return tokenMeta; } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java index 82696a3ef5c00..3f4a02d62546b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java @@ -463,7 +463,7 @@ private TokenService.CreateTokenResult storeToken(byte[] userTokenBytes, byte[] .user(new User("bob")) .realmRef(new RealmRef("native", NativeRealmSettings.TYPE, "node01")) .build(false); - final Map metadata = samlRealm.createTokenMetadata(nameId, session); + final Map metadata = samlRealm.createTokenMetadata(nameId, session, null); final PlainActionFuture future = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(userTokenBytes, refreshTokenBytes, authentication, authentication, metadata, future); return future.actionGet(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java index a8e4c13c8f4cc..8ad2cb4302060 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java @@ -278,7 +278,8 @@ public void testLogoutInvalidatesToken() throws Exception { final Authentication.RealmRef realmRef = new Authentication.RealmRef(samlRealm.name(), SingleSpSamlRealmSettings.TYPE, "node01"); final Map tokenMetadata = samlRealm.createTokenMetadata( new SamlNameId(NameID.TRANSIENT, nameId, null, null, null), - session + session, + null ); final Authentication authentication = Authentication.newRealmAuthentication(user, realmRef); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributesTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributesTests.java index 45d845f676c50..00f12edbc0c70 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributesTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAttributesTests.java @@ -19,9 +19,11 @@ public void testToString() { final String nameFormat = randomFrom(NameID.TRANSIENT, NameID.PERSISTENT, NameID.EMAIL); final String nameId = randomIdentifier(); final String session = randomAlphaOfLength(16); + final String inResponseTo = randomAlphanumericOfLength(16); final SamlAttributes attributes = new SamlAttributes( new SamlNameId(nameFormat, nameId, null, null, null), session, + inResponseTo, List.of( new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.1", null, List.of("peter.ng")), new SamlAttributes.SamlAttribute("urn:oid:2.5.4.3", "name", List.of("Peter Ng")), @@ -46,6 +48,8 @@ public void testToString() { + ("NameId(" + nameFormat + ")=" + nameId) + ")[" + session + + "][" + + inResponseTo + "]{[" + "urn:oid:0.9.2342.19200300.100.1.1=[peter.ng](len=1)" + ", " diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java index e5f37a5e9de0f..a1840473d2fd8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java @@ -85,7 +85,9 @@ import static org.elasticsearch.core.Strings.format; import static org.elasticsearch.test.ActionListenerUtils.anyActionListener; import static org.elasticsearch.test.TestMatchers.throwableWithMessage; +import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.CONTEXT_TOKEN_DATA; import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.PRIVATE_ATTRIBUTES_METADATA; +import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.TOKEN_METADATA_IN_RESPONSE_TO; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -101,6 +103,7 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.typeCompatibleWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -580,6 +583,7 @@ private AuthenticationResult performAuthentication( final String nameIdValue = principalIsEmailAddress ? "clint.barton@shield.gov" : "clint.barton"; final String uidValue = principalIsEmailAddress ? "cbarton@shield.gov" : "cbarton"; final String realmType = SingleSpSamlRealmSettings.TYPE; + final String inResponseTo = "_request_id_12345"; final RealmConfig.RealmIdentifier realmIdentifier = new RealmConfig.RealmIdentifier("mock", "mock_lookup"); final MockLookupRealm lookupRealm = new MockLookupRealm( @@ -669,6 +673,7 @@ private AuthenticationResult performAuthentication( final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.PERSISTENT, nameIdValue, idp.getEntityID(), sp.getEntityId(), null), randomAlphaOfLength(16), + inResponseTo, List.of( new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.1", "uid", Collections.singletonList(uidValue)), new SamlAttributes.SamlAttribute("urn:oid:1.3.6.1.4.1.5923.1.5.1.1", "groups", groups), @@ -698,6 +703,11 @@ public void onResponse(AuthenticationResult result) { @SuppressWarnings("unchecked") var metadata = (Map>) result.getMetadata().get(PRIVATE_ATTRIBUTES_METADATA); secureAttributes.forEach((name, value) -> assertThat(metadata.get(name), equalTo(value))); + Object tokenContext = result.getMetadata().get(CONTEXT_TOKEN_DATA); + assertThat(tokenContext, notNullValue()); + assertThat(tokenContext.getClass(), typeCompatibleWith(Map.class)); + Object returnedInResponseTo = ((Map) tokenContext).get(TOKEN_METADATA_IN_RESPONSE_TO); + assertThat(returnedInResponseTo, equalTo(inResponseTo)); } super.onResponse(result); } @@ -778,6 +788,7 @@ private List performAttributeSelectionWithSplit(String delimiter, String final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(24), null, null, null), randomAlphaOfLength(16), + randomAlphaOfLength(16), List.of( new SamlAttributes.SamlAttribute( "departments", @@ -850,6 +861,7 @@ public void testAttributeSelectionWithSplitAndListThrowsSecurityException() { final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(24), null, null, null), randomAlphaOfLength(16), + randomAlphaOfLength(16), List.of( new SamlAttributes.SamlAttribute( "departments", @@ -883,6 +895,7 @@ public void testAttributeSelectionWithRegex() { final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(24), null, null, null), randomAlphaOfLength(16), + randomAlphaOfLength(16), List.of( new SamlAttributes.SamlAttribute( "urn:oid:0.9.2342.19200300.100.1.3", @@ -1053,6 +1066,7 @@ public void testNonMatchingPrincipalPatternThrowsSamlException() throws Exceptio final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(12), null, null, null), randomAlphaOfLength(16), + randomAlphaOfLength(16), List.of(new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", Collections.singletonList(mail))), List.of() ); From 6205f347cea6660e239753b25daba9856d8da63b Mon Sep 17 00:00:00 2001 From: Piotr Sulkowski Date: Mon, 10 Nov 2025 10:01:14 +0000 Subject: [PATCH 2/3] Update docs/changelog/137599.yaml --- docs/changelog/137599.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/137599.yaml diff --git a/docs/changelog/137599.yaml b/docs/changelog/137599.yaml new file mode 100644 index 0000000000000..7043007c62f5f --- /dev/null +++ b/docs/changelog/137599.yaml @@ -0,0 +1,5 @@ +pr: 137599 +summary: In-response-to in saml successful response +area: Authentication +type: enhancement +issues: [] From 869fbf3955f7a1b73d84943f001934540e9dd59f Mon Sep 17 00:00:00 2001 From: Piotr Sulkowski Date: Mon, 10 Nov 2025 10:03:36 +0000 Subject: [PATCH 3/3] Update docs/changelog/137599.yaml --- docs/changelog/137599.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog/137599.yaml b/docs/changelog/137599.yaml index 7043007c62f5f..6deba4a7a7bfe 100644 --- a/docs/changelog/137599.yaml +++ b/docs/changelog/137599.yaml @@ -2,4 +2,5 @@ pr: 137599 summary: In-response-to in saml successful response area: Authentication type: enhancement -issues: [] +issues: + - 128179