Skip to content

Commit b9e6633

Browse files
fix and unmute SamlRedirectTests (elastic#139459) (elastic#139727)
SamlRedirectTests are comparing request values, which are compressed with zlib deflate. This function is not guaranteed to always be deterministic and can change when zlib version is updated. That is exactly what happened. For that reason, instead of relying on hardcoded result of compression, the test is not comparing uncompressed values. Resolves elastic#139389 Resolves elastic#139390 Resolves elastic#139391 Resolves elastic#139392 (cherry picked from commit 9f6cd29) # Conflicts: # muted-tests.yml Co-authored-by: Piotr Sulkowski <[email protected]>
1 parent 6a07529 commit b9e6633

File tree

2 files changed

+99
-51
lines changed

2 files changed

+99
-51
lines changed

muted-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,4 +415,4 @@ tests:
415415
# issue: "https://github.com/elastic/elasticsearch/..."
416416
# - class: "org.elasticsearch.xpack.esql.**"
417417
# method: "test {union_types.MultiIndexIpStringStatsInline *}"
418-
# issue: "https://github.com/elastic/elasticsearch/..."
418+
# issue: "https://github.com/elastic/elasticsearch/..."

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRedirectTests.java

Lines changed: 98 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
*/
77
package org.elasticsearch.xpack.security.authc.saml;
88

9+
import org.elasticsearch.core.Strings;
10+
import org.hamcrest.Matcher;
911
import org.opensaml.saml.common.xml.SAMLConstants;
1012
import org.opensaml.saml.saml2.core.Issuer;
1113
import org.opensaml.saml.saml2.core.LogoutRequest;
1214
import org.opensaml.saml.saml2.core.NameID;
1315
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
1416
import org.opensaml.security.x509.X509Credential;
1517

18+
import java.io.ByteArrayInputStream;
19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
1621
import java.net.URLDecoder;
1722
import java.nio.charset.StandardCharsets;
1823
import java.security.InvalidKeyException;
@@ -26,11 +31,16 @@
2631
import java.time.ZonedDateTime;
2732
import java.util.Base64;
2833
import java.util.Collections;
34+
import java.util.HashMap;
35+
import java.util.Map;
36+
import java.util.function.BiFunction;
37+
import java.util.zip.Inflater;
38+
import java.util.zip.InflaterInputStream;
2939

3040
import static java.util.Collections.emptySet;
3141
import static java.util.Collections.singleton;
3242
import static org.hamcrest.Matchers.equalTo;
33-
import static org.hamcrest.Matchers.startsWith;
43+
import static org.hamcrest.Matchers.notNullValue;
3444

3545
public class SamlRedirectTests extends SamlTestCase {
3646

@@ -42,19 +52,22 @@ public class SamlRedirectTests extends SamlTestCase {
4252

4353
private static final SigningConfiguration NO_SIGNING = new SigningConfiguration(emptySet(), null);
4454

55+
/**
56+
* XML template of LogoutRequest.
57+
*/
58+
private static final String EXPECTED_LOGOUT_REQUEST_TEMPLATE = """
59+
<?xml version="1.0" encoding="UTF-8"?>\
60+
<saml2p:LogoutRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" \
61+
Destination="%s" \
62+
ID="_id123456789" IssueInstant="2018-01-14T22:47:00.000Z" Version="2.0">\
63+
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.test/</saml2:Issuer>\
64+
<saml2:NameID xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">name-123456-7890</saml2:NameID>\
65+
</saml2p:LogoutRequest>""";
66+
4567
public void testRedirectUrlWithoutRelayStateOrSigning() {
4668
final SamlRedirect redirect = new SamlRedirect(buildLogoutRequest(LOGOUT_URL), NO_SIGNING);
4769
final String url = redirect.getRedirectUrl();
48-
assertThat(
49-
url,
50-
equalTo(
51-
LOGOUT_URL
52-
+ "?SAMLRequest=nZFBa4QwFIT%2FSnh3Naa2ax%2FqsiAFYdtDu91DLyVo2AY0cX2x9Oc36gpLCz30mAwz3"
53-
+ "wwv2351LftUA2lrcohDDkyZ2jbanHJ4PTwEKWyLjGTXih739mRH96zOoyLHvNMQLlIO42DQStKERnaK0NX4snvcowg59oN1trYtsNIbtZFupn04"
54-
+ "1xNGkW760HkhmrKidoYAq8oc3nUTi5vk9m6T3vsfolFVhpw0LgfB4zTgcRAnByEw2SDnIef8DdhxnePZcCmPs3m4Lv13Z0mkhqknFL96ZtF15kp"
55-
+ "48hlV%2BS%2FCJAbL0sBP5StgiSwuzx8HKL4B"
56-
)
57-
);
70+
assertRedirectUrl(url, Map.of("SAMLRequest", equalTo(buildExpectedLogoutRequestString(LOGOUT_URL))));
5871
}
5972

6073
public void testRedirectUrlWithRelayStateAndSigning() throws Exception {
@@ -64,51 +77,93 @@ public void testRedirectUrlWithRelayStateAndSigning() throws Exception {
6477
);
6578
final SamlRedirect redirect = new SamlRedirect(buildLogoutRequest(LOGOUT_URL), signing);
6679
final String url = redirect.getRedirectUrl("hello");
67-
assertThat(
80+
assertRedirectUrl(
6881
url,
69-
startsWith(
70-
LOGOUT_URL
71-
+ "?SAMLRequest=nZFBa4QwFIT%2FSnh3Naa2ax%2FqsiAFYdtDu91DLyVo2AY0cX2x9Oc36gpLC"
72-
+ "z30mAwz3wwv2351LftUA2lrcohDDkyZ2jbanHJ4PTwEKWyLjGTXih739mRH96zOoyLHvNMQLlIO42DQStKERnaK0NX4snvcowg59oN1trY"
73-
+ "tsNIbtZFupn041xNGkW760HkhmrKidoYAq8oc3nUTi5vk9m6T3vsfolFVhpw0LgfB4zTgcRAnByEw2SDnIef8DdhxnePZcCmPs3m4Lv13Z"
74-
+ "0mkhqknFL96ZtF15kp48hlV%2BS%2FCJAbL0sBP5StgiSwuzx8HKL4B"
75-
+ "&RelayState=hello"
76-
+ "&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256"
77-
+ "&Signature="
82+
Map.of(
83+
"SAMLRequest",
84+
equalTo(buildExpectedLogoutRequestString(LOGOUT_URL)),
85+
"RelayState",
86+
equalTo("hello"),
87+
"SigAlg",
88+
equalTo("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
89+
"Signature",
90+
notNullValue(String.class)
7891
)
7992
);
8093
}
8194

8295
public void testRedirectUrlWithExistingParameters() {
8396
final SamlRedirect redirect = new SamlRedirect(buildLogoutRequest(LOGOUT_URL + "?a=xyz"), NO_SIGNING);
8497
final String url = redirect.getRedirectUrl("foo");
85-
assertThat(
98+
assertRedirectUrl(
8699
url,
87-
equalTo(
88-
LOGOUT_URL
89-
+ "?a=xyz"
90-
+ "&SAMLRequest=nZFBS8QwFIT%2FSnn3tmmsbn00LUIRCqsHXT14kdCGNdAmtS%2BV1V9v2u7CouDBYzLMzDe8vDz0XfChRtLWCE"
91-
+ "giBoEyjW212Qt42t2GGZRFTrLv%2BIBbu7eTe1DvkyIXeKchXCUB02jQStKERvaK0DX4eHO3RR4xHEbrbGM7CCpv1Ea6pe3NuYE"
92-
+ "wjnU7RM4L8ZwVd0tJKcXh8wuCuhLwqtuEX6SXV5vs2v8QTao25KRxAjhLspAlYZLuOMd0g4xFjLEXCJ5PozwBHCfgYh7P0f8ml0"
93-
+ "RqnGmh%2BEWbx%2BeZp4Z7n1FX%2F2qYxXBdGvqp7FSwRhbH548zFN8%3D"
94-
+ "&RelayState=foo"
100+
Map.of(
101+
"SAMLRequest",
102+
equalTo(buildExpectedLogoutRequestString(LOGOUT_URL + "?a=xyz")),
103+
"RelayState",
104+
equalTo("foo"),
105+
"a",
106+
equalTo("xyz")
95107
)
96108
);
97109
}
98110

99111
public void testRedirectUrlWithTrailingQuestionMark() {
100112
final SamlRedirect redirect = new SamlRedirect(buildLogoutRequest(LOGOUT_URL + "?"), NO_SIGNING);
101113
final String url = redirect.getRedirectUrl();
102-
assertThat(
103-
url,
104-
equalTo(
105-
LOGOUT_URL
106-
+ "?SAMLRequest=nZFPS8QwFMS%2FSnj3tmmsbn30D0IRCqsHXffgRUIb1kCb1L5U%2FPim7R"
107-
+ "YWBQ8ek2HmN8PLyq%2B%2BY59qJG1NDnHIgSnT2FabUw4vh%2FsghbLISPadGHBvT3ZyT%2BpjUuSYdxrCVcphGg1aSZrQyF4Rug"
108-
+ "af7x72KEKOw2idbWwHrPJGbaRbaO%2FODYRRpNshdF6I5qyoWyAlsLrK4U23sbhKrm926a3%2FIZpUbchJ43IQPE4DHgdxchACkx"
109-
+ "1yHnLOX4Edtz0eDuf2uJjHy9Z%2Fl5ZEapyLQvGraBZdZm6ER59RV%2F8izGKwLg38VL4B1sji%2FPxxgeIb"
110-
)
111-
);
114+
assertRedirectUrl(url, Map.of("SAMLRequest", equalTo(buildExpectedLogoutRequestString(LOGOUT_URL + "?"))));
115+
}
116+
117+
private static String buildExpectedLogoutRequestString(String destination) {
118+
return Strings.format(EXPECTED_LOGOUT_REQUEST_TEMPLATE, destination);
119+
}
120+
121+
private static Map<String, String> parseAndDecodeUrlParameters(String url) {
122+
return parseAndDecodeUrlParameters(url, (key, value) -> value);
123+
}
124+
125+
private static Map<String, String> parseAndDecodeUrlParameters(String url, BiFunction<String, String, String> valueDecoder) {
126+
Map<String, String> params = new HashMap<>();
127+
String[] parts = url.split("\\?", 2);
128+
if (parts.length < 2 || parts[1].isEmpty()) {
129+
return params;
130+
}
131+
for (String param : parts[1].split("&")) {
132+
String[] keyValue = param.split("=", 2);
133+
if (keyValue.length == 2) {
134+
String key = keyValue[0];
135+
String value = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8);
136+
params.put(key, valueDecoder.apply(key, value));
137+
}
138+
}
139+
return params;
140+
}
141+
142+
private void assertRedirectUrl(String actualRequestUrl, Map<String, Matcher<String>> expectedParams) {
143+
String[] parts = actualRequestUrl.split("\\?", 2);
144+
assertThat(parts[0], equalTo(LOGOUT_URL));
145+
146+
Map<String, String> actualParams = parseAndDecodeUrlParameters(actualRequestUrl, (key, value) -> {
147+
if (key.equals("SAMLRequest")) {
148+
return decompressAndBase64Decode(value);
149+
}
150+
return value;
151+
});
152+
assertThat("URL parameter keys", actualParams.keySet(), equalTo(expectedParams.keySet()));
153+
154+
for (Map.Entry<String, Matcher<String>> expected : expectedParams.entrySet()) {
155+
assertThat("Parameter " + expected.getKey(), actualParams.get(expected.getKey()), expected.getValue());
156+
}
157+
}
158+
159+
private static String decompressAndBase64Decode(String compressedBase64) {
160+
byte[] compressed = Base64.getDecoder().decode(compressedBase64);
161+
Inflater inflater = new Inflater(true);
162+
try (InflaterInputStream in = new InflaterInputStream(new ByteArrayInputStream(compressed), inflater)) {
163+
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
164+
} catch (IOException e) {
165+
throw new UncheckedIOException("Failed to decompress input: " + compressedBase64, e);
166+
}
112167
}
113168

114169
public void testLogoutRequestSigning() throws Exception {
@@ -156,18 +211,11 @@ public void testAuthnRequestSigning() throws Exception {
156211
assertThat(validateSignature(queryParam.substring(0, queryParam.length() - 5), signature, credential), equalTo(false));
157212
}
158213

159-
private String parseAndUrlDecodeParameter(String parameter) {
160-
final String value = parameter.split("=", 2)[1];
161-
return URLDecoder.decode(value, StandardCharsets.UTF_8);
162-
}
163-
164214
private String validateUrlAndGetSignature(String url) {
165-
final String params[] = url.split("\\?")[1].split("&");
166-
assert (params.length == 3);
167-
String sigAlg = parseAndUrlDecodeParameter(params[1]);
215+
Map<String, String> params = parseAndDecodeUrlParameters(url);
168216
// We currently only support signing with SHA256withRSA, this test should be updated if we add support for more
169-
assertThat(sigAlg, equalTo("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"));
170-
return parseAndUrlDecodeParameter(params[2]);
217+
assertThat(params.get("SigAlg"), equalTo("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"));
218+
return params.get("Signature");
171219
}
172220

173221
private boolean validateSignature(String queryParam, String signature, X509Credential credential) {

0 commit comments

Comments
 (0)