Skip to content
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

Add realm information for Authenticate API #35648

Merged
merged 15 commits into from
Nov 27, 2018
Merged
Show file tree
Hide file tree
Changes from 10 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 @@ -46,27 +46,44 @@ public final class AuthenticateResponse {
static final ParseField FULL_NAME = new ParseField("full_name");
static final ParseField EMAIL = new ParseField("email");
static final ParseField ENABLED = new ParseField("enabled");
static final ParseField AUTHENTICATION_REALM_NAME = new ParseField("authentication_realm_name");
static final ParseField AUTHENTICATION_REALM_TYPE = new ParseField("authentication_realm_type");
static final ParseField LOOKUP_REALM_NAME = new ParseField("lookup_realm_name");
static final ParseField LOOKUP_REALM_TYPE = new ParseField("lookup_realm_type");

@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<AuthenticateResponse, Void> PARSER = new ConstructingObjectParser<>(
"client_security_authenticate_response",
a -> new AuthenticateResponse(new User((String) a[0], ((List<String>) a[1]), (Map<String, Object>) a[2],
(String) a[3], (String) a[4]), (Boolean) a[5]));
(String) a[3], (String) a[4]), (Boolean) a[5], (String) a[6], (String) a[7], (String) a[8], (String) a[9]));
static {
PARSER.declareString(constructorArg(), USERNAME);
PARSER.declareStringArray(constructorArg(), ROLES);
PARSER.<Map<String, Object>>declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME);
PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL);
PARSER.declareBoolean(constructorArg(), ENABLED);
PARSER.declareString(constructorArg(), AUTHENTICATION_REALM_NAME);
PARSER.declareString(constructorArg(), AUTHENTICATION_REALM_TYPE);
PARSER.declareString(constructorArg(), LOOKUP_REALM_NAME);
PARSER.declareString(constructorArg(), LOOKUP_REALM_TYPE);
}

private final User user;
private final boolean enabled;
private final String authenticationRealmName;
private final String authenticationRealmType;
private final String lookupRealmName;
private final String lookupRealmType;

public AuthenticateResponse(User user, boolean enabled) {
public AuthenticateResponse(User user, boolean enabled, String authenticationRealmName, String authenticationRealmType,
String lookupRealmName, String lookupRealmType) {
this.user = user;
this.enabled = enabled;
this.authenticationRealmName = authenticationRealmName;
this.authenticationRealmType = authenticationRealmType;
this.lookupRealmName = lookupRealmName;
this.lookupRealmType = lookupRealmType;
}

/**
Expand All @@ -85,21 +102,50 @@ public boolean enabled() {
return enabled;
}

/**
* @return the name of the realm that authenticated the user
*/
public String getAuthenticationRealmName() {
return authenticationRealmName;
}

/**
* @return the type of the realm that authenticated the user
*/
public String getAuthenticationRealmType() {
return authenticationRealmType;
}

/**
* @return the name of the realm where the user information was looked up
*/
public String getLookupRealmName() {
return lookupRealmName;
}

/**
* @return the type of the realm where the user information was looked up
*/
public String getLookupRealmType() {
return lookupRealmType;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final AuthenticateResponse that = (AuthenticateResponse) o;
return user.equals(that.user) && enabled == that.enabled;
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthenticateResponse that = (AuthenticateResponse) o;
return enabled == that.enabled &&
Objects.equals(user, that.user) &&
Objects.equals(authenticationRealmName, that.authenticationRealmName) &&
Objects.equals(authenticationRealmType, that.authenticationRealmType) &&
Objects.equals(lookupRealmName, that.lookupRealmName) &&
Objects.equals(lookupRealmType, that.lookupRealmType);
}

@Override
public int hashCode() {
return Objects.hash(user, enabled);
return Objects.hash(user, enabled, authenticationRealmName, authenticationRealmType, lookupRealmName, lookupRealmType);
}

public static AuthenticateResponse fromXContent(XContentParser parser) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ public void testAuthenticate() throws Exception {
//tag::authenticate-response
User user = response.getUser(); // <1>
boolean enabled = response.enabled(); // <2>
final String authenticationRealmName = response.getAuthenticationRealmName(); // <3>
final String authenticationRealmType = response.getAuthenticationRealmType(); // <4>
final String lookupRealmName = response.getLookupRealmName(); // <5>
final String lookupRealmType = response.getLookupRealmType(); // <6>
//end::authenticate-response

assertThat(user.getUsername(), is("test_user"));
Expand All @@ -413,6 +417,10 @@ public void testAuthenticate() throws Exception {
assertThat(user.getEmail(), nullValue());
assertThat(user.getMetadata().isEmpty(), is(true));
assertThat(enabled, is(true));
assertThat(authenticationRealmName, is("default_file"));
assertThat(authenticationRealmType, is("file"));
assertThat(lookupRealmName, is("default_file"));
assertThat(lookupRealmType, is("file"));
}

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ protected AuthenticateResponse createTestInstance() {
final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4));
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4));
final boolean enabled = randomBoolean();
return new AuthenticateResponse(new User(username, roles, metadata, fullName, email), enabled);
final String authenticationRealmName = randomAlphaOfLength(5);
final String authenticationRealmType = randomFrom("file", "native", "ldap", "ad", "saml", "kerberos");
final String lookupRealmName = randomAlphaOfLength(5);
final String lookupRealmType = randomFrom("file", "native", "ldap", "ad", "saml", "kerberos");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's totally insignificant, but the actual AD realm type is active_directory.

return new AuthenticateResponse(
new User(username, roles, metadata, fullName, email), enabled, authenticationRealmName, authenticationRealmType,
lookupRealmName, lookupRealmType);
}

private void toXContent(AuthenticateResponse response, XContentBuilder builder) throws IOException {
Expand All @@ -87,41 +93,63 @@ private void toXContent(AuthenticateResponse response, XContentBuilder builder)
builder.field(AuthenticateResponse.EMAIL.getPreferredName(), user.getEmail());
}
builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled);
builder.field(AuthenticateResponse.AUTHENTICATION_REALM_NAME.getPreferredName(), response.getAuthenticationRealmName());
builder.field(AuthenticateResponse.AUTHENTICATION_REALM_TYPE.getPreferredName(), response.getAuthenticationRealmType());
builder.field(AuthenticateResponse.LOOKUP_REALM_NAME.getPreferredName(), response.getLookupRealmName());
builder.field(AuthenticateResponse.LOOKUP_REALM_TYPE.getPreferredName(), response.getLookupRealmType());
builder.endObject();
}

private AuthenticateResponse copy(AuthenticateResponse response) {
final User originalUser = response.getUser();
final User copyUser = new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail());
return new AuthenticateResponse(copyUser, response.enabled());
return new AuthenticateResponse(copyUser, response.enabled(), response.getAuthenticationRealmName(),
response.getAuthenticationRealmType(), response.getLookupRealmName(), response.getLookupRealmType());
}

private AuthenticateResponse mutate(AuthenticateResponse response) {
final User originalUser = response.getUser();
switch (randomIntBetween(1, 6)) {
switch (randomIntBetween(1, 8)) {
case 1:
return new AuthenticateResponse(new User(originalUser.getUsername() + "wrong", originalUser.getRoles(),
originalUser.getMetadata(), originalUser.getFullName(), originalUser.getEmail()), response.enabled());
originalUser.getMetadata(), originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
response.getAuthenticationRealmName(), response.getAuthenticationRealmType(), response.getLookupRealmName(),
response.getLookupRealmType());
case 2:
final Collection<String> wrongRoles = new ArrayList<>(originalUser.getRoles());
wrongRoles.add(randomAlphaOfLengthBetween(1, 4));
return new AuthenticateResponse(new User(originalUser.getUsername(), wrongRoles, originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail()), response.enabled());
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealmName(),
response.getAuthenticationRealmType(), response.getLookupRealmName(), response.getLookupRealmType());
case 3:
final Map<String, Object> wrongMetadata = new HashMap<>(originalUser.getMetadata());
wrongMetadata.put("wrong_string", randomAlphaOfLengthBetween(0, 4));
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), wrongMetadata,
originalUser.getFullName(), originalUser.getEmail()), response.enabled());
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealmName(),
response.getAuthenticationRealmType(), response.getLookupRealmName(), response.getLookupRealmType());
case 4:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled());
originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled(),
response.getAuthenticationRealmName(), response.getAuthenticationRealmType(), response.getLookupRealmName(),
response.getLookupRealmType());
case 5:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled());
originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled(),
response.getAuthenticationRealmName(), response.getAuthenticationRealmType(), response.getLookupRealmName(),
response.getLookupRealmType());
case 6:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail()), !response.enabled());
originalUser.getFullName(), originalUser.getEmail()), !response.enabled(), response.getAuthenticationRealmName(),
response.getAuthenticationRealmType(), response.getLookupRealmName(), response.getLookupRealmType());
case 7:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealmName(),
response.getAuthenticationRealmType(), randomAlphaOfLength(5), randomAlphaOfLength(5));
case 8:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), randomAlphaOfLength(5),
randomAlphaOfLength(5), response.getLookupRealmName(), response.getLookupRealmType());
}
throw new IllegalStateException("Bad random number");
}
Expand Down
14 changes: 11 additions & 3 deletions docs/java-rest/high-level/security/authenticate.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ This method does not require a request object. The client waits for the
[id="{upid}-{api}-response"]
==== Response

The returned +{response}+ contains two fields. Firstly, the `user` field
The returned +{response}+ contains six fields. The `user` field
, accessed with `getUser`, contains all the information about this
authenticated user. The other field, `enabled`, tells if this user is actually
usable or has been temporalily deactivated.
authenticated user. The field `enabled`, tells if this user is actually
usable or has been temporarily deactivated. The following four fields
`authenticationRealmName`, `authenticationRealmType`, `lookupRealmName`,
`lookupRealmType` correspond to the name and type of the Realm that has
authenticated the user and the name and type of the Realm where the user
information were retrieved from, respectively.

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
Expand All @@ -36,6 +40,10 @@ include-tagged::{doc-tests-file}[{api}-response]
<1> `getUser` retrieves the `User` instance containing the information,
see {javadoc-client}/security/user/User.html.
<2> `enabled` tells if this user is usable or is deactivated.
<3> `getAuthenticationRealmName` retrieves the name of the realm that authenticated the user.
<4> `getAuthenticationRealmType` retrieves the type of the realm that authenticated the user.
<5> `getLookupRealmName` retrieves the name of the realm from where the user information is looked up.
<6> `getLookupRealmType` retrieves the type of the realm from where the user information is looked up.

[id="{upid}-{api}-async"]
==== Asynchronous Execution
Expand Down
10 changes: 7 additions & 3 deletions x-pack/docs/en/rest-api/security/authenticate.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ authenticate a user and retrieve information about the authenticated user.

==== Description

A successful call returns a JSON structure that shows what roles are assigned
to the user as well as any assigned metadata.
A successful call returns a JSON structure that shows user information such as their username, the roles that are
assigned to the user, any assigned metadata, and information about the realms that authenticated and authorized the user.

If the user cannot be authenticated, this API returns a 401 status code.

Expand All @@ -41,7 +41,11 @@ The following example output provides information about the "rdeniro" user:
"full_name": null,
"email": null,
"metadata": { },
"enabled": true
"enabled": true,
"authentication_realm_name" : "default_file",
"authentication_realm_type" : "file",
"lookup_realm_name" : "default_file",
"lookup_realm_type" : "file"
}
--------------------------------------------------
// TESTRESPONSE[s/"rdeniro"/"$body.username"/]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,49 @@
*/
package org.elasticsearch.xpack.core.security.action.user;

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.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.user.User;

import java.io.IOException;

public class AuthenticateResponse extends ActionResponse {

private User user;
private Authentication authentication;

public AuthenticateResponse() {}

public AuthenticateResponse(User user) {
this.user = user;
public AuthenticateResponse(Authentication authentication){
this.authentication = authentication;
}

public User user() {
return user;
public Authentication authentication() {
return authentication;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
User.writeTo(user, out);
if (out.getVersion().before(Version.V_6_6_0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have to keep Version.V_7_0_0 and change on backport. I don't know how CI passed, it should've failed in the bwc tests. To not lose any extra time I think you can try to push to both branches simultaneously, Tom Cruise style. Squash locally and run the precommit tests on 6.x, then merge on master, and do a fast cherry-pick and push origin on the 6.x . 🤞

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll address this now and take advantage of CI split and the fact that Tim won't wake up for another 6 hours :)

User.writeTo(authentication.getUser(), out);
} else {
authentication.writeTo(out);
}
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
user = User.readFrom(in);
if (in.getVersion().before(Version.V_6_6_0)) {
final User user = User.readFrom(in);
final Authentication.RealmRef unknownRealm = new Authentication.RealmRef("__unknown", "__unknown", "__unknown");
authentication = new Authentication(user, unknownRealm, unknownRealm);
} else {
authentication = new Authentication(in);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.security.user.InternalUserSerializationHelper;
import org.elasticsearch.xpack.core.security.user.User;

Expand All @@ -20,7 +22,7 @@

// TODO(hub-cap) Clean this up after moving User over - This class can re-inherit its field AUTHENTICATION_KEY in AuthenticationField.
// That interface can be removed
public class Authentication {
public class Authentication implements ToXContentObject {

private final User user;
private final RealmRef authenticatedBy;
Expand Down Expand Up @@ -163,6 +165,27 @@ public int hashCode() {
return result;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(User.Fields.USERNAME.getPreferredName(), user.principal());
builder.array(User.Fields.ROLES.getPreferredName(), user.roles());
builder.field(User.Fields.FULL_NAME.getPreferredName(), user.fullName());
builder.field(User.Fields.EMAIL.getPreferredName(), user.email());
builder.field(User.Fields.METADATA.getPreferredName(), user.metadata());
builder.field(User.Fields.ENABLED.getPreferredName(), user.enabled());
builder.field(User.Fields.AUTHENTICATION_REALM_NAME.getPreferredName(), getAuthenticatedBy().getName());
builder.field(User.Fields.AUTHENTICATION_REALM_TYPE.getPreferredName(), getAuthenticatedBy().getType());
if (getLookedUpBy() != null) {
builder.field(User.Fields.LOOKUP_REALM_NAME.getPreferredName(), getLookedUpBy().getName());
builder.field(User.Fields.LOOKUP_REALM_TYPE.getPreferredName(), getLookedUpBy().getType());
} else {
builder.field(User.Fields.LOOKUP_REALM_NAME.getPreferredName(), getAuthenticatedBy().getName());
builder.field(User.Fields.LOOKUP_REALM_TYPE.getPreferredName(), getAuthenticatedBy().getType());
}
return builder.endObject();
}

public static class RealmRef {

private final String nodeName;
Expand Down
Loading