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 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 @@ -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));
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", "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