Skip to content

Commit b515ec7

Browse files
committed
Add realm information for Authenticate API (#35648)
- 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.
1 parent c847953 commit b515ec7

File tree

15 files changed

+222
-47
lines changed

15 files changed

+222
-47
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java

+71-11
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,43 @@ public final class AuthenticateResponse {
4646
static final ParseField FULL_NAME = new ParseField("full_name");
4747
static final ParseField EMAIL = new ParseField("email");
4848
static final ParseField ENABLED = new ParseField("enabled");
49+
static final ParseField AUTHENTICATION_REALM = new ParseField("authentication_realm");
50+
static final ParseField LOOKUP_REALM = new ParseField("lookup_realm");
51+
static final ParseField REALM_NAME = new ParseField("name");
52+
static final ParseField REALM_TYPE = new ParseField("type");
4953

5054
@SuppressWarnings("unchecked")
5155
private static final ConstructingObjectParser<AuthenticateResponse, Void> PARSER = new ConstructingObjectParser<>(
5256
"client_security_authenticate_response",
5357
a -> new AuthenticateResponse(new User((String) a[0], ((List<String>) a[1]), (Map<String, Object>) a[2],
54-
(String) a[3], (String) a[4]), (Boolean) a[5]));
58+
(String) a[3], (String) a[4]), (Boolean) a[5], (RealmInfo) a[6], (RealmInfo) a[7]));
5559
static {
60+
final ConstructingObjectParser<RealmInfo, Void> realmInfoParser = new ConstructingObjectParser<>("realm_info",
61+
a -> new RealmInfo((String) a[0], (String) a[1]));
62+
realmInfoParser.declareString(constructorArg(), REALM_NAME);
63+
realmInfoParser.declareString(constructorArg(), REALM_TYPE);
5664
PARSER.declareString(constructorArg(), USERNAME);
5765
PARSER.declareStringArray(constructorArg(), ROLES);
5866
PARSER.<Map<String, Object>>declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
5967
PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME);
6068
PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL);
6169
PARSER.declareBoolean(constructorArg(), ENABLED);
70+
PARSER.declareObject(constructorArg(), realmInfoParser, AUTHENTICATION_REALM);
71+
PARSER.declareObject(constructorArg(), realmInfoParser, LOOKUP_REALM);
6272
}
6373

6474
private final User user;
6575
private final boolean enabled;
76+
private final RealmInfo authenticationRealm;
77+
private final RealmInfo lookupRealm;
6678

67-
public AuthenticateResponse(User user, boolean enabled) {
79+
80+
public AuthenticateResponse(User user, boolean enabled, RealmInfo authenticationRealm,
81+
RealmInfo lookupRealm) {
6882
this.user = user;
6983
this.enabled = enabled;
84+
this.authenticationRealm = authenticationRealm;
85+
this.lookupRealm = lookupRealm;
7086
}
7187

7288
/**
@@ -85,25 +101,69 @@ public boolean enabled() {
85101
return enabled;
86102
}
87103

104+
/**
105+
* @return the realm that authenticated the user
106+
*/
107+
public RealmInfo getAuthenticationRealm() {
108+
return authenticationRealm;
109+
}
110+
111+
/**
112+
* @return the realm where the user information was looked up
113+
*/
114+
public RealmInfo getLookupRealm() {
115+
return lookupRealm;
116+
}
117+
88118
@Override
89119
public boolean equals(Object o) {
90-
if (this == o) {
91-
return true;
92-
}
93-
if (o == null || getClass() != o.getClass()) {
94-
return false;
95-
}
96-
final AuthenticateResponse that = (AuthenticateResponse) o;
97-
return user.equals(that.user) && enabled == that.enabled;
120+
if (this == o) return true;
121+
if (o == null || getClass() != o.getClass()) return false;
122+
AuthenticateResponse that = (AuthenticateResponse) o;
123+
return enabled == that.enabled &&
124+
Objects.equals(user, that.user) &&
125+
Objects.equals(authenticationRealm, that.authenticationRealm) &&
126+
Objects.equals(lookupRealm, that.lookupRealm);
98127
}
99128

100129
@Override
101130
public int hashCode() {
102-
return Objects.hash(user, enabled);
131+
return Objects.hash(user, enabled, authenticationRealm, lookupRealm);
103132
}
104133

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

138+
public static class RealmInfo {
139+
private String name;
140+
private String type;
141+
142+
RealmInfo(String name, String type) {
143+
this.name = name;
144+
this.type = type;
145+
}
146+
147+
public String getName() {
148+
return name;
149+
}
150+
151+
public String getType() {
152+
return type;
153+
}
154+
155+
@Override
156+
public boolean equals(Object o) {
157+
if (this == o) return true;
158+
if (o == null || getClass() != o.getClass()) return false;
159+
RealmInfo realmInfo = (RealmInfo) o;
160+
return Objects.equals(name, realmInfo.name) &&
161+
Objects.equals(type, realmInfo.type);
162+
}
163+
164+
@Override
165+
public int hashCode() {
166+
return Objects.hash(name, type);
167+
}
168+
}
109169
}

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

+8
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,10 @@ public void testAuthenticate() throws Exception {
595595
//tag::authenticate-response
596596
User user = response.getUser(); // <1>
597597
boolean enabled = response.enabled(); // <2>
598+
final String authenticationRealmName = response.getAuthenticationRealm().getName(); // <3>
599+
final String authenticationRealmType = response.getAuthenticationRealm().getType(); // <4>
600+
final String lookupRealmName = response.getLookupRealm().getName(); // <5>
601+
final String lookupRealmType = response.getLookupRealm().getType(); // <6>
598602
//end::authenticate-response
599603

600604
assertThat(user.getUsername(), is("test_user"));
@@ -603,6 +607,10 @@ public void testAuthenticate() throws Exception {
603607
assertThat(user.getEmail(), nullValue());
604608
assertThat(user.getMetadata().isEmpty(), is(true));
605609
assertThat(enabled, is(true));
610+
assertThat(authenticationRealmName, is("default_file"));
611+
assertThat(authenticationRealmType, is("file"));
612+
assertThat(lookupRealmName, is("default_file"));
613+
assertThat(lookupRealmType, is("file"));
606614
}
607615

608616
{

client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java

+39-9
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,14 @@ protected AuthenticateResponse createTestInstance() {
7070
final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4));
7171
final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4));
7272
final boolean enabled = randomBoolean();
73-
return new AuthenticateResponse(new User(username, roles, metadata, fullName, email), enabled);
73+
final String authenticationRealmName = randomAlphaOfLength(5);
74+
final String authenticationRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos");
75+
final String lookupRealmName = randomAlphaOfLength(5);
76+
final String lookupRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos");
77+
return new AuthenticateResponse(
78+
new User(username, roles, metadata, fullName, email), enabled,
79+
new AuthenticateResponse.RealmInfo(authenticationRealmName, authenticationRealmType),
80+
new AuthenticateResponse.RealmInfo(lookupRealmName, lookupRealmType));
7481
}
7582

7683
private void toXContent(AuthenticateResponse response, XContentBuilder builder) throws IOException {
@@ -87,41 +94,64 @@ private void toXContent(AuthenticateResponse response, XContentBuilder builder)
8794
builder.field(AuthenticateResponse.EMAIL.getPreferredName(), user.getEmail());
8895
}
8996
builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled);
97+
builder.startObject(AuthenticateResponse.AUTHENTICATION_REALM.getPreferredName());
98+
builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), response.getAuthenticationRealm().getName());
99+
builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), response.getAuthenticationRealm().getType());
100+
builder.endObject();
101+
builder.startObject(AuthenticateResponse.LOOKUP_REALM.getPreferredName());
102+
builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), response.getLookupRealm().getName());
103+
builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), response.getLookupRealm().getType());
104+
builder.endObject();
90105
builder.endObject();
91106
}
92107

93108
private AuthenticateResponse copy(AuthenticateResponse response) {
94109
final User originalUser = response.getUser();
95110
final User copyUser = new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
96111
originalUser.getFullName(), originalUser.getEmail());
97-
return new AuthenticateResponse(copyUser, response.enabled());
112+
return new AuthenticateResponse(copyUser, response.enabled(), response.getAuthenticationRealm(),
113+
response.getLookupRealm());
98114
}
99115

100116
private AuthenticateResponse mutate(AuthenticateResponse response) {
101117
final User originalUser = response.getUser();
102-
switch (randomIntBetween(1, 6)) {
118+
switch (randomIntBetween(1, 8)) {
103119
case 1:
104120
return new AuthenticateResponse(new User(originalUser.getUsername() + "wrong", originalUser.getRoles(),
105-
originalUser.getMetadata(), originalUser.getFullName(), originalUser.getEmail()), response.enabled());
121+
originalUser.getMetadata(), originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
122+
response.getAuthenticationRealm(), response.getLookupRealm());
106123
case 2:
107124
final Collection<String> wrongRoles = new ArrayList<>(originalUser.getRoles());
108125
wrongRoles.add(randomAlphaOfLengthBetween(1, 4));
109126
return new AuthenticateResponse(new User(originalUser.getUsername(), wrongRoles, originalUser.getMetadata(),
110-
originalUser.getFullName(), originalUser.getEmail()), response.enabled());
127+
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
128+
response.getLookupRealm());
111129
case 3:
112130
final Map<String, Object> wrongMetadata = new HashMap<>(originalUser.getMetadata());
113131
wrongMetadata.put("wrong_string", randomAlphaOfLengthBetween(0, 4));
114132
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), wrongMetadata,
115-
originalUser.getFullName(), originalUser.getEmail()), response.enabled());
133+
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
134+
response.getLookupRealm());
116135
case 4:
117136
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
118-
originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled());
137+
originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled(),
138+
response.getAuthenticationRealm(), response.getLookupRealm());
119139
case 5:
120140
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
121-
originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled());
141+
originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled(),
142+
response.getAuthenticationRealm(), response.getLookupRealm());
122143
case 6:
123144
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
124-
originalUser.getFullName(), originalUser.getEmail()), !response.enabled());
145+
originalUser.getFullName(), originalUser.getEmail()), !response.enabled(), response.getAuthenticationRealm(),
146+
response.getLookupRealm());
147+
case 7:
148+
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
149+
originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
150+
new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5)));
151+
case 8:
152+
return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
153+
originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
154+
new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5)), response.getLookupRealm());
125155
}
126156
throw new IllegalStateException("Bad random number");
127157
}

docs/java-rest/high-level/security/authenticate.asciidoc

+11-3
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ This method does not require a request object. The client waits for the
2424
[id="{upid}-{api}-response"]
2525
==== Response
2626

27-
The returned +{response}+ contains two fields. Firstly, the `user` field
27+
The returned +{response}+ contains four fields. The `user` field
2828
, accessed with `getUser`, contains all the information about this
29-
authenticated user. The other field, `enabled`, tells if this user is actually
30-
usable or has been temporalily deactivated.
29+
authenticated user. The field `enabled`, tells if this user is actually
30+
usable or has been temporarily deactivated. The field `authentication_realm`,
31+
accessed with `getAuthenticationRealm` contains the name and type of the
32+
Realm that has authenticated the user and the field `lookup_realm`,
33+
accessed with `getLookupRealm` contains the name and type of the Realm where
34+
the user information were retrieved from.
3135

3236
["source","java",subs="attributes,callouts,macros"]
3337
--------------------------------------------------
@@ -36,6 +40,10 @@ include-tagged::{doc-tests-file}[{api}-response]
3640
<1> `getUser` retrieves the `User` instance containing the information,
3741
see {javadoc-client}/security/user/User.html.
3842
<2> `enabled` tells if this user is usable or is deactivated.
43+
<3> `getAuthenticationRealm().getName()` retrieves the name of the realm that authenticated the user.
44+
<4> `getAuthenticationRealm().getType()` retrieves the type of the realm that authenticated the user.
45+
<5> `getLookupRealm().getName()` retrieves the name of the realm from where the user information is looked up.
46+
<6> `getLookupRealm().getType()` retrieves the type of the realm from where the user information is looked up.
3947

4048
[id="{upid}-{api}-async"]
4149
==== Asynchronous Execution

x-pack/docs/en/rest-api/security/authenticate.asciidoc

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ authenticate a user and retrieve information about the authenticated user.
1313

1414
==== Description
1515

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

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

@@ -41,7 +41,15 @@ The following example output provides information about the "rdeniro" user:
4141
"full_name": null,
4242
"email": null,
4343
"metadata": { },
44-
"enabled": true
44+
"enabled": true,
45+
"authentication_realm": {
46+
"name" : "default_file",
47+
"type" : "file"
48+
},
49+
"lookup_realm": {
50+
"name" : "default_file",
51+
"type" : "file"
52+
}
4553
}
4654
--------------------------------------------------
4755
// TESTRESPONSE[s/"rdeniro"/"$body.username"/]

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/AuthenticateResponse.java

+20-7
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,49 @@
55
*/
66
package org.elasticsearch.xpack.core.security.action.user;
77

8+
import org.elasticsearch.Version;
89
import org.elasticsearch.action.ActionResponse;
910
import org.elasticsearch.common.io.stream.StreamInput;
1011
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.xpack.core.security.authc.Authentication;
1113
import org.elasticsearch.xpack.core.security.user.User;
1214

1315
import java.io.IOException;
1416

1517
public class AuthenticateResponse extends ActionResponse {
1618

17-
private User user;
19+
private Authentication authentication;
1820

1921
public AuthenticateResponse() {}
2022

21-
public AuthenticateResponse(User user) {
22-
this.user = user;
23+
public AuthenticateResponse(Authentication authentication){
24+
this.authentication = authentication;
2325
}
2426

25-
public User user() {
26-
return user;
27+
public Authentication authentication() {
28+
return authentication;
2729
}
2830

2931
@Override
3032
public void writeTo(StreamOutput out) throws IOException {
3133
super.writeTo(out);
32-
User.writeTo(user, out);
34+
if (out.getVersion().before(Version.V_6_6_0)) {
35+
User.writeTo(authentication.getUser(), out);
36+
} else {
37+
authentication.writeTo(out);
38+
}
3339
}
3440

3541
@Override
3642
public void readFrom(StreamInput in) throws IOException {
3743
super.readFrom(in);
38-
user = User.readFrom(in);
44+
if (in.getVersion().before(Version.V_6_6_0)) {
45+
final User user = User.readFrom(in);
46+
final Authentication.RealmRef unknownRealm = new Authentication.RealmRef("__unknown", "__unknown", "__unknown");
47+
authentication = new Authentication(user, unknownRealm, unknownRealm);
48+
} else {
49+
authentication = new Authentication(in);
50+
}
3951
}
52+
4053
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java

+28-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.elasticsearch.common.io.stream.StreamInput;
1212
import org.elasticsearch.common.io.stream.StreamOutput;
1313
import org.elasticsearch.common.util.concurrent.ThreadContext;
14+
import org.elasticsearch.common.xcontent.ToXContentObject;
15+
import org.elasticsearch.common.xcontent.XContentBuilder;
1416
import org.elasticsearch.xpack.core.security.user.InternalUserSerializationHelper;
1517
import org.elasticsearch.xpack.core.security.user.User;
1618

@@ -20,7 +22,7 @@
2022

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

2527
private final User user;
2628
private final RealmRef authenticatedBy;
@@ -163,6 +165,31 @@ public int hashCode() {
163165
return result;
164166
}
165167

168+
@Override
169+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
170+
builder.startObject();
171+
builder.field(User.Fields.USERNAME.getPreferredName(), user.principal());
172+
builder.array(User.Fields.ROLES.getPreferredName(), user.roles());
173+
builder.field(User.Fields.FULL_NAME.getPreferredName(), user.fullName());
174+
builder.field(User.Fields.EMAIL.getPreferredName(), user.email());
175+
builder.field(User.Fields.METADATA.getPreferredName(), user.metadata());
176+
builder.field(User.Fields.ENABLED.getPreferredName(), user.enabled());
177+
builder.startObject(User.Fields.AUTHENTICATION_REALM.getPreferredName());
178+
builder.field(User.Fields.REALM_NAME.getPreferredName(), getAuthenticatedBy().getName());
179+
builder.field(User.Fields.REALM_TYPE.getPreferredName(), getAuthenticatedBy().getType());
180+
builder.endObject();
181+
builder.startObject(User.Fields.LOOKUP_REALM.getPreferredName());
182+
if (getLookedUpBy() != null) {
183+
builder.field(User.Fields.REALM_NAME.getPreferredName(), getLookedUpBy().getName());
184+
builder.field(User.Fields.REALM_TYPE.getPreferredName(), getLookedUpBy().getType());
185+
} else {
186+
builder.field(User.Fields.REALM_NAME.getPreferredName(), getAuthenticatedBy().getName());
187+
builder.field(User.Fields.REALM_TYPE.getPreferredName(), getAuthenticatedBy().getType());
188+
}
189+
builder.endObject();
190+
return builder.endObject();
191+
}
192+
166193
public static class RealmRef {
167194

168195
private final String nodeName;

0 commit comments

Comments
 (0)