Skip to content

Commit

Permalink
Add realm information for Authenticate API (#35648)
Browse files Browse the repository at this point in the history
- Add the authentication realm and lookup realm name and type in the response for the _authenticate API
- The authentication realm is set as the lookup realm too (instead of setting the lookup realm to null or empty ) when no lookup realm is used.
  • Loading branch information
jkakavas authored Nov 27, 2018
1 parent fe603e9 commit 580b5ba
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,43 @@ 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 = new ParseField("authentication_realm");
static final ParseField LOOKUP_REALM = new ParseField("lookup_realm");
static final ParseField REALM_NAME = new ParseField("name");
static final ParseField REALM_TYPE = new ParseField("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], (RealmInfo) a[6], (RealmInfo) a[7]));
static {
final ConstructingObjectParser<RealmInfo, Void> realmInfoParser = new ConstructingObjectParser<>("realm_info",
a -> new RealmInfo((String) a[0], (String) a[1]));
realmInfoParser.declareString(constructorArg(), REALM_NAME);
realmInfoParser.declareString(constructorArg(), REALM_TYPE);
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.declareObject(constructorArg(), realmInfoParser, AUTHENTICATION_REALM);
PARSER.declareObject(constructorArg(), realmInfoParser, LOOKUP_REALM);
}

private final User user;
private final boolean enabled;
private final RealmInfo authenticationRealm;
private final RealmInfo lookupRealm;

public AuthenticateResponse(User user, boolean enabled) {

public AuthenticateResponse(User user, boolean enabled, RealmInfo authenticationRealm,
RealmInfo lookupRealm) {
this.user = user;
this.enabled = enabled;
this.authenticationRealm = authenticationRealm;
this.lookupRealm = lookupRealm;
}

/**
Expand All @@ -85,25 +101,69 @@ public boolean enabled() {
return enabled;
}

/**
* @return the realm that authenticated the user
*/
public RealmInfo getAuthenticationRealm() {
return authenticationRealm;
}

/**
* @return the realm where the user information was looked up
*/
public RealmInfo getLookupRealm() {
return lookupRealm;
}

@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(authenticationRealm, that.authenticationRealm) &&
Objects.equals(lookupRealm, that.lookupRealm);
}

@Override
public int hashCode() {
return Objects.hash(user, enabled);
return Objects.hash(user, enabled, authenticationRealm, lookupRealm);
}

public static AuthenticateResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

public static class RealmInfo {
private String name;
private String type;

RealmInfo(String name, String type) {
this.name = name;
this.type = type;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RealmInfo realmInfo = (RealmInfo) o;
return Objects.equals(name, realmInfo.name) &&
Objects.equals(type, realmInfo.type);
}

@Override
public int hashCode() {
return Objects.hash(name, type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,10 @@ public void testAuthenticate() throws Exception {
//tag::authenticate-response
User user = response.getUser(); // <1>
boolean enabled = response.enabled(); // <2>
final String authenticationRealmName = response.getAuthenticationRealm().getName(); // <3>
final String authenticationRealmType = response.getAuthenticationRealm().getType(); // <4>
final String lookupRealmName = response.getLookupRealm().getName(); // <5>
final String lookupRealmType = response.getLookupRealm().getType(); // <6>
//end::authenticate-response

assertThat(user.getUsername(), is("test_user"));
Expand All @@ -540,6 +544,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,14 @@ protected AuthenticateResponse createTestInstance() {
final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4));
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", "active_directory", "saml", "kerberos");
final String lookupRealmName = randomAlphaOfLength(5);
final String lookupRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos");
return new AuthenticateResponse(
new User(username, roles, metadata, fullName, email), enabled,
new AuthenticateResponse.RealmInfo(authenticationRealmName, authenticationRealmType),
new AuthenticateResponse.RealmInfo(lookupRealmName, lookupRealmType));
}

private void toXContent(AuthenticateResponse response, XContentBuilder builder) throws IOException {
Expand All @@ -87,41 +94,64 @@ private void toXContent(AuthenticateResponse response, XContentBuilder builder)
builder.field(AuthenticateResponse.EMAIL.getPreferredName(), user.getEmail());
}
builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled);
builder.startObject(AuthenticateResponse.AUTHENTICATION_REALM.getPreferredName());
builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), response.getAuthenticationRealm().getName());
builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), response.getAuthenticationRealm().getType());
builder.endObject();
builder.startObject(AuthenticateResponse.LOOKUP_REALM.getPreferredName());
builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), response.getLookupRealm().getName());
builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), response.getLookupRealm().getType());
builder.endObject();
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.getAuthenticationRealm(),
response.getLookupRealm());
}

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.getAuthenticationRealm(), response.getLookupRealm());
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.getAuthenticationRealm(),
response.getLookupRealm());
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.getAuthenticationRealm(),
response.getLookupRealm());
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.getAuthenticationRealm(), response.getLookupRealm());
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.getAuthenticationRealm(), response.getLookupRealm());
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.getAuthenticationRealm(),
response.getLookupRealm());
case 7:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5)));
case 8:
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5)), response.getLookupRealm());
}
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 four 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 field `authentication_realm`,
accessed with `getAuthenticationRealm` contains the name and type of the
Realm that has authenticated the user and the field `lookup_realm`,
accessed with `getLookupRealm` contains the name and type of the Realm where
the user information were retrieved from.

["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> `getAuthenticationRealm().getName()` retrieves the name of the realm that authenticated the user.
<4> `getAuthenticationRealm().getType()` retrieves the type of the realm that authenticated the user.
<5> `getLookupRealm().getName()` retrieves the name of the realm from where the user information is looked up.
<6> `getLookupRealm().getType()` retrieves the type of the realm from where the user information is looked up.

[id="{upid}-{api}-async"]
==== Asynchronous Execution
Expand Down
14 changes: 11 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,15 @@ 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",
"type" : "file"
},
"lookup_realm": {
"name" : "default_file",
"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_7_0_0)) {
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_7_0_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,31 @@ 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.startObject(User.Fields.AUTHENTICATION_REALM.getPreferredName());
builder.field(User.Fields.REALM_NAME.getPreferredName(), getAuthenticatedBy().getName());
builder.field(User.Fields.REALM_TYPE.getPreferredName(), getAuthenticatedBy().getType());
builder.endObject();
builder.startObject(User.Fields.LOOKUP_REALM.getPreferredName());
if (getLookedUpBy() != null) {
builder.field(User.Fields.REALM_NAME.getPreferredName(), getLookedUpBy().getName());
builder.field(User.Fields.REALM_TYPE.getPreferredName(), getLookedUpBy().getType());
} else {
builder.field(User.Fields.REALM_NAME.getPreferredName(), getAuthenticatedBy().getName());
builder.field(User.Fields.REALM_TYPE.getPreferredName(), getAuthenticatedBy().getType());
}
builder.endObject();
return builder.endObject();
}

public static class RealmRef {

private final String nodeName;
Expand Down
Loading

0 comments on commit 580b5ba

Please sign in to comment.