Skip to content

Adding authentication information to access token create APIs (#62490) #63841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -77,6 +79,8 @@ public String getKerberosAuthenticationResponseToken() {
return kerberosAuthenticationResponseToken;
}

public AuthenticateResponse getAuthentication() { return authentication; }

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -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<CreateTokenResponse, Void> 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"));
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -53,6 +56,8 @@ public TimeValue getExpiresIn() {
return expiresIn;
}

public AuthenticateResponse getAuthentication() { return authentication; }

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -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<DelegatePkiAuthenticationResponse, Void> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,6 +27,7 @@
import org.elasticsearch.test.ESTestCase;

import java.io.IOException;
import java.util.Arrays;

import static org.hamcrest.Matchers.equalTo;

Expand All @@ -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);
Expand All @@ -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);

Expand All @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<String, Object> 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));
}
}
4 changes: 3 additions & 1 deletion docs/java-rest/high-level/security/create-token.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
--------------------------------------------------
Expand Down Expand Up @@ -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
<2> Called in case of failure. The raised exception is provided as an argument
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
--------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/]
Loading