diff --git a/test/framework/src/main/java/org/elasticsearch/common/util/NamedFormatter.java b/test/framework/src/main/java/org/elasticsearch/common/util/NamedFormatter.java
new file mode 100644
index 0000000000000..7fba3e4241283
--- /dev/null
+++ b/test/framework/src/main/java/org/elasticsearch/common/util/NamedFormatter.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.common.util;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A formatter that allows named placeholders e.g. "%(param)" to be replaced.
+ */
+public class NamedFormatter {
+ private static final Pattern PARAM_REGEX = Pattern
+ .compile(
+ // Match either any backlash-escaped characters, or a "%(param)" pattern.
+ // COMMENTS is specified to allow whitespace in this pattern, for clarity
+ "\\\\(.) | (% \\( ([^)]+) \\) )",
+ Pattern.COMMENTS
+ );
+
+ private NamedFormatter() {}
+
+ /**
+ * Replaces named parameters of the form %(param)
in format strings. For example:
+ *
+ *
+ * NamedFormatter.format("Hello, %(name)!", Map.of("name", "world"))
→ "Hello, world!"
+ * NamedFormatter.format("Hello, \%(name)!", Map.of("name", "world"))
→ "Hello, %(world)!"
+ * NamedFormatter.format("Hello, %(oops)!", Map.of("name", "world"))
→ {@link IllegalArgumentException}
+ *
+ *
+ * @param fmt The format string. Any %(param)
is replaced by its corresponding value in the values
map.
+ * Parameter patterns can be escaped by prefixing with a backslash.
+ * @param values a map of parameter names to values.
+ * @return The formatted string.
+ * @throws IllegalArgumentException if a parameter is found in the format string with no corresponding value
+ */
+ public static String format(String fmt, Map values) {
+ final Matcher matcher = PARAM_REGEX.matcher(fmt);
+
+ boolean result = matcher.find();
+
+ if (result) {
+ final StringBuffer sb = new StringBuffer();
+ do {
+ String replacement;
+
+ // Escaped characters are unchanged
+ if (matcher.group(1) != null) {
+ replacement = matcher.group(1);
+ } else {
+ final String paramName = matcher.group(3);
+ if (values.containsKey(paramName) == true) {
+ replacement = values.get(paramName).toString();
+ } else {
+ throw new IllegalArgumentException("No parameter value for %(" + paramName + ")");
+ }
+ }
+
+ matcher.appendReplacement(sb, replacement);
+ result = matcher.find();
+ } while (result);
+
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ return fmt;
+ }
+}
diff --git a/test/framework/src/test/java/org/elasticsearch/common/util/NamedFormatterTests.java b/test/framework/src/test/java/org/elasticsearch/common/util/NamedFormatterTests.java
new file mode 100644
index 0000000000000..f0cdf5b6514dd
--- /dev/null
+++ b/test/framework/src/test/java/org/elasticsearch/common/util/NamedFormatterTests.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.common.util;
+
+import org.elasticsearch.test.ESTestCase;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Collections.singletonMap;
+import static org.hamcrest.Matchers.equalTo;
+
+public class NamedFormatterTests extends ESTestCase {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ public void testPatternAreFormatted() {
+ assertThat(NamedFormatter.format("Hello, %(name)!", singletonMap("name", "world")), equalTo("Hello, world!"));
+ }
+
+ public void testDuplicatePatternsAreFormatted() {
+ assertThat(NamedFormatter.format("Hello, %(name) and %(name)!", singletonMap("name", "world")), equalTo("Hello, world and world!"));
+ }
+
+ public void testMultiplePatternsAreFormatted() {
+ final Map values = new HashMap<>();
+ values.put("name", "world");
+ values.put("second_name", "fred");
+
+ assertThat(
+ NamedFormatter.format("Hello, %(name) and %(second_name)!", values),
+ equalTo("Hello, world and fred!")
+ );
+ }
+
+ public void testEscapedPatternsAreNotFormatted() {
+ assertThat(NamedFormatter.format("Hello, \\%(name)!", singletonMap("name", "world")), equalTo("Hello, %(name)!"));
+ }
+
+ public void testUnknownPatternsThrowException() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("No parameter value for %(name)");
+ NamedFormatter.format("Hello, %(name)!", singletonMap("foo", "world"));
+ }
+}
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticatorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticatorTests.java
index e8b406da90b77..cb2ada89e34d4 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticatorTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticatorTests.java
@@ -21,6 +21,7 @@
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.util.NamedFormatter;
import org.elasticsearch.test.MockLogAppender;
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
import org.hamcrest.Matchers;
@@ -209,13 +210,30 @@ public void testParseEmptyContentIsRejected() throws Exception {
public void testParseContentWithNoAssertionsIsRejected() throws Exception {
Instant now = clock.instant();
- SamlToken token = token("\n" +
- "" +
- "" +
- IDP_ENTITY_ID + "" +
- "" +
- "");
+ final String xml = "\n"
+ + ""
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("now", now);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+
+ SamlToken token = token(NamedFormatter.format(xml, replacements));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("No assertions found in SAML response"));
assertThat(exception.getCause(), nullValue());
@@ -227,38 +245,59 @@ public void testSuccessfullyParseContentWithASingleValidAssertion() throws Excep
Instant validUntil = now.plusSeconds(30);
final String nameId = randomAlphaOfLengthBetween(12, 24);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" + nameId + "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " %(nameId)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("nameId", nameId);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final SamlAttributes attributes = authenticator.authenticate(token);
assertThat(attributes, notNullValue());
assertThat(attributes.attributes(), iterableWithSize(1));
@@ -409,38 +448,56 @@ public void testIncorrectResponseIssuerIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "xxx" + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)xxx"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("Issuer"));
assertThat(exception.getCause(), nullValue());
@@ -451,38 +508,56 @@ public void testIncorrectAssertionIssuerIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "_" + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)_"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("Issuer"));
assertThat(exception.getCause(), nullValue());
@@ -493,38 +568,61 @@ public void testIncorrectDestinationIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = randomBoolean() ? token(signDoc(xml)) : token(signAssertions(xml, idpSigningCertificatePair));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ final String xmlWithReplacements = NamedFormatter.format(xml, replacements);
+
+ SamlToken token = randomBoolean()
+ ? token(signDoc(xmlWithReplacements))
+ : token(signAssertions(xmlWithReplacements, idpSigningCertificatePair));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("destination"));
assertThat(exception.getCause(), nullValue());
@@ -535,38 +633,56 @@ public void testMissingDestinationIsNotRejectedForNotSignedResponse() throws Exc
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signAssertions(xml, idpSigningCertificatePair));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signAssertions(NamedFormatter.format(xml, replacements), idpSigningCertificatePair));
final SamlAttributes attributes = authenticator.authenticate(token);
assertThat(attributes, notNullValue());
assertThat(attributes.attributes(), iterableWithSize(1));
@@ -582,38 +698,58 @@ public void testIncorrectRequestIdIsRejected() throws Exception {
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
final String incorrectId = "_012345";
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("incorrectId", incorrectId);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("in-response-to"));
assertThat(exception.getMessage(), containsString(requestId));
@@ -626,38 +762,60 @@ public void testIncorrectRecipientIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("SAML Assertion SubjectConfirmationData Recipient"));
assertThat(exception.getMessage(), containsString(SP_ACS_URL + "/fake"));
@@ -667,25 +825,45 @@ public void testIncorrectRecipientIsRejected() throws Exception {
public void testAssertionWithoutSubjectIsRejected() throws Exception {
Instant now = clock.instant();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("randomId2", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("has no Subject"));
assertThat(exception.getCause(), nullValue());
@@ -695,32 +873,52 @@ public void testAssertionWithoutSubjectIsRejected() throws Exception {
public void testAssertionWithoutAuthnStatementIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("randomId2", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("Authn Statements while exactly one was expected."));
assertThat(exception.getCause(), nullValue());
@@ -733,39 +931,59 @@ public void testExpiredAuthnStatementSessionIsRejected() throws Exception {
Instant sessionValidUntil = now.plusSeconds(60);
final String nameId = randomAlphaOfLengthBetween(12, 24);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" + nameId + "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " %(nameId)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("nameId", nameId);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("sessionValidUntil", sessionValidUntil);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
// check that the content is valid "now"
- final SamlToken token = token(signDoc(xml));
+ final SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
assertThat(authenticator.authenticate(token), notNullValue());
// and still valid if we advance partway through the session expiry time
@@ -790,40 +1008,59 @@ public void testIncorrectAuthnContextClassRefIsRejected() throws Exception {
Instant validUntil = now.plusSeconds(30);
final String nameId = randomAlphaOfLengthBetween(12, 24);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" + nameId + "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " %(nameId)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("nameId", nameId);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
SamlAuthenticator authenticatorWithReqAuthnCtx = buildAuthenticator(() -> buildOpenSamlCredential(idpSigningCertificatePair),
Arrays.asList(X509_AUTHN_CTX, KERBEROS_AUTHN_CTX));
- SamlToken token = token(signDoc(xml));
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticatorWithReqAuthnCtx.authenticate(token));
assertThat(exception.getMessage(), containsString("Rejecting SAML assertion as the AuthnContextClassRef"));
assertThat(SamlUtils.isSamlException(exception), is(true));
@@ -831,28 +1068,46 @@ public void testIncorrectAuthnContextClassRefIsRejected() throws Exception {
public void testAssertionWithoutSubjectConfirmationIsRejected() throws Exception {
Instant now = clock.instant();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("now", now);
+ replacements.put("randomId", randomId());
+ replacements.put("randomId2", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("SAML Assertion subject contains [0] bearer SubjectConfirmation"));
assertThat(exception.getCause(), nullValue());
@@ -861,29 +1116,48 @@ public void testAssertionWithoutSubjectConfirmationIsRejected() throws Exception
public void testAssertionWithoutSubjectConfirmationDataIsRejected() throws Exception {
Instant now = clock.instant();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("randomId", randomId());
+ replacements.put("randomId2", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("bearer SubjectConfirmation, while exactly one was expected."));
assertThat(exception.getCause(), nullValue());
@@ -894,38 +1168,57 @@ public void testAssetionWithoutBearerSubjectConfirmationMethodIsRejected() throw
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_ATTRIB_NAME", METHOD_ATTRIB_NAME);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("bearer SubjectConfirmation, while exactly one was expected."));
assertThat(exception.getCause(), nullValue());
@@ -937,38 +1230,61 @@ public void testIncorrectSubjectConfirmationDataInResponseToIsRejected() throws
Instant validUntil = now.plusSeconds(30);
final String incorrectId = "_123456";
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("incorrectId", incorrectId);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("SAML Assertion SubjectConfirmationData is in-response-to"));
assertThat(exception.getMessage(), containsString(requestId));
@@ -981,40 +1297,58 @@ public void testExpiredSubjectConfirmationDataIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(120);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
// check that the content is valid "now"
- final SamlToken token = token(signDoc(xml));
+ final SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
assertThat(authenticator.authenticate(token), notNullValue());
// and still valid if we advance partway through the expiry time
@@ -1043,37 +1377,56 @@ public void testIdpInitiatedLoginIsAllowed() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
- final SamlToken token = token(signDoc(xml));
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ final SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
final SamlAttributes attributes = authenticator.authenticate(token);
assertThat(attributes, notNullValue());
assertThat(attributes.attributes(), iterableWithSize(1));
@@ -1084,45 +1437,67 @@ public void testIncorrectSigningKeyIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ final String xmlWithReplacements = NamedFormatter.format(xml, replacements);
// check that the content is valid when signed by the correct key-pair
- assertThat(authenticator.authenticate(token(signer.transform(xml, idpSigningCertificatePair))), notNullValue());
+ assertThat(authenticator.authenticate(token(signer.transform(xmlWithReplacements, idpSigningCertificatePair))), notNullValue());
// check is rejected when signed by a different key-pair
final Tuple wrongKey = readKeyPair("RSA_4096_updated");
- final ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class,
- () -> authenticator.authenticate(token(signer.transform(xml, wrongKey))));
+ final ElasticsearchSecurityException exception = expectThrows(
+ ElasticsearchSecurityException.class,
+ () -> authenticator.authenticate(token(signer.transform(xmlWithReplacements, wrongKey)))
+ );
assertThat(exception.getMessage(), containsString("SAML Signature"));
assertThat(exception.getMessage(), containsString("could not be validated"));
assertThat(exception.getCause(), nullValue());
@@ -1149,40 +1524,58 @@ public void testParsingRejectsTamperedContent() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
// check that the original signed content is valid
- final String signed = signer.transform(xml, idpSigningCertificatePair);
+ final String signed = signer.transform(NamedFormatter.format(xml, replacements), idpSigningCertificatePair);
assertThat(authenticator.authenticate(token(signed)), notNullValue());
// but altered content is rejected
@@ -1208,41 +1601,61 @@ public void testSigningWhenIdpHasMultipleKeys() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(30);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ final String xmlWithReplacements = NamedFormatter.format(xml, replacements);
// check that the content is valid when signed by the each of the key-pairs
for (Tuple key : keys) {
- assertThat(authenticator.authenticate(token(signer.transform(xml, key))), notNullValue());
+ assertThat(authenticator.authenticate(token(signer.transform(xmlWithReplacements, key))), notNullValue());
}
}
@@ -1305,41 +1718,59 @@ public void testExpiredContentIsRejected() throws Exception {
Instant now = clock.instant();
Instant validUntil = now.plusSeconds(120);
final String sessionindex = randomId();
- final String xml = "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "randomopaquestring" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " randomopaquestring"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
// check that the content is valid "now"
- final SamlToken token = token(signDoc(xml));
+ final SamlToken token = token(signDoc(NamedFormatter.format(xml, replacements)));
assertThat(authenticator.authenticate(token), notNullValue());
// and still valid if we advance partway through the expiry time
@@ -2079,19 +2510,31 @@ private Response toResponse(String xml) throws SAXException, IOException, Parser
private String getStatusFailedResponse() {
final Instant now = clock.instant();
- return "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" +
- "";
+ final String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("now", now);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+
+ return NamedFormatter.format(xml, replacements);
}
private String getSimpleResponse(Instant now) {
@@ -2101,43 +2544,66 @@ private String getSimpleResponse(Instant now) {
private String getSimpleResponse(Instant now, String nameId, String sessionindex) {
Instant validUntil = now.plusSeconds(30);
- return "\n" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" +
- "" + IDP_ENTITY_ID + "" +
- "" +
- "" + nameId + "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" + PASSWORD_AUTHN_CTX + "" +
- "" +
- "" +
- "" +
- "daredevil" +
- "" +
- "" +
- "defenders" +
- "netflix" +
- "" +
- "" +
- "";
+ String xml = "\n"
+ + ""
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " "
+ + " %(IDP_ENTITY_ID)"
+ + " "
+ + " %(nameId)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(PASSWORD_AUTHN_CTX)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " daredevil"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " defenders"
+ + " netflix"
+ + " "
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("IDP_ENTITY_ID", IDP_ENTITY_ID);
+ replacements.put("METHOD_BEARER", METHOD_BEARER);
+ replacements.put("nameId", nameId);
+ replacements.put("now", now);
+ replacements.put("PASSWORD_AUTHN_CTX", PASSWORD_AUTHN_CTX);
+ replacements.put("randomId", randomId());
+ replacements.put("requestId", requestId);
+ replacements.put("sessionindex", sessionindex);
+ replacements.put("SP_ACS_URL", SP_ACS_URL);
+ replacements.put("SP_ENTITY_ID", SP_ENTITY_ID);
+ replacements.put("TRANSIENT", TRANSIENT);
+ replacements.put("validUntil", validUntil);
+
+ return NamedFormatter.format(xml, replacements);
}
private String getResponseWithAudienceRestrictions(String... requiredAudiences) {
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlSpMetadataBuilderTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlSpMetadataBuilderTests.java
index 1133a71993d19..7d30be36bc9fd 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlSpMetadataBuilderTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlSpMetadataBuilderTests.java
@@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.security.authc.saml;
+import org.elasticsearch.common.util.NamedFormatter;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
import org.hamcrest.Matchers;
import org.junit.Before;
@@ -20,10 +21,14 @@
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.stream.Collectors;
+import static org.hamcrest.Matchers.equalTo;
+
public class SamlSpMetadataBuilderTests extends SamlTestCase {
private X509Certificate certificate;
@@ -67,16 +72,23 @@ public void testBuildMinimalistMetadata() throws Exception {
final Element element = new EntityDescriptorMarshaller().marshall(descriptor);
final String xml = SamlUtils.toString(element);
- assertThat(xml, Matchers.equalTo("" +
- "" +
- "" +
- "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" +
- "" +
- "" +
- ""
- ));
+
+ final String expectedXml = ""
+ + ""
+ + " "
+ + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
+ + " "
+ + " "
+ + "";
+
+ assertThat(xml, equalTo(normaliseXml(expectedXml)));
assertValidXml(xml);
}
@@ -101,66 +113,87 @@ public void testBuildFullMetadata() throws Exception {
final Element element = new EntityDescriptorMarshaller().marshall(descriptor);
final String xml = SamlUtils.toString(element);
- assertThat(xml, Matchers.equalTo("" +
- "" +
- "" +
- "" +
- "" +
- "MIIDWDCCAkCgAwIBAgIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA0GCSqGSIb3DQEBCwUAMB0xGzAZ" + System.lineSeparator() +
- "BgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDAeFw0xNzExMjkwMjQ3MjZaFw0yMDExMjgwMjQ3MjZa" + System.lineSeparator() +
- "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC" + System.lineSeparator() +
- "AQoCggEBALHTuPGOieCbD2mZUdYrdH4ofo7qFze6rQUROCLKqf69uBuwvraNWOcwxHUTKVlLMV3d" + System.lineSeparator() +
- "dKzYo+yfC44AMXrrV+79xVWsTCNHu9sxQzcDwiEx2OtOOX9MAk6tJQ3svNrMPNXWh8ftwmmY9XdF" + System.lineSeparator() +
- "ZwMYUdo6FPjSQj5uQTDmGWRgF08f7VRlk6N92d/fzn9DlDm+TFuaOr17OTSR4B6RTrNwKC29AmXQ" + System.lineSeparator() +
- "TwCijCObjLqyMEqP20dZCQeVf2qw8JKUHhW4r6mCLzqmeR+kRTqiHMSWxJddzxDGw6X7fOS7iuzB" + System.lineSeparator() +
- "0+TnsKwgu8nYrEXds9MkGf1Yco7WsM43g+Es+LhNHP+es70CAwEAAaOBjjCBizAdBgNVHQ4EFgQU" + System.lineSeparator() +
- "ILqVKGhIi8p5Xffsow/IKFLhRbIwWQYDVR0jBFIwUIAUILqVKGhIi8p5Xffsow/IKFLhRbKhIaQf" + System.lineSeparator() +
- "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTIIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA8G" + System.lineSeparator() +
- "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGhl4V9mp4SWSV2E3HAJ1PX+Vmp6k27K" + System.lineSeparator() +
- "d0tkOk1B9fyA13QB30teyiL7RR0vSHRyWFY8rQH1mHD366GKRWLITRG/QPULamGdYXX4h0pFj5ld" + System.lineSeparator() +
- "aubLxM/O9vEAxOgmo/lsdkeIq9tLBqY06r/5A/Mcgo63KGi00AFYBoyvqfOu6nRLPnQr+rKVfdNO" + System.lineSeparator() +
- "pWeIiFY1i2XTNZ3CZjNPSTwiQMUzrCxKXB9lL0vF6QL2Gj2iBhzNfXi88wf7xaR6XKY1wNuv3HLP" + System.lineSeparator() +
- "sL7n+PWby7LRX188dyS1dmKfQcrKL65OssBA5NC8CAYyBiygBmWN+5kVJM5fSb0SwPSoVWrNyz+8" + System.lineSeparator() +
- "IUldQE8=" +
- "" +
- "" +
- "" +
- "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" +
- "" +
- "" +
- "Hydra Kibana" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "Hydra" +
- "Hydra" +
- "https://hail.hydra/" +
- "" +
- "" +
- "Wolfgang" +
- "von Strucker" +
- "baron.strucker@supreme.hydra" +
- "" +
- "" +
- "Paul" +
- "Ebersol" +
- "pne@tech.hydra" +
- "" +
- ""
- ));
+
+ final String expectedCertificate = joinCertificateLines(
+ "MIIDWDCCAkCgAwIBAgIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA0GCSqGSIb3DQEBCwUAMB0xGzAZ",
+ "BgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDAeFw0xNzExMjkwMjQ3MjZaFw0yMDExMjgwMjQ3MjZa",
+ "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC",
+ "AQoCggEBALHTuPGOieCbD2mZUdYrdH4ofo7qFze6rQUROCLKqf69uBuwvraNWOcwxHUTKVlLMV3d",
+ "dKzYo+yfC44AMXrrV+79xVWsTCNHu9sxQzcDwiEx2OtOOX9MAk6tJQ3svNrMPNXWh8ftwmmY9XdF",
+ "ZwMYUdo6FPjSQj5uQTDmGWRgF08f7VRlk6N92d/fzn9DlDm+TFuaOr17OTSR4B6RTrNwKC29AmXQ",
+ "TwCijCObjLqyMEqP20dZCQeVf2qw8JKUHhW4r6mCLzqmeR+kRTqiHMSWxJddzxDGw6X7fOS7iuzB",
+ "0+TnsKwgu8nYrEXds9MkGf1Yco7WsM43g+Es+LhNHP+es70CAwEAAaOBjjCBizAdBgNVHQ4EFgQU",
+ "ILqVKGhIi8p5Xffsow/IKFLhRbIwWQYDVR0jBFIwUIAUILqVKGhIi8p5Xffsow/IKFLhRbKhIaQf",
+ "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTIIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA8G",
+ "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGhl4V9mp4SWSV2E3HAJ1PX+Vmp6k27K",
+ "d0tkOk1B9fyA13QB30teyiL7RR0vSHRyWFY8rQH1mHD366GKRWLITRG/QPULamGdYXX4h0pFj5ld",
+ "aubLxM/O9vEAxOgmo/lsdkeIq9tLBqY06r/5A/Mcgo63KGi00AFYBoyvqfOu6nRLPnQr+rKVfdNO",
+ "pWeIiFY1i2XTNZ3CZjNPSTwiQMUzrCxKXB9lL0vF6QL2Gj2iBhzNfXi88wf7xaR6XKY1wNuv3HLP",
+ "sL7n+PWby7LRX188dyS1dmKfQcrKL65OssBA5NC8CAYyBiygBmWN+5kVJM5fSb0SwPSoVWrNyz+8",
+ "IUldQE8="
+ );
+
+ final String expectedXml = ""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(expectedCertificate)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
+ + " "
+ + " "
+ + " Hydra Kibana"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " Hydra"
+ + " Hydra"
+ + " https://hail.hydra/"
+ + " "
+ + " "
+ + " Wolfgang"
+ + " von Strucker"
+ + " baron.strucker@supreme.hydra"
+ + " "
+ + " "
+ + " Paul"
+ + " Ebersol"
+ + " pne@tech.hydra"
+ + " "
+ + "";
+
+ final Map replacements = Collections.singletonMap("expectedCertificate", expectedCertificate);
+ final String expectedXmlWithCertificate = NamedFormatter.format(expectedXml, replacements);
+
+ assertThat(xml, equalTo(normaliseXml(expectedXmlWithCertificate)));
+
assertValidXml(xml);
}
@@ -185,110 +218,151 @@ public void testBuildFullMetadataWithSigningAndTwoEncryptionCerts() throws Excep
final Element element = new EntityDescriptorMarshaller().marshall(descriptor);
final String xml = SamlUtils.toString(element);
- assertThat(xml, Matchers.equalTo("" +
- "" +
- "" +
- "" +
- "" +
- "MIIDWDCCAkCgAwIBAgIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA0GCSqGSIb3DQEBCwUAMB0xGzAZ" + System.lineSeparator() +
- "BgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDAeFw0xNzExMjkwMjQ3MjZaFw0yMDExMjgwMjQ3MjZa" + System.lineSeparator() +
- "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC" + System.lineSeparator() +
- "AQoCggEBALHTuPGOieCbD2mZUdYrdH4ofo7qFze6rQUROCLKqf69uBuwvraNWOcwxHUTKVlLMV3d" + System.lineSeparator() +
- "dKzYo+yfC44AMXrrV+79xVWsTCNHu9sxQzcDwiEx2OtOOX9MAk6tJQ3svNrMPNXWh8ftwmmY9XdF" + System.lineSeparator() +
- "ZwMYUdo6FPjSQj5uQTDmGWRgF08f7VRlk6N92d/fzn9DlDm+TFuaOr17OTSR4B6RTrNwKC29AmXQ" + System.lineSeparator() +
- "TwCijCObjLqyMEqP20dZCQeVf2qw8JKUHhW4r6mCLzqmeR+kRTqiHMSWxJddzxDGw6X7fOS7iuzB" + System.lineSeparator() +
- "0+TnsKwgu8nYrEXds9MkGf1Yco7WsM43g+Es+LhNHP+es70CAwEAAaOBjjCBizAdBgNVHQ4EFgQU" + System.lineSeparator() +
- "ILqVKGhIi8p5Xffsow/IKFLhRbIwWQYDVR0jBFIwUIAUILqVKGhIi8p5Xffsow/IKFLhRbKhIaQf" + System.lineSeparator() +
- "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTIIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA8G" + System.lineSeparator() +
- "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGhl4V9mp4SWSV2E3HAJ1PX+Vmp6k27K" + System.lineSeparator() +
- "d0tkOk1B9fyA13QB30teyiL7RR0vSHRyWFY8rQH1mHD366GKRWLITRG/QPULamGdYXX4h0pFj5ld" + System.lineSeparator() +
- "aubLxM/O9vEAxOgmo/lsdkeIq9tLBqY06r/5A/Mcgo63KGi00AFYBoyvqfOu6nRLPnQr+rKVfdNO" + System.lineSeparator() +
- "pWeIiFY1i2XTNZ3CZjNPSTwiQMUzrCxKXB9lL0vF6QL2Gj2iBhzNfXi88wf7xaR6XKY1wNuv3HLP" + System.lineSeparator() +
- "sL7n+PWby7LRX188dyS1dmKfQcrKL65OssBA5NC8CAYyBiygBmWN+5kVJM5fSb0SwPSoVWrNyz+8" + System.lineSeparator() +
- "IUldQE8=" +
- "" +
- "" +
- "" +
- "" +
- "MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNVBAoTA29yZzEW" + System.lineSeparator() +
- "MBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3RpY3NlYXJjaCBUZXN0IE5vZGUw" + System.lineSeparator() +
- "HhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsT" + System.lineSeparator() +
- "DWVsYXN0aWNzZWFyY2gxIDAeBgNVBAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkq" + System.lineSeparator() +
- "hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUK" + System.lineSeparator() +
- "KNR1Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c7u0sLch9" + System.lineSeparator() +
- "p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg/lATm8V71LMY68inht71" + System.lineSeparator() +
- "/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5zJhn660es/1ZnR6nvwt6xnSTl/mNHMjk" + System.lineSeparator() +
- "fv1bs4rJ/py3qPxicdoSIn/KyojUcgHVF38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQAB" + System.lineSeparator() +
- "o4G/MIG8MAkGA1UdEwQCMAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREE" + System.lineSeparator() +
- "gYcwgYSCCWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghdsb2Nh" + System.lineSeparator() +
- "bGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5sb2NhbGRvbWFpbjaH" + System.lineSeparator() +
- "BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAMjGGXT8Nt1tbl2GkiKt" + System.lineSeparator() +
- "miuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHek2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugo" + System.lineSeparator() +
- "Q3wct0bQC5wEWYN+oMDvSyO6M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzq" + System.lineSeparator() +
- "k/mQTug+Y8aEmVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z" + System.lineSeparator() +
- "1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxCy4g6cTMM3S/U" + System.lineSeparator() +
- "Mt5/+aIB2JAuMKyuD+A=" +
- "" +
- "" +
- "" +
- "" +
- "MIID1zCCAr+gAwIBAgIJALnUl/KSS74pMA0GCSqGSIb3DQEBCwUAMEoxDDAKBgNVBAoTA29yZzEW" + System.lineSeparator() +
- "MBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVu" + System.lineSeparator() +
- "dDAeFw0xNTA5MjMxODUyNTVaFw0xOTA5MjIxODUyNTVaMEoxDDAKBgNVBAoTA29yZzEWMBQGA1UE" + System.lineSeparator() +
- "CxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVudDCCASIw" + System.lineSeparator() +
- "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMKm+P6vDAff0c6BWKGdhnYoNl9HijLIgfU3d9CQ" + System.lineSeparator() +
- "cqKtwT+yUW3DPSVjIfaLmDIGj6Hl8jTHWPB7ZP4fzhrPi6m4qlRGclJMECBuNASZFiPDtEDv3mso" + System.lineSeparator() +
- "eqOKQet6n7PZvgpWM7hxYZO4P1aMKJtRsFAdvBAdZUnv0spR5G4UZTHzSKmMeanIKFkLaD0XVKiL" + System.lineSeparator() +
- "Qu9/z9M6roDQeAEoCJ/8JsanG8ih2ymfPHIZuNyYIOrVekHN2zU6bnVn8/PCeZSjS6h5xYw+Jl5g" + System.lineSeparator() +
- "zGI/n+F5CZ+THoH8pM4pGp6xRVzpiH12gvERGwgSIDXdn/+uZZj+4lE7n2ENRSOt5KcOGG99r60C" + System.lineSeparator() +
- "AwEAAaOBvzCBvDAJBgNVHRMEAjAAMB0GA1UdDgQWBBSSFhBXNp7AaNrHdlgCV0mCEzt7ajCBjwYD" + System.lineSeparator() +
- "VR0RBIGHMIGEgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5sb2NhbGRvbWFpboIKbG9jYWxob3N0NIIX" + System.lineSeparator() +
- "bG9jYWxob3N0NC5sb2NhbGRvbWFpbjSCCmxvY2FsaG9zdDaCF2xvY2FsaG9zdDYubG9jYWxkb21h" + System.lineSeparator() +
- "aW42hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQANvAkddfLxn4/B" + System.lineSeparator() +
- "CY4LY/1ET3d7ZRldjFTyjjHRYJ3CYBXWVahMskLxIcFNca8YjKfXoX8mcK+NQK/dAbGHXqk76yMl" + System.lineSeparator() +
- "krKjh1OQiZ1YAX5ryYerGrZ99N3E9wnbn72bW3iumoLlqmTWlHEpMI0Ql6J75BQLTgKHxCPupVA5" + System.lineSeparator() +
- "sTbWkKwGjXXAi84rUlzhDJOR8jk3/7ct0iZO8Hk6AWMcNix5Wka3IDGUXuEVevYRlxgVyCxcnZWC" + System.lineSeparator() +
- "7JWREpar5aIPQFkY6VCEglxwUyXbHZw5T/u6XaKKnS7gz8RiwRh68ddSQJeEHi5e4onUD7bOCJgf" + System.lineSeparator() +
- "siUwdiCkDbfN9Yum8OIpmBRs" +
- "" +
- "" +
- "" +
- "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" +
- "" +
- "" +
- "Hydra Kibana" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "Hydra" +
- "Hydra" +
- "https://hail.hydra/" +
- "" +
- "" +
- "Wolfgang" +
- "von Strucker" +
- "baron.strucker@supreme.hydra" +
- "" +
- "" +
- "Paul" +
- "Ebersol" +
- "pne@tech.hydra" +
- "" +
- ""
- ));
+
+ final String expectedCertificateOne = joinCertificateLines(
+ "MIIDWDCCAkCgAwIBAgIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA0GCSqGSIb3DQEBCwUAMB0xGzAZ",
+ "BgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDAeFw0xNzExMjkwMjQ3MjZaFw0yMDExMjgwMjQ3MjZa",
+ "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC",
+ "AQoCggEBALHTuPGOieCbD2mZUdYrdH4ofo7qFze6rQUROCLKqf69uBuwvraNWOcwxHUTKVlLMV3d",
+ "dKzYo+yfC44AMXrrV+79xVWsTCNHu9sxQzcDwiEx2OtOOX9MAk6tJQ3svNrMPNXWh8ftwmmY9XdF",
+ "ZwMYUdo6FPjSQj5uQTDmGWRgF08f7VRlk6N92d/fzn9DlDm+TFuaOr17OTSR4B6RTrNwKC29AmXQ",
+ "TwCijCObjLqyMEqP20dZCQeVf2qw8JKUHhW4r6mCLzqmeR+kRTqiHMSWxJddzxDGw6X7fOS7iuzB",
+ "0+TnsKwgu8nYrEXds9MkGf1Yco7WsM43g+Es+LhNHP+es70CAwEAAaOBjjCBizAdBgNVHQ4EFgQU",
+ "ILqVKGhIi8p5Xffsow/IKFLhRbIwWQYDVR0jBFIwUIAUILqVKGhIi8p5Xffsow/IKFLhRbKhIaQf",
+ "MB0xGzAZBgNVBAMTEkVsYXN0aWNzZWFyY2gtU0FNTIIVANRTZaFrK+Pz19O8TZsb3HSJmAWpMA8G",
+ "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGhl4V9mp4SWSV2E3HAJ1PX+Vmp6k27K",
+ "d0tkOk1B9fyA13QB30teyiL7RR0vSHRyWFY8rQH1mHD366GKRWLITRG/QPULamGdYXX4h0pFj5ld",
+ "aubLxM/O9vEAxOgmo/lsdkeIq9tLBqY06r/5A/Mcgo63KGi00AFYBoyvqfOu6nRLPnQr+rKVfdNO",
+ "pWeIiFY1i2XTNZ3CZjNPSTwiQMUzrCxKXB9lL0vF6QL2Gj2iBhzNfXi88wf7xaR6XKY1wNuv3HLP",
+ "sL7n+PWby7LRX188dyS1dmKfQcrKL65OssBA5NC8CAYyBiygBmWN+5kVJM5fSb0SwPSoVWrNyz+8",
+ "IUldQE8="
+ );
+
+ final String expectedCertificateTwo = joinCertificateLines(
+ "MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNVBAoTA29yZzEW",
+ "MBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3RpY3NlYXJjaCBUZXN0IE5vZGUw",
+ "HhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsT",
+ "DWVsYXN0aWNzZWFyY2gxIDAeBgNVBAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkq",
+ "hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUK",
+ "KNR1Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c7u0sLch9",
+ "p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg/lATm8V71LMY68inht71",
+ "/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5zJhn660es/1ZnR6nvwt6xnSTl/mNHMjk",
+ "fv1bs4rJ/py3qPxicdoSIn/KyojUcgHVF38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQAB",
+ "o4G/MIG8MAkGA1UdEwQCMAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREE",
+ "gYcwgYSCCWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghdsb2Nh",
+ "bGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5sb2NhbGRvbWFpbjaH",
+ "BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAMjGGXT8Nt1tbl2GkiKt",
+ "miuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHek2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugo",
+ "Q3wct0bQC5wEWYN+oMDvSyO6M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzq",
+ "k/mQTug+Y8aEmVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z",
+ "1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxCy4g6cTMM3S/U",
+ "Mt5/+aIB2JAuMKyuD+A="
+ );
+
+ final String expectedCertificateThree = joinCertificateLines(
+ "MIID1zCCAr+gAwIBAgIJALnUl/KSS74pMA0GCSqGSIb3DQEBCwUAMEoxDDAKBgNVBAoTA29yZzEW",
+ "MBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVu",
+ "dDAeFw0xNTA5MjMxODUyNTVaFw0xOTA5MjIxODUyNTVaMEoxDDAKBgNVBAoTA29yZzEWMBQGA1UE",
+ "CxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVudDCCASIw",
+ "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMKm+P6vDAff0c6BWKGdhnYoNl9HijLIgfU3d9CQ",
+ "cqKtwT+yUW3DPSVjIfaLmDIGj6Hl8jTHWPB7ZP4fzhrPi6m4qlRGclJMECBuNASZFiPDtEDv3mso",
+ "eqOKQet6n7PZvgpWM7hxYZO4P1aMKJtRsFAdvBAdZUnv0spR5G4UZTHzSKmMeanIKFkLaD0XVKiL",
+ "Qu9/z9M6roDQeAEoCJ/8JsanG8ih2ymfPHIZuNyYIOrVekHN2zU6bnVn8/PCeZSjS6h5xYw+Jl5g",
+ "zGI/n+F5CZ+THoH8pM4pGp6xRVzpiH12gvERGwgSIDXdn/+uZZj+4lE7n2ENRSOt5KcOGG99r60C",
+ "AwEAAaOBvzCBvDAJBgNVHRMEAjAAMB0GA1UdDgQWBBSSFhBXNp7AaNrHdlgCV0mCEzt7ajCBjwYD",
+ "VR0RBIGHMIGEgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5sb2NhbGRvbWFpboIKbG9jYWxob3N0NIIX",
+ "bG9jYWxob3N0NC5sb2NhbGRvbWFpbjSCCmxvY2FsaG9zdDaCF2xvY2FsaG9zdDYubG9jYWxkb21h",
+ "aW42hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQANvAkddfLxn4/B",
+ "CY4LY/1ET3d7ZRldjFTyjjHRYJ3CYBXWVahMskLxIcFNca8YjKfXoX8mcK+NQK/dAbGHXqk76yMl",
+ "krKjh1OQiZ1YAX5ryYerGrZ99N3E9wnbn72bW3iumoLlqmTWlHEpMI0Ql6J75BQLTgKHxCPupVA5",
+ "sTbWkKwGjXXAi84rUlzhDJOR8jk3/7ct0iZO8Hk6AWMcNix5Wka3IDGUXuEVevYRlxgVyCxcnZWC",
+ "7JWREpar5aIPQFkY6VCEglxwUyXbHZw5T/u6XaKKnS7gz8RiwRh68ddSQJeEHi5e4onUD7bOCJgf",
+ "siUwdiCkDbfN9Yum8OIpmBRs"
+ );
+
+ final String expectedXml = ""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(expectedCertificateOne)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(expectedCertificateTwo)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " %(expectedCertificateThree)"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
+ + " "
+ + " "
+ + " Hydra Kibana"
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " Hydra"
+ + " Hydra"
+ + " https://hail.hydra/"
+ + " "
+ + " "
+ + " Wolfgang"
+ + " von Strucker"
+ + " baron.strucker@supreme.hydra"
+ + " "
+ + " "
+ + " Paul"
+ + " Ebersol"
+ + " pne@tech.hydra"
+ + " "
+ + "";
+
+ final Map replacements = new HashMap<>();
+ replacements.put("expectedCertificateOne", expectedCertificateOne);
+ replacements.put("expectedCertificateTwo", expectedCertificateTwo);
+ replacements.put("expectedCertificateThree", expectedCertificateThree);
+
+ final String expectedXmlWithCertificate = NamedFormatter.format(expectedXml, replacements);
+
+ assertThat(xml, equalTo(normaliseXml(expectedXmlWithCertificate)));
+
assertValidXml(xml);
}
@@ -307,4 +381,14 @@ public void testAttributeNameIsRequired() {
private void assertValidXml(String xml) throws Exception {
SamlUtils.validate(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)), SamlMetadataCommand.METADATA_SCHEMA);
}
-}
\ No newline at end of file
+
+ private String joinCertificateLines(String... lines) {
+ return Arrays.stream(lines).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ private String normaliseXml(String input) {
+ // Remove spaces between elements, and compress other spaces. These patterns don't use \s because
+ // that would match newlines.
+ return input.replaceAll("> +<", "><").replaceAll(" +", " ");
+ }
+}