Handle RelayState in preparing a SAMLAuthN Request (#46534) (#47092)

This change allows for the caller of the `saml/prepare` API to pass
a `relay_state` parameter that will then be part of the redirect
URL in the response as the `RelayState` query parameter.

The SAML IdP is required to reflect back the value of that relay
state when sending a SAML Response. The caller of the APIs can
then, when receiving the SAML Response, read and consume the value
as it see fit.
This commit is contained in:
Ioannis Kakavas 2019-09-25 13:23:46 +03:00 committed by GitHub
parent eef1ba3fad
commit 23bceaadf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 30 additions and 7 deletions

View File

@ -179,8 +179,8 @@ task verifyVersions {
* after the backport of the backcompat code is complete.
*/
boolean bwc_tests_enabled = true
final String bwc_tests_disabled_issue = "" /* place a PR link here when committing bwc changes */
boolean bwc_tests_enabled = false
final String bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/pull/46534" /* place a PR link here when committing bwc changes */
if (bwc_tests_enabled == false) {
if (bwc_tests_disabled_issue.isEmpty()) {
throw new GradleException("bwc_tests_disabled_issue must be set when bwc_tests_enabled == false")

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.core.security.action.saml;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
@ -24,10 +25,16 @@ public final class SamlPrepareAuthenticationRequest extends ActionRequest {
@Nullable
private String assertionConsumerServiceURL;
@Nullable
private String relayState;
public SamlPrepareAuthenticationRequest(StreamInput in) throws IOException {
super(in);
realmName = in.readOptionalString();
assertionConsumerServiceURL = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_5_0)) {
relayState = in.readOptionalString();
}
}
public SamlPrepareAuthenticationRequest() {
@ -54,11 +61,20 @@ public final class SamlPrepareAuthenticationRequest extends ActionRequest {
this.assertionConsumerServiceURL = assertionConsumerServiceURL;
}
public String getRelayState() {
return relayState;
}
public void setRelayState(String relayState) {
this.relayState = relayState;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"realmName=" + realmName +
", assertionConsumerServiceURL=" + assertionConsumerServiceURL +
", relayState=" + relayState +
'}';
}
@ -67,5 +83,8 @@ public final class SamlPrepareAuthenticationRequest extends ActionRequest {
super.writeTo(out);
out.writeOptionalString(realmName);
out.writeOptionalString(assertionConsumerServiceURL);
if (out.getVersion().onOrAfter(Version.V_7_5_0)) {
out.writeOptionalString(relayState);
}
}
}

View File

@ -49,14 +49,14 @@ public final class TransportSamlPrepareAuthenticationAction
} else if (realms.size() > 1) {
listener.onFailure(SamlUtils.samlException("Found multiple matching realms [{}] for [{}]", realms, request));
} else {
prepareAuthentication(realms.get(0), listener);
prepareAuthentication(realms.get(0), request.getRelayState(), listener);
}
}
private void prepareAuthentication(SamlRealm realm, ActionListener<SamlPrepareAuthenticationResponse> listener) {
private void prepareAuthentication(SamlRealm realm, String relayState, ActionListener<SamlPrepareAuthenticationResponse> listener) {
final AuthnRequest authnRequest = realm.buildAuthenticationRequest();
try {
String redirectUrl = new SamlRedirect(authnRequest, realm.getSigningConfiguration()).getRedirectUrl();
String redirectUrl = new SamlRedirect(authnRequest, realm.getSigningConfiguration()).getRedirectUrl(relayState);
listener.onResponse(new SamlPrepareAuthenticationResponse(
realm.name(),
authnRequest.getID(),

View File

@ -25,7 +25,7 @@ public class SamlRedirect {
private final SAMLObject samlObject;
private final String destination;
private final String parameterName;
private final SigningConfiguration signing;
private final SigningConfiguration signing;
public SamlRedirect(RequestAbstractType request, SigningConfiguration signing) {
this.samlObject = request;

View File

@ -44,6 +44,7 @@ public class RestSamlPrepareAuthenticationAction extends SamlBaseRestHandler {
static {
PARSER.declareString(SamlPrepareAuthenticationRequest::setAssertionConsumerServiceURL, new ParseField("acs"));
PARSER.declareString(SamlPrepareAuthenticationRequest::setRealmName, new ParseField("realm"));
PARSER.declareString(SamlPrepareAuthenticationRequest::setRelayState, new ParseField("relay_state"));
}
public RestSamlPrepareAuthenticationAction(Settings settings, RestController controller, XPackLicenseState licenseState) {

View File

@ -18,6 +18,7 @@ public class SamlPrepareAuthenticationRequestTests extends SamlTestCase {
final SamlPrepareAuthenticationRequest req = new SamlPrepareAuthenticationRequest();
req.setRealmName("saml1");
req.setAssertionConsumerServiceURL("https://sp.example.com/sso/saml2/post");
req.setRelayState("the_relay_state");
serialiseAndValidate(req);
}
@ -25,6 +26,7 @@ public class SamlPrepareAuthenticationRequestTests extends SamlTestCase {
final SamlPrepareAuthenticationRequest req = new SamlPrepareAuthenticationRequest();
req.setRealmName(null);
req.setAssertionConsumerServiceURL(null);
req.setRelayState(null);
serialiseAndValidate(req);
}
@ -36,7 +38,8 @@ public class SamlPrepareAuthenticationRequestTests extends SamlTestCase {
assertThat(req2.getRealmName(), Matchers.equalTo(req1.getRealmName()));
assertThat(req2.getAssertionConsumerServiceURL(), Matchers.equalTo(req1.getAssertionConsumerServiceURL()));
assertThat(req2.getRelayState(), Matchers.equalTo(req1.getRelayState()));
assertThat(req2.getParentTask(), Matchers.equalTo(req1.getParentTask()));
}
}
}