diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index 351f793e8969f..7e25f43419f98 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -22,6 +22,8 @@ import org.elasticsearch.client.security.user.User; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; @@ -38,7 +40,7 @@ * user object contains all user metadata which Elasticsearch uses to map roles, * etc. */ -public final class AuthenticateResponse { +public final class AuthenticateResponse implements ToXContentObject { static final ParseField USERNAME = new ParseField("username"); static final ParseField ROLES = new ParseField("roles"); @@ -123,6 +125,27 @@ public String getAuthenticationType() { return authenticationType; } + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject() + .field("username", user.getUsername()) + .field("roles", user.getRoles()) + .field("metadata", user.getMetadata()) + .field("full_name", user.getFullName()) + .field("email", user.getEmail()) + .field("enabled", enabled); + builder.startObject("authentication_realm") + .field("name", authenticationRealm.name) + .field("type", authenticationRealm.type); + builder.endObject(); + builder.startObject("lookup_realm") + .field("name", lookupRealm == null? authenticationRealm.name: lookupRealm.name) + .field("type", lookupRealm == null? authenticationRealm.type: lookupRealm.type); + builder.endObject(); + builder.field("authentication_type", authenticationType); + return builder.endObject(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java index dc71d49f4b770..808c9e06914da 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java @@ -42,15 +42,17 @@ public final class CreateTokenResponse { private final String scope; private final String refreshToken; private final String kerberosAuthenticationResponseToken; + private final AuthenticateResponse authentication; public CreateTokenResponse(String accessToken, String type, TimeValue expiresIn, String scope, String refreshToken, - String kerberosAuthenticationResponseToken) { + String kerberosAuthenticationResponseToken, AuthenticateResponse authentication) { this.accessToken = accessToken; this.type = type; this.expiresIn = expiresIn; this.scope = scope; this.refreshToken = refreshToken; this.kerberosAuthenticationResponseToken = kerberosAuthenticationResponseToken; + this.authentication = authentication; } public String getAccessToken() { @@ -77,6 +79,8 @@ public String getKerberosAuthenticationResponseToken() { return kerberosAuthenticationResponseToken; } + public AuthenticateResponse getAuthentication() { return authentication; } + @Override public boolean equals(Object o) { if (this == o) { @@ -91,17 +95,19 @@ public boolean equals(Object o) { Objects.equals(expiresIn, that.expiresIn) && Objects.equals(scope, that.scope) && Objects.equals(refreshToken, that.refreshToken) && - Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken); + Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken)&& + Objects.equals(authentication, that.authentication); } @Override public int hashCode() { - return Objects.hash(accessToken, type, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken); + return Objects.hash(accessToken, type, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken, authentication); } private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( "create_token_response", true, args -> new CreateTokenResponse((String) args[0], (String) args[1], - TimeValue.timeValueSeconds((Long) args[2]), (String) args[3], (String) args[4], (String) args[5])); + TimeValue.timeValueSeconds((Long) args[2]), (String) args[3], (String) args[4], (String) args[5], + (AuthenticateResponse) args[6])); static { PARSER.declareString(constructorArg(), new ParseField("access_token")); @@ -110,6 +116,7 @@ public int hashCode() { PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("scope")); PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("refresh_token")); PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("kerberos_authentication_response_token")); + PARSER.declareObject(constructorArg(), (p, c) -> AuthenticateResponse.fromXContent(p), new ParseField("authentication")); } public static CreateTokenResponse fromXContent(XContentParser parser) throws IOException { diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponse.java index 064a5a9a4e293..4e4a9fd03b491 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponse.java @@ -34,11 +34,14 @@ public final class DelegatePkiAuthenticationResponse { private final String accessToken; private final String type; private final TimeValue expiresIn; + private final AuthenticateResponse authentication; - public DelegatePkiAuthenticationResponse(String accessToken, String type, TimeValue expiresIn) { + public DelegatePkiAuthenticationResponse(String accessToken, String type, TimeValue expiresIn, + AuthenticateResponse authentication) { this.accessToken = accessToken; this.type = type; this.expiresIn = expiresIn; + this.authentication = authentication; } public String getAccessToken() { @@ -53,6 +56,8 @@ public TimeValue getExpiresIn() { return expiresIn; } + public AuthenticateResponse getAuthentication() { return authentication; } + @Override public boolean equals(Object o) { if (this == o) { @@ -64,22 +69,26 @@ public boolean equals(Object o) { final DelegatePkiAuthenticationResponse that = (DelegatePkiAuthenticationResponse) o; return Objects.equals(accessToken, that.accessToken) && Objects.equals(type, that.type) && - Objects.equals(expiresIn, that.expiresIn); + Objects.equals(expiresIn, that.expiresIn) && + Objects.equals(authentication, that.authentication); } @Override public int hashCode() { - return Objects.hash(accessToken, type, expiresIn); + return Objects.hash(accessToken, type, expiresIn, authentication); } + @SuppressWarnings("unchecked") private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( "delegate_pki_response", true, - args -> new DelegatePkiAuthenticationResponse((String) args[0], (String) args[1], TimeValue.timeValueSeconds((Long) args[2]))); + args -> new DelegatePkiAuthenticationResponse((String) args[0], (String) args[1], TimeValue.timeValueSeconds((Long) args[2]), + (AuthenticateResponse) args[3])); static { PARSER.declareString(constructorArg(), new ParseField("access_token")); PARSER.declareString(constructorArg(), new ParseField("type")); PARSER.declareLong(constructorArg(), new ParseField("expires_in")); + PARSER.declareObject(constructorArg(), (p, c) -> AuthenticateResponse.fromXContent(p), new ParseField("authentication")); } public static DelegatePkiAuthenticationResponse fromXContent(XContentParser parser) throws IOException { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java index 34a03647f6060..7c9a52d97eb83 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.client.security; +import org.elasticsearch.client.security.user.User; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -26,6 +27,7 @@ import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.util.Arrays; import static org.hamcrest.Matchers.equalTo; @@ -38,6 +40,10 @@ public void testFromXContent() throws IOException { final String scope = randomBoolean() ? null : randomAlphaOfLength(4); final String type = randomAlphaOfLength(6); final String kerberosAuthenticationResponseToken = randomBoolean() ? null : randomAlphaOfLength(7); + final AuthenticateResponse authentication = new AuthenticateResponse(new User(randomAlphaOfLength(7), + Arrays.asList( randomAlphaOfLength(9) )), + true, new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(7) ), + new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5) ), "realm"); final XContentType xContentType = randomFrom(XContentType.values()); final XContentBuilder builder = XContentFactory.contentBuilder(xContentType); @@ -54,6 +60,7 @@ public void testFromXContent() throws IOException { if (kerberosAuthenticationResponseToken != null) { builder.field("kerberos_authentication_response_token", kerberosAuthenticationResponseToken); } + builder.field("authentication", authentication); builder.endObject(); BytesReference xContent = BytesReference.bytes(builder); @@ -64,5 +71,6 @@ public void testFromXContent() throws IOException { assertThat(response.getType(), equalTo(type)); assertThat(response.getExpiresIn(), equalTo(expiresIn)); assertThat(response.getKerberosAuthenticationResponseToken(), equalTo(kerberosAuthenticationResponseToken)); + assertThat(response.getAuthentication(), equalTo(authentication)); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponseTests.java index 8a5ecb16e540e..163977a2ca4c0 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DelegatePkiAuthenticationResponseTests.java @@ -19,14 +19,21 @@ package org.elasticsearch.client.security; +import org.elasticsearch.Version; import org.elasticsearch.client.AbstractResponseTestCase; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.client.security.DelegatePkiAuthenticationResponse; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.User; import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class DelegatePkiAuthenticationResponseTests extends @@ -37,7 +44,8 @@ public class DelegatePkiAuthenticationResponseTests extends protected org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse createServerTestInstance( XContentType xContentType) { return new org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse(randomAlphaOfLength(6), - TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn")); + TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn"), + createAuthentication()); } @Override @@ -51,5 +59,52 @@ protected void assertInstances(org.elasticsearch.xpack.core.security.action.Dele assertThat(serverTestInstance.getAccessToken(), is(clientInstance.getAccessToken())); assertThat(serverTestInstance.getExpiresIn(), is(clientInstance.getExpiresIn())); assertThat(clientInstance.getType(), is("Bearer")); + AuthenticateResponse serverAuthenticationResponse = createServerAuthenticationResponse(serverTestInstance.getAuthentication()); + User user = serverTestInstance.getAuthentication().getUser(); + assertThat(serverAuthenticationResponse, equalTo(clientInstance.getAuthentication())); + } + + protected Authentication createAuthentication() { + final String username = randomAlphaOfLengthBetween(1, 4); + final String[] roles = generateRandomStringArray(4, 4, false, true); + final Map metadata; + metadata = new HashMap<>(); + if (randomBoolean()) { + metadata.put("string", null); + } else { + metadata.put("string", randomAlphaOfLengthBetween(0, 4)); + } + if (randomBoolean()) { + metadata.put("string_list", null); + } else { + metadata.put("string_list", Arrays.asList(generateRandomStringArray(4, 4, false, true))); + } + final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final boolean enabled = randomBoolean(); + final String authenticationRealmName = randomAlphaOfLength(5); + final String authenticationRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos"); + final String lookupRealmName = randomAlphaOfLength(5); + final String lookupRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos"); + final String nodeName = randomAlphaOfLengthBetween(1, 10); + final Authentication.AuthenticationType authenticationType = randomFrom(Authentication.AuthenticationType.values()); + return new Authentication( + new User(username, roles, fullName, email, metadata, true), + new Authentication.RealmRef(authenticationRealmName, authenticationRealmType, nodeName), + new Authentication.RealmRef(lookupRealmName, lookupRealmType, nodeName), Version.CURRENT, authenticationType, metadata); + } + + AuthenticateResponse createServerAuthenticationResponse(Authentication authentication){ + User user = authentication.getUser(); + org.elasticsearch.client.security.user.User cUser = new org.elasticsearch.client.security.user.User(user.principal(), + Arrays.asList(user.roles()), user.metadata(), user.fullName(), user.email()); + AuthenticateResponse.RealmInfo authenticatedBy = new AuthenticateResponse.RealmInfo(authentication.getAuthenticatedBy().getName(), + authentication.getAuthenticatedBy().getType()); + AuthenticateResponse.RealmInfo lookedUpBy = new AuthenticateResponse.RealmInfo(authentication.getLookedUpBy() == null? + authentication.getAuthenticatedBy().getName(): authentication.getLookedUpBy().getName(), + authentication.getLookedUpBy() == null? + authentication.getAuthenticatedBy().getType(): authentication.getLookedUpBy().getType()); + return new AuthenticateResponse(cUser, user.enabled(), authenticatedBy, lookedUpBy, + authentication.getAuthenticationType().toString().toLowerCase(Locale.ROOT)); } } diff --git a/docs/java-rest/high-level/security/create-token.asciidoc b/docs/java-rest/high-level/security/create-token.asciidoc index d911c747a13f2..b20242220479f 100644 --- a/docs/java-rest/high-level/security/create-token.asciidoc +++ b/docs/java-rest/high-level/security/create-token.asciidoc @@ -49,6 +49,8 @@ The returned `CreateTokenResponse` contains the following properties: `scope`:: The scope of the token. May be `null`. `refreshToken`:: A secondary "refresh" token that may be used to extend the life of an access token. May be `null`. +`authentication`:: This is the authentication object for the newly created token. See also +<<{upid}-authenticate-response, authenticate response>> for details. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -83,4 +85,4 @@ include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-execute-li -------------------------------------------------- <1> Called when the execution is successfully completed. The response is provided as an argument -<2> Called in case of failure. The raised exception is provided as an argument \ No newline at end of file +<2> Called in case of failure. The raised exception is provided as an argument diff --git a/docs/java-rest/high-level/security/delegate-pki-authentication.asciidoc b/docs/java-rest/high-level/security/delegate-pki-authentication.asciidoc index ca3f832f40562..189318d150008 100644 --- a/docs/java-rest/high-level/security/delegate-pki-authentication.asciidoc +++ b/docs/java-rest/high-level/security/delegate-pki-authentication.asciidoc @@ -52,6 +52,8 @@ The returned +{response}+ contains the following properties: `type`:: The type of the token, this is always `"Bearer"`. `expiresIn`:: The length of time (in seconds) until the token will expire. The token will be considered invalid after that time. +`authentication`:: This is the authentication object for the newly created token. See also +<<{upid}-authenticate-response, authenticate response>> for details. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- diff --git a/x-pack/docs/en/rest-api/security/delegate-pki-authentication.asciidoc b/x-pack/docs/en/rest-api/security/delegate-pki-authentication.asciidoc index c34ecdcb5f337..5ed76fe3e3829 100644 --- a/x-pack/docs/en/rest-api/security/delegate-pki-authentication.asciidoc +++ b/x-pack/docs/en/rest-api/security/delegate-pki-authentication.asciidoc @@ -89,7 +89,28 @@ Which returns the following response: { "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", "type" : "Bearer", - "expires_in" : 1200 + "expires_in" : 1200, + "authentication" : { + "username" : "Elasticsearch Test Client", + "roles" : [ ], + "full_name" : null, + "email" : null, + "metadata" : { + "pki_dn" : "O=org, OU=Elasticsearch, CN=Elasticsearch Test Client", + "pki_delegated_by_user" : "test_admin", + "pki_delegated_by_realm" : "file" + }, + "enabled" : true, + "authentication_realm" : { + "name" : "pki1", + "type" : "pki" + }, + "lookup_realm" : { + "name" : "pki1", + "type" : "pki" + }, + "authentication_type" : "realm" + } } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index 196156639bdc8..d859d7d50d785 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -10,7 +10,7 @@ Creates a bearer token for access without requiring basic authentication. [[security-api-get-token-request]] ==== {api-request-title} -`POST /_security/oauth2/token` +`POST /_security/oauth2/token` [[security-api-get-token-prereqs]] ==== {api-prereq-title} @@ -22,9 +22,9 @@ Creates a bearer token for access without requiring basic authentication. The tokens are created by the {es} Token Service, which is automatically enabled when you configure TLS on the HTTP interface. See <>. Alternatively, -you can explicitly enable the `xpack.security.authc.token.enabled` setting. When -you are running in production mode, a bootstrap check prevents you from enabling -the token service unless you also enable TLS on the HTTP interface. +you can explicitly enable the `xpack.security.authc.token.enabled` setting. When +you are running in production mode, a bootstrap check prevents you from enabling +the token service unless you also enable TLS on the HTTP interface. The get token API takes the same parameters as a typical OAuth 2.0 token API except for the use of a JSON request body. @@ -38,7 +38,7 @@ they are valid and after that time period, they can no longer be used. That time period is defined by the `xpack.security.authc.token.timeout` setting. For more information, see <>. -If you want to invalidate a token immediately, you can do so by using the +If you want to invalidate a token immediately, you can do so by using the <>. @@ -59,9 +59,9 @@ for machine to machine communication and is not suitable or designed for the self-service user creation of tokens. It generates only access tokens that cannot be refreshed. The premise is that the entity that uses `client_credentials` has constant access to a set of (client, not end-user) -credentials and can authenticate itself at will. +credentials and can authenticate itself at will. -`_kerberos`::: +`_kerberos`::: This grant type is supported internally and implements SPNEGO based Kerberos support. The `_kerberos` grant type may change from version to version. @@ -88,7 +88,7 @@ grant type. with any other supported grant type. `refresh_token`:: -(Optional^*^, string) The string that was returned when you created the token, +(Optional^*^, string) The string that was returned when you created the token, which enables you to extend its life. If you specify the `refresh_token` grant type, this parameter is required. This parameter is not valid with any other supported grant type. @@ -98,7 +98,7 @@ supported grant type. `FULL` regardless of the value sent with the request. `username`:: -(Optional^*^, string) The username that identifies the user. If you specify the `password` +(Optional^*^, string) The username that identifies the user. If you specify the `password` grant type, this parameter is required. This parameter is not valid with any other supported grant type. @@ -124,7 +124,26 @@ seconds) that the token expires in, and the type: { "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", "type" : "Bearer", - "expires_in" : 1200 + "expires_in" : 1200, + "authentication" : { + "username" : "test_admin", + "roles" : [ + "superuser" + ], + "full_name" : null, + "email" : null, + "metadata" : { }, + "enabled" : true, + "authentication_realm" : { + "name" : "file", + "type" : "file" + }, + "lookup_realm" : { + "name" : "file", + "type" : "file" + }, + "authentication_type" : "realm" + } } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] @@ -161,7 +180,26 @@ seconds) that the token expires in, the type, and the refresh token: "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", "type" : "Bearer", "expires_in" : 1200, - "refresh_token": "vLBPvmAB6KvwvJZr27cS" + "refresh_token": "vLBPvmAB6KvwvJZr27cS", + "authentication" : { + "username" : "test_admin", + "roles" : [ + "superuser" + ], + "full_name" : null, + "email" : null, + "metadata" : { }, + "enabled" : true, + "authentication_realm" : { + "name" : "file", + "type" : "file" + }, + "lookup_realm" : { + "name" : "file", + "type" : "file" + }, + "authentication_type" : "realm" + } } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] @@ -183,7 +221,7 @@ POST /_security/oauth2/token // TEST[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/] // TEST[continued] -The API will return a new token and refresh token. Each refresh token may only +The API will return a new token and refresh token. Each refresh token may only be used one time. [source,console-result] @@ -192,7 +230,26 @@ be used one time. "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", "type" : "Bearer", "expires_in" : 1200, - "refresh_token": "vLBPvmAB6KvwvJZr27cS" + "refresh_token": "vLBPvmAB6KvwvJZr27cS", + "authentication" : { + "username" : "test_admin", + "roles" : [ + "superuser" + ], + "full_name" : null, + "email" : null, + "metadata" : { }, + "enabled" : true, + "authentication_realm" : { + "name" : "file", + "type" : "file" + }, + "lookup_realm" : { + "name" : "file", + "type" : "file" + }, + "authentication_type" : "token" + } } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] @@ -223,7 +280,26 @@ Each refresh token may only be used one time. When the mutual authentication is "type" : "Bearer", "expires_in" : 1200, "refresh_token": "vLBPvmAB6KvwvJZr27cS" - "kerberos_authentication_response_token": "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg" + "kerberos_authentication_response_token": "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg", + "authentication" : { + "username" : "test_admin", + "roles" : [ + "superuser" + ], + "full_name" : null, + "email" : null, + "metadata" : { }, + "enabled" : true, + "authentication_realm" : { + "name" : "file", + "type" : "file" + }, + "lookup_realm" : { + "name" : "file", + "type" : "file" + }, + "authentication_type" : "realm" + } } -------------------------------------------------- -// NOTCONSOLE \ No newline at end of file +// NOTCONSOLE diff --git a/x-pack/docs/en/rest-api/security/invalidate-tokens.asciidoc b/x-pack/docs/en/rest-api/security/invalidate-tokens.asciidoc index d4da53ebbe5c4..15c31255fc461 100644 --- a/x-pack/docs/en/rest-api/security/invalidate-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/invalidate-tokens.asciidoc @@ -16,9 +16,9 @@ Invalidates one or more access tokens or refresh tokens. ==== {api-description-title} The access tokens returned by the <> have a -finite period of time for which they are valid and after that time period, they -can no longer be used. That time period is defined by the -`xpack.security.authc.token.timeout` setting. For more information, see +finite period of time for which they are valid and after that time period, they +can no longer be used. That time period is defined by the +`xpack.security.authc.token.timeout` setting. For more information, see <>. The refresh tokens returned by the <> are @@ -83,7 +83,26 @@ The get token API returns the following information about the access token: { "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", "type" : "Bearer", - "expires_in" : 1200 + "expires_in" : 1200, + "authentication" : { + "username" : "test_admin", + "roles" : [ + "superuser" + ], + "full_name" : null, + "email" : null, + "metadata" : { }, + "enabled" : true, + "authentication_realm" : { + "name" : "file", + "type" : "file" + }, + "lookup_realm" : { + "name" : "file", + "type" : "file" + }, + "authentication_type" : "realm" + } } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] @@ -122,13 +141,32 @@ The get token API returns the following information: "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", "type" : "Bearer", "expires_in" : 1200, - "refresh_token": "vLBPvmAB6KvwvJZr27cS" + "refresh_token": "vLBPvmAB6KvwvJZr27cS", + "authentication" : { + "username" : "test_admin", + "roles" : [ + "superuser" + ], + "full_name" : null, + "email" : null, + "metadata" : { }, + "enabled" : true, + "authentication_realm" : { + "name" : "file", + "type" : "file" + }, + "lookup_realm" : { + "name" : "file", + "type" : "file" + }, + "authentication_type" : "realm" + } } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] // TESTRESPONSE[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/] -The refresh token can now also be immediately invalidated as shown +The refresh token can now also be immediately invalidated as shown in the following example: [source,console] diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/DelegatePkiAuthenticationResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/DelegatePkiAuthenticationResponse.java index 4335d8f1cc667..8f21397be01a6 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/DelegatePkiAuthenticationResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/DelegatePkiAuthenticationResponse.java @@ -6,14 +6,15 @@ package org.elasticsearch.xpack.core.security.action; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.xpack.core.security.authc.Authentication; import java.io.IOException; import java.util.Objects; @@ -26,39 +27,28 @@ public final class DelegatePkiAuthenticationResponse extends ActionResponse impl private static final ParseField ACCESS_TOKEN_FIELD = new ParseField("access_token"); private static final ParseField TYPE_FIELD = new ParseField("type"); private static final ParseField EXPIRES_IN_FIELD = new ParseField("expires_in"); - - public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( - "delegate_pki_response", true, a -> { - final String accessToken = (String) a[0]; - final String type = (String) a[1]; - if (false == "Bearer".equals(type)) { - throw new IllegalArgumentException("Unknown token type [" + type + "], only [Bearer] type permitted"); - } - final Long expiresIn = (Long) a[2]; - return new DelegatePkiAuthenticationResponse(accessToken, TimeValue.timeValueSeconds(expiresIn)); - }); - - static { - PARSER.declareString(ConstructingObjectParser.constructorArg(), ACCESS_TOKEN_FIELD); - PARSER.declareString(ConstructingObjectParser.constructorArg(), TYPE_FIELD); - PARSER.declareLong(ConstructingObjectParser.constructorArg(), EXPIRES_IN_FIELD); - } + private static final ParseField AUTHENTICATION = new ParseField("authentication"); private String accessToken; private TimeValue expiresIn; + private Authentication authentication; DelegatePkiAuthenticationResponse() { } - public DelegatePkiAuthenticationResponse(String accessToken, TimeValue expiresIn) { + public DelegatePkiAuthenticationResponse(String accessToken, TimeValue expiresIn, Authentication authentication) { this.accessToken = Objects.requireNonNull(accessToken); // always store expiration in seconds because this is how we "serialize" to JSON and we need to parse back this.expiresIn = TimeValue.timeValueSeconds(Objects.requireNonNull(expiresIn).getSeconds()); + this.authentication = authentication; } public DelegatePkiAuthenticationResponse(StreamInput input) throws IOException { super(input); accessToken = input.readString(); expiresIn = input.readTimeValue(); + if (input.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication = new Authentication(input); + } } public String getAccessToken() { @@ -69,10 +59,17 @@ public TimeValue getExpiresIn() { return expiresIn; } + public Authentication getAuthentication() { + return authentication; + } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(accessToken); out.writeTimeValue(expiresIn); + if (out.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication.writeTo(out); + } } @Override @@ -81,20 +78,24 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; DelegatePkiAuthenticationResponse that = (DelegatePkiAuthenticationResponse) o; return Objects.equals(accessToken, that.accessToken) && - Objects.equals(expiresIn, that.expiresIn); + Objects.equals(expiresIn, that.expiresIn) && + Objects.equals(authentication, that.authentication); } @Override public int hashCode() { - return Objects.hash(accessToken, expiresIn); + return Objects.hash(accessToken, expiresIn, authentication); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject() - .field(ACCESS_TOKEN_FIELD.getPreferredName(), accessToken) - .field(TYPE_FIELD.getPreferredName(), "Bearer") - .field(EXPIRES_IN_FIELD.getPreferredName(), expiresIn.getSeconds()); + builder.startObject(); + builder.field(ACCESS_TOKEN_FIELD.getPreferredName(), accessToken); + builder.field(TYPE_FIELD.getPreferredName(), "Bearer"); + builder.field(EXPIRES_IN_FIELD.getPreferredName(), expiresIn.getSeconds()); + if (authentication != null) { + builder.field(AUTHENTICATION.getPreferredName(), authentication); + } return builder.endObject(); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectAuthenticateResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectAuthenticateResponse.java index 9598d619eed91..ae7e1eeaf9158 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectAuthenticateResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectAuthenticateResponse.java @@ -5,10 +5,12 @@ */ package org.elasticsearch.xpack.core.security.action.oidc; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.xpack.core.security.authc.Authentication; import java.io.IOException; @@ -17,12 +19,15 @@ public class OpenIdConnectAuthenticateResponse extends ActionResponse { private String accessTokenString; private String refreshTokenString; private TimeValue expiresIn; + private Authentication authentication; - public OpenIdConnectAuthenticateResponse(String principal, String accessTokenString, String refreshTokenString, TimeValue expiresIn) { - this.principal = principal; + public OpenIdConnectAuthenticateResponse(Authentication authentication, String accessTokenString, String refreshTokenString, + TimeValue expiresIn) { + this.principal = authentication.getUser().principal();; this.accessTokenString = accessTokenString; this.refreshTokenString = refreshTokenString; this.expiresIn = expiresIn; + this.authentication = authentication; } public OpenIdConnectAuthenticateResponse(StreamInput in) throws IOException { @@ -31,6 +36,9 @@ public OpenIdConnectAuthenticateResponse(StreamInput in) throws IOException { accessTokenString = in.readString(); refreshTokenString = in.readString(); expiresIn = in.readTimeValue(); + if (in.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication = new Authentication(in); + } } public String getPrincipal() { @@ -49,11 +57,16 @@ public TimeValue getExpiresIn() { return expiresIn; } + public Authentication getAuthentication() { return authentication; } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(principal); out.writeString(accessTokenString); out.writeString(refreshTokenString); out.writeTimeValue(expiresIn); + if (out.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication.writeTo(out); + } } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/saml/SamlAuthenticateResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/saml/SamlAuthenticateResponse.java index ff1edfca2dc87..755d02c394766 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/saml/SamlAuthenticateResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/saml/SamlAuthenticateResponse.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.xpack.core.security.authc.Authentication; import java.io.IOException; @@ -24,6 +25,7 @@ public final class SamlAuthenticateResponse extends ActionResponse { private String refreshToken; private String realm; private TimeValue expiresIn; + private Authentication authentication; public SamlAuthenticateResponse(StreamInput in) throws IOException { super(in); @@ -34,14 +36,18 @@ public SamlAuthenticateResponse(StreamInput in) throws IOException { tokenString = in.readString(); refreshToken = in.readString(); expiresIn = in.readTimeValue(); + if (in.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication = new Authentication(in); + } } - public SamlAuthenticateResponse(String principal, String realm, String tokenString, String refreshToken, TimeValue expiresIn) { - this.principal = principal; - this.realm = realm; + public SamlAuthenticateResponse(Authentication authentication, String tokenString, String refreshToken, TimeValue expiresIn) { + this.principal = authentication.getUser().principal(); + this.realm = authentication.getAuthenticatedBy().getName(); this.tokenString = tokenString; this.refreshToken = refreshToken; this.expiresIn = expiresIn; + this.authentication = authentication; } public String getPrincipal() { @@ -64,6 +70,8 @@ public TimeValue getExpiresIn() { return expiresIn; } + public Authentication getAuthentication() { return authentication; } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(principal); @@ -73,6 +81,9 @@ public void writeTo(StreamOutput out) throws IOException { out.writeString(tokenString); out.writeString(refreshToken); out.writeTimeValue(expiresIn); + if (out.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication.writeTo(out); + } } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java index efcf0a19e208a..2b858680d71d3 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.xpack.core.security.authc.Authentication; import java.io.IOException; import java.util.Objects; @@ -28,6 +29,7 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont private String scope; private String refreshToken; private String kerberosAuthenticationResponseToken; + private Authentication authentication; CreateTokenResponse() {} @@ -42,15 +44,19 @@ public CreateTokenResponse(StreamInput in) throws IOException { refreshToken = in.readString(); } kerberosAuthenticationResponseToken = in.readOptionalString(); + if (in.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication = new Authentication(in); + } } public CreateTokenResponse(String tokenString, TimeValue expiresIn, String scope, String refreshToken, - String kerberosAuthenticationResponseToken) { + String kerberosAuthenticationResponseToken, Authentication authentication) { this.tokenString = Objects.requireNonNull(tokenString); this.expiresIn = Objects.requireNonNull(expiresIn); this.scope = scope; this.refreshToken = refreshToken; this.kerberosAuthenticationResponseToken = kerberosAuthenticationResponseToken; + this.authentication = authentication; } public String getTokenString() { @@ -73,6 +79,8 @@ public String getKerberosAuthenticationResponseToken() { return kerberosAuthenticationResponseToken; } + public Authentication getAuthentication() { return authentication; } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(tokenString); @@ -88,6 +96,9 @@ public void writeTo(StreamOutput out) throws IOException { } } out.writeOptionalString(kerberosAuthenticationResponseToken); + if (out.getVersion().onOrAfter(Version.V_7_11_0)) { + authentication.writeTo(out); + } } @Override @@ -106,6 +117,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (kerberosAuthenticationResponseToken != null) { builder.field("kerberos_authentication_response_token", kerberosAuthenticationResponseToken); } + if (authentication != null) { + builder.field("authentication", authentication); + } return builder.endObject(); } @@ -118,11 +132,13 @@ public boolean equals(Object o) { Objects.equals(expiresIn, that.expiresIn) && Objects.equals(scope, that.scope) && Objects.equals(refreshToken, that.refreshToken) && - Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken); + Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken) && + Objects.equals(authentication, that.authentication); } @Override public int hashCode() { - return Objects.hash(tokenString, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken); + return Objects.hash(tokenString, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken, + authentication); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/DelegatePkiAuthenticationResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/DelegatePkiAuthenticationResponseTests.java index 362068053b71c..cbd1365a1c369 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/DelegatePkiAuthenticationResponseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/DelegatePkiAuthenticationResponseTests.java @@ -6,15 +6,26 @@ package org.elasticsearch.xpack.core.action; +import org.elasticsearch.Version; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractXContentTestCase; import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.User; import java.io.IOException; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; import static org.hamcrest.Matchers.is; public class DelegatePkiAuthenticationResponseTests extends AbstractXContentTestCase { @@ -27,6 +38,7 @@ public void testSerialization() throws Exception { DelegatePkiAuthenticationResponse serialized = new DelegatePkiAuthenticationResponse(input); assertThat(response.getAccessToken(), is(serialized.getAccessToken())); assertThat(response.getExpiresIn(), is(serialized.getExpiresIn())); + assertThat(response.getAuthentication(), is(serialized.getAuthentication())); assertThat(response, is(serialized)); } } @@ -35,16 +47,97 @@ public void testSerialization() throws Exception { @Override protected DelegatePkiAuthenticationResponse createTestInstance() { return new DelegatePkiAuthenticationResponse(randomAlphaOfLengthBetween(0, 10), - TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn")); + TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn"), + createAuthentication()); } @Override protected DelegatePkiAuthenticationResponse doParseInstance(XContentParser parser) throws IOException { - return DelegatePkiAuthenticationResponse.PARSER.apply(parser, null); + return DelegatePkiAuthenticationResponseTests.PARSER.apply(parser, null); } @Override protected boolean supportsUnknownFields() { - return true; + return false; + } + + private static final ParseField ACCESS_TOKEN_FIELD = new ParseField("access_token"); + private static final ParseField TYPE_FIELD = new ParseField("type"); + private static final ParseField EXPIRES_IN_FIELD = new ParseField("expires_in"); + private static final ParseField AUTHENTICATION = new ParseField("authentication"); + + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "delegate_pki_response", true, a -> { + final String accessToken = (String) a[0]; + final String type = (String) a[1]; + if (false == "Bearer".equals(type)) { + throw new IllegalArgumentException("Unknown token type [" + type + "], only [Bearer] type permitted"); + } + final Long expiresIn = (Long) a[2]; + final Authentication authentication = (Authentication) a[3]; + + return new DelegatePkiAuthenticationResponse(accessToken, TimeValue.timeValueSeconds(expiresIn), authentication); + }); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), ACCESS_TOKEN_FIELD); + PARSER.declareString(ConstructingObjectParser.constructorArg(), TYPE_FIELD); + PARSER.declareLong(ConstructingObjectParser.constructorArg(), EXPIRES_IN_FIELD); + PARSER.declareObject(optionalConstructorArg(), (p, c) -> parseAuthentication(p), AUTHENTICATION); + } + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser AUTH_PARSER = new ConstructingObjectParser<>( + "authentication", true, + a -> new Authentication(new User((String) a[0], ((ArrayList) a[1]).toArray(new String[0]), (String) a[2], (String) a[3], + (Map) a[4], (boolean) a[5]), (Authentication.RealmRef) a[6], (Authentication.RealmRef) a[7], Version.CURRENT, + Authentication.AuthenticationType.valueOf(a[8].toString().toUpperCase(Locale.ROOT)), (Map) a[4])); + static { + final ConstructingObjectParser realmInfoParser = new ConstructingObjectParser<>("realm_info", true, + a -> new Authentication.RealmRef((String) a[0], (String) a[1], "node_name")); + realmInfoParser.declareString(ConstructingObjectParser.constructorArg(), User.Fields.REALM_NAME); + realmInfoParser.declareString(ConstructingObjectParser.constructorArg(), User.Fields.REALM_TYPE); + AUTH_PARSER.declareString(ConstructingObjectParser.constructorArg(), User.Fields.USERNAME); + AUTH_PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), User.Fields.ROLES); + AUTH_PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), User.Fields.FULL_NAME); + AUTH_PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), User.Fields.EMAIL); + AUTH_PARSER.declareObject(ConstructingObjectParser.constructorArg(), (parser, c) -> parser.map(), User.Fields.METADATA); + AUTH_PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), User.Fields.ENABLED); + AUTH_PARSER.declareObject(ConstructingObjectParser.constructorArg(), realmInfoParser, User.Fields.AUTHENTICATION_REALM); + AUTH_PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), realmInfoParser, User.Fields.LOOKUP_REALM); + AUTH_PARSER.declareString(ConstructingObjectParser.constructorArg(), User.Fields.AUTHENTICATION_TYPE); + } + + public static Authentication parseAuthentication(final XContentParser parser) throws IOException { + return AUTH_PARSER.apply(parser, null); + } + + public static Authentication createAuthentication() { + final String username = randomAlphaOfLengthBetween(1, 4); + final String[] roles = generateRandomStringArray(4, 4, false, true); + final Map metadata; + metadata = new HashMap<>(); + if (randomBoolean()) { + metadata.put("string", null); + } else { + metadata.put("string", randomAlphaOfLengthBetween(0, 4)); + } + if (randomBoolean()) { + metadata.put("string_list", null); + } else { + metadata.put("string_list", Arrays.asList(generateRandomStringArray(4, 4, false, true))); + } + final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final String authenticationRealmName = randomAlphaOfLength(5); + final String authenticationRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos"); + final String lookupRealmName = randomAlphaOfLength(5); + final String lookupRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos"); + final String nodeName = "node_name"; + final Authentication.AuthenticationType authenticationType = randomFrom(Authentication.AuthenticationType.values()); + return new Authentication( + new User(username, roles, fullName, email, metadata, true), + new Authentication.RealmRef(authenticationRealmName, authenticationRealmType, nodeName), + new Authentication.RealmRef(lookupRealmName, lookupRealmType, nodeName), Version.CURRENT, authenticationType, metadata); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java index 764bbf66777bc..af8fdc30fcc08 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java @@ -11,12 +11,16 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.VersionUtils; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.User; public class CreateTokenResponseTests extends ESTestCase { public void testSerialization() throws Exception { CreateTokenResponse response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L), - randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10), randomBoolean() ? null :randomAlphaOfLengthBetween(1, 10)); + randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10), randomBoolean() ? null :randomAlphaOfLengthBetween(1, 10), + new Authentication(new User("joe", new String[]{"custom_superuser"}, new User("bar", "not_superuser")), + new Authentication.RealmRef("test", "test", "node"), new Authentication.RealmRef("test", "test", "node"))); try (BytesStreamOutput output = new BytesStreamOutput()) { response.writeTo(output); try (StreamInput input = output.bytes().streamInput()) { @@ -26,7 +30,9 @@ public void testSerialization() throws Exception { } response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L), - randomBoolean() ? null : "FULL", null, null); + randomBoolean() ? null : "FULL", null, null, + new Authentication(new User("joe", new String[]{"custom_superuser"}, new User("bar", "not_superuser")), + new Authentication.RealmRef("test", "test", "node"), new Authentication.RealmRef("test", "test", "node"))); try (BytesStreamOutput output = new BytesStreamOutput()) { response.writeTo(output); try (StreamInput input = output.bytes().streamInput()) { @@ -38,7 +44,10 @@ public void testSerialization() throws Exception { public void testSerializationToPre62Version() throws Exception { CreateTokenResponse response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L), - randomBoolean() ? null : "FULL", randomBoolean() ? null : randomAlphaOfLengthBetween(1, 10), null); + randomBoolean() ? null : "FULL", randomBoolean() ? null : randomAlphaOfLengthBetween(1, 10), null, + new Authentication(new User("joe", new String[]{"custom_superuser"}, new User("bar", "not_superuser")), + new Authentication.RealmRef("test", "test", "node"), + new Authentication.RealmRef("test", "test", "node"))); final Version version = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.V_6_1_4); try (BytesStreamOutput output = new BytesStreamOutput()) { output.setVersion(version); @@ -56,7 +65,7 @@ public void testSerializationToPre62Version() throws Exception { public void testSerializationToPost62Pre65Version() throws Exception { CreateTokenResponse response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L), - randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10), null); + randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10), null, null); final Version version = VersionUtils.randomVersionBetween(random(), Version.V_6_2_0, Version.V_6_4_0); try (BytesStreamOutput output = new BytesStreamOutput()) { output.setVersion(version); @@ -70,7 +79,10 @@ public void testSerializationToPost62Pre65Version() throws Exception { // no refresh token response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L), - randomBoolean() ? null : "FULL", null, null); + randomBoolean() ? null : "FULL", null, null, new Authentication( + new User("joe", new String[]{"custom_superuser"}, new User("bar", "not_superuser")), + new Authentication.RealmRef("test", "test", "node"), + new Authentication.RealmRef("test", "test", "node"))); try (BytesStreamOutput output = new BytesStreamOutput()) { output.setVersion(version); response.writeTo(output); diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java index 8b18e7b86ef09..d9fe11f8a7204 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java @@ -114,6 +114,7 @@ public void testTokenServiceBootstrapOnNodeJoin() throws Exception { PlainActionFuture userTokenFuture = new PlainActionFuture<>(); tokenService.decodeToken(response.getTokenString(), userTokenFuture); assertNotNull(userTokenFuture.actionGet()); + assertNotNull(response.getAuthentication()); } @@ -147,6 +148,8 @@ public void testTokenServiceCanRotateKeys() throws Exception { assertNotNull(userTokenFuture.actionGet()); assertNotEquals(activeKeyHash, tokenService.getActiveKeyHash()); } + assertNotNull(response.getAuthentication()); + assertEquals(SecuritySettingsSource.TEST_USER_NAME, response.getAuthentication().getUser().principal()); } public void testExpiredTokensDeletedAfterExpiration() throws Exception { @@ -468,6 +471,7 @@ public void testRefreshingToken() { assertNoTimeout(client().filterWithHeader(Collections.singletonMap("Authorization", "Bearer " + refreshResponse.getTokenString())) .admin().cluster().prepareHealth().get()); + assertNotNull(refreshResponse.getAuthentication()); } public void testRefreshingInvalidatedToken() { diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthDelegationIntegTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthDelegationIntegTests.java index 757768adf1696..6ab5371030ca2 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthDelegationIntegTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthDelegationIntegTests.java @@ -149,6 +149,9 @@ public void testDelegateThenAuthenticate() throws Exception { optionsBuilder.build()); String token = delegatePkiResponse.getAccessToken(); assertThat(token, is(notNullValue())); + assertNotNull(delegatePkiResponse.getAuthentication()); + assertEquals("Elasticsearch Test Client", delegatePkiResponse.getAuthentication().getUser().getUsername()); + // authenticate optionsBuilder = RequestOptions.DEFAULT.toBuilder(); optionsBuilder.addHeader("Authorization", "Bearer " + token); @@ -187,6 +190,7 @@ public void testTokenInvalidate() throws Exception { optionsBuilder.build()); String token = delegatePkiResponse.getAccessToken(); assertThat(token, is(notNullValue())); + assertNotNull(delegatePkiResponse.getAuthentication()); // authenticate optionsBuilder = RequestOptions.DEFAULT.toBuilder(); optionsBuilder.addHeader("Authorization", "Bearer " + token); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java index da73d117df598..b510b9351c4cd 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java @@ -84,7 +84,7 @@ protected void doExecute(Task task, DelegatePkiAuthenticationRequest request, tokenService.createOAuth2Tokens(authentication, delegateeAuthentication, Collections.emptyMap(), false, ActionListener.wrap(tuple -> { final TimeValue expiresIn = tokenService.getExpirationDelay(); - listener.onResponse(new DelegatePkiAuthenticationResponse(tuple.v1(), expiresIn)); + listener.onResponse(new DelegatePkiAuthenticationResponse(tuple.v1(), expiresIn, authentication)); }, listener::onFailure)); }, e -> { logger.debug((Supplier) () -> new ParameterizedMessage("Delegated x509Token [{}] could not be authenticated", diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java index 92ba783e532ae..54a5dde88b325 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java @@ -74,8 +74,7 @@ protected void doExecute(Task task, OpenIdConnectAuthenticateRequest request, tokenService.createOAuth2Tokens(authentication, originatingAuthentication, tokenMetadata, true, ActionListener.wrap(tuple -> { final TimeValue expiresIn = tokenService.getExpirationDelay(); - listener.onResponse(new OpenIdConnectAuthenticateResponse(authentication.getUser().principal(), tuple.v1(), - tuple.v2(), expiresIn)); + listener.onResponse(new OpenIdConnectAuthenticateResponse(authentication, tuple.v1(), tuple.v2(), expiresIn)); }, listener::onFailure)); }, e -> { logger.debug(() -> new ParameterizedMessage("OpenIDConnectToken [{}] could not be authenticated", token), e); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java index 647af62e25373..a9bb650413fda 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java @@ -68,8 +68,7 @@ protected void doExecute(Task task, SamlAuthenticateRequest request, ActionListe tokenMeta, true, ActionListener.wrap(tuple -> { final TimeValue expiresIn = tokenService.getExpirationDelay(); listener.onResponse( - new SamlAuthenticateResponse(authentication.getUser().principal(), - authentication.getAuthenticatedBy().getName(), tuple.v1(), tuple.v2(), expiresIn)); + new SamlAuthenticateResponse(authentication, tuple.v1(), tuple.v2(), expiresIn)); }, listener::onFailure)); }, e -> { logger.debug(() -> new ParameterizedMessage("SamlToken [{}] could not be authenticated", saml), e); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java index a2172c5b2e684..ff0cbe925a0f0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java @@ -135,7 +135,7 @@ private void createToken(GrantType grantType, CreateTokenRequest request, Authen final String scope = getResponseScopeValue(request.getScope()); final String base64AuthenticateResponse = (grantType == GrantType.KERBEROS) ? extractOutToken() : null; final CreateTokenResponse response = new CreateTokenResponse(tuple.v1(), tokenService.getExpirationDelay(), scope, - tuple.v2(), base64AuthenticateResponse); + tuple.v2(), base64AuthenticateResponse, authentication); listener.onResponse(response); }, listener::onFailure)); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java index 9a99e6020ea36..da7c31a3fc8e5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java @@ -9,6 +9,7 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.tasks.Task; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; @@ -32,9 +33,11 @@ public TransportRefreshTokenAction(TransportService transportService, ActionFilt protected void doExecute(Task task, CreateTokenRequest request, ActionListener listener) { tokenService.refreshToken(request.getRefreshToken(), ActionListener.wrap(tuple -> { final String scope = getResponseScopeValue(request.getScope()); - final CreateTokenResponse response = - new CreateTokenResponse(tuple.v1(), tokenService.getExpirationDelay(), scope, tuple.v2(), null); - listener.onResponse(response); + tokenService.authenticateToken(new SecureString(tuple.v1()), ActionListener.wrap(authentication -> { + listener.onResponse(new CreateTokenResponse(tuple.v1(), tokenService.getExpirationDelay(), scope, tuple.v2(), null, + authentication)); + }, + listener::onFailure)); }, listener::onFailure)); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oidc/RestOpenIdConnectAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oidc/RestOpenIdConnectAuthenticateAction.java index 0148ff0e107dc..5d3816c27c05d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oidc/RestOpenIdConnectAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oidc/RestOpenIdConnectAuthenticateAction.java @@ -67,12 +67,15 @@ protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClien @Override public RestResponse buildResponse(OpenIdConnectAuthenticateResponse response, XContentBuilder builder) throws Exception { - builder.startObject() - .field("username", response.getPrincipal()) - .field("access_token", response.getAccessTokenString()) - .field("refresh_token", response.getRefreshTokenString()) - .field("expires_in", response.getExpiresIn().seconds()) - .endObject(); + builder.startObject(); + builder.field("username", response.getPrincipal()); + builder.field("access_token", response.getAccessTokenString()); + builder.field("refresh_token", response.getRefreshTokenString()); + builder.field("expires_in", response.getExpiresIn().seconds()); + if(response.getAuthentication() != null) { + builder.field("authentication", response.getAuthentication()); + } + builder.endObject(); return new BytesRestResponse(RestStatus.OK, builder); } }); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/saml/RestSamlAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/saml/RestSamlAuthenticateAction.java index f251c651ddb4d..f79f038823976 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/saml/RestSamlAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/saml/RestSamlAuthenticateAction.java @@ -98,13 +98,16 @@ public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient c requestBuilder.execute(new RestBuilderListener(channel) { @Override public RestResponse buildResponse(SamlAuthenticateResponse response, XContentBuilder builder) throws Exception { - builder.startObject() - .field("username", response.getPrincipal()) - .field("realm", response.getRealm()) - .field("access_token", response.getTokenString()) - .field("refresh_token", response.getRefreshToken()) - .field("expires_in", response.getExpiresIn().seconds()) - .endObject(); + builder.startObject(); + builder.field("username", response.getPrincipal()); + builder.field("realm", response.getRealm()); + builder.field("access_token", response.getTokenString()); + builder.field("refresh_token", response.getRefreshToken()); + builder.field("expires_in", response.getExpiresIn().seconds()); + if(response.getAuthentication() != null) { + builder.field("authentication", response.getAuthentication()); + } + builder.endObject(); return new BytesRestResponse(RestStatus.OK, builder); } }); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index a3e875fec2c02..d426cfd936e3e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -23,7 +23,9 @@ import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.support.NoOpLogger; +import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.kerberos.KerberosAuthenticationToken; import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction.CreateTokenResponseActionListener; @@ -31,6 +33,7 @@ import java.util.Map; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; public class RestGetTokenActionTests extends ESTestCase { @@ -71,7 +74,9 @@ public void sendResponse(RestResponse restResponse) { CreateTokenResponseActionListener listener = new CreateTokenResponseActionListener(restChannel, restRequest, NoOpLogger.INSTANCE); CreateTokenResponse createTokenResponse = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 256), TimeValue.timeValueHours(1L), null, randomAlphaOfLength(4), - randomAlphaOfLength(5)); + randomAlphaOfLength(5), new Authentication(new User("joe", new String[]{"custom_superuser"}, + new User("bar", "not_superuser")), new Authentication.RealmRef("test", "test", "node"), + new Authentication.RealmRef("test", "test", "node"))); listener.onResponse(createTokenResponse); RestResponse response = responseSetOnce.get(); @@ -85,7 +90,10 @@ public void sendResponse(RestResponse restResponse) { assertThat(map, hasEntry("expires_in", Math.toIntExact(createTokenResponse.getExpiresIn().seconds()))); assertThat(map, hasEntry("refresh_token", createTokenResponse.getRefreshToken())); assertThat(map, hasEntry("kerberos_authentication_response_token", createTokenResponse.getKerberosAuthenticationResponseToken())); - assertEquals(5, map.size()); + assertThat(map, hasKey("authentication")); + assertThat((Map)(map.get("authentication")), + hasEntry("username", createTokenResponse.getAuthentication().getUser().principal())); + assertEquals(6, map.size()); } public void testSendResponseKerberosError() { diff --git a/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java b/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java index b2fd3907dbd00..094ba6ee5ecd7 100644 --- a/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java +++ b/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java @@ -420,6 +420,8 @@ private Tuple completeAuthentication(String redirectUri, String logger.info(" OpenIDConnect authentication response {}", responseBody); assertNotNull(responseBody.get("access_token")); assertNotNull(responseBody.get("refresh_token")); + assertNotNull(responseBody.get("authentication")); + assertEquals("alice", ((Map)responseBody.get("authentication")).get("username")); return Tuple.tuple(responseBody.get("access_token").toString(), responseBody.get("refresh_token").toString()); } } diff --git a/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java b/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java index 8c58576c6e0cc..6b1653922b718 100644 --- a/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java +++ b/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java @@ -204,6 +204,11 @@ private Tuple loginViaSaml(String realmName) throws Exception { assertThat(refreshToken, notNullValue()); assertThat(refreshToken, instanceOf(String.class)); + final Object authentication = result.get("authentication"); + assertThat(authentication, notNullValue()); + assertThat(authentication, instanceOf(Map.class)); + assertEquals("thor", ((Map)authentication).get("username")); + return new Tuple<>((String) accessToken, (String) refreshToken); } }