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

User Profile: mappings update #82700

Merged
merged 3 commits into from
Jan 20, 2022
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 @@ -24,7 +24,7 @@ public record Profile(
boolean enabled,
long lastSynchronized,
ProfileUser user,
Access access,
Map<String, Object> access,
Map<String, Object> applicationData,
VersionControl versionControl
) implements Writeable, ToXContentObject {
Expand All @@ -33,6 +33,7 @@ public record QualifiedName(String username, String realmDomain) {}

public record ProfileUser(
String username,
List<String> roles,
String realmName,
@Nullable String realmDomain,
String email,
Expand All @@ -44,6 +45,7 @@ public record ProfileUser(
public ProfileUser(StreamInput in) throws IOException {
this(
in.readString(),
in.readStringList(),
in.readString(),
in.readOptionalString(),
in.readOptionalString(),
Expand All @@ -61,6 +63,7 @@ public QualifiedName qualifiedName() {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("user");
builder.field("username", username);
builder.field("roles", roles);
builder.field("realm_name", realmName);
if (realmDomain != null) {
builder.field("realm_domain", realmDomain);
Expand All @@ -82,6 +85,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(username);
out.writeStringCollection(roles);
out.writeString(realmName);
out.writeOptionalString(realmDomain);
out.writeOptionalString(email);
Expand All @@ -91,28 +95,6 @@ public void writeTo(StreamOutput out) throws IOException {
}
}

public record Access(List<String> roles, Map<String, Object> applications) implements Writeable, ToXContent {

public Access(StreamInput in) throws IOException {
this(in.readStringList(), in.readMap());
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("access");
builder.field("roles", roles);
builder.field("applications", applications);
builder.endObject();
return builder;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeStringCollection(roles);
out.writeMap(applications);
}
}

public record VersionControl(long primaryTerm, long seqNo) implements Writeable, ToXContent {

public VersionControl(StreamInput in) throws IOException {
Expand All @@ -136,7 +118,7 @@ public void writeTo(StreamOutput out) throws IOException {
}

public Profile(StreamInput in) throws IOException {
this(in.readString(), in.readBoolean(), in.readLong(), new ProfileUser(in), new Access(in), in.readMap(), new VersionControl(in));
this(in.readString(), in.readBoolean(), in.readLong(), new ProfileUser(in), in.readMap(), in.readMap(), new VersionControl(in));
}

@Override
Expand All @@ -146,7 +128,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field("enabled", enabled);
builder.field("last_synchronized", lastSynchronized);
user.toXContent(builder, params);
access.toXContent(builder, params);
builder.field("access", access);
builder.field("data", applicationData);
versionControl.toXContent(builder, params);
builder.endObject();
Expand All @@ -159,7 +141,7 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(enabled);
out.writeLong(lastSynchronized);
user.writeTo(out);
access.writeTo(out);
out.writeMap(access);
out.writeMap(applicationData);
versionControl.writeTo(out);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,20 @@ public class ProfileIT extends ESRestTestCase {
"enabled": true,
"user": {
"username": "foo",
"roles": [
"role1",
"role2"
],
"realm": {
"name": "realm_name",
"type": "realm_type",
"name": "realm_name_1",
"type": "realm_type_1",
"domain": {
"name": "domainA",
"realms": [
{ "name": "realm_name_1", "type": "realm_type_1" },
{ "name": "realm_name_2", "type": "realm_type_2" }
]
},
"node_name": "node1"
},
"email": "foo@example.com",
Expand All @@ -44,11 +55,6 @@ public class ProfileIT extends ESRestTestCase {
},
"last_synchronized": %s,
"access": {
"roles": [
"role1",
"role2"
],
"applications": {}
},
"application_data": {
"app1": { "name": "app1" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void testGetProfileByAuthentication() {
final ProfileService profileService = node().injector().getInstance(ProfileService.class);
final Authentication authentication = new Authentication(
new User("foo"),
new Authentication.RealmRef("realm_name", "realm_type", randomAlphaOfLengthBetween(3, 8)),
new Authentication.RealmRef("realm_name_1", "realm_type_1", randomAlphaOfLengthBetween(3, 8)),
null
);

Expand Down Expand Up @@ -170,7 +170,35 @@ public void testActivateProfile() {
assertThat(profile3.uid(), not(equalTo(profile1.uid())));
assertThat(profile3.user().email(), equalTo(RAC_USER_NAME + "@example.com"));
assertThat(profile3.user().fullName(), nullValue());
assertThat(profile3.access().roles(), containsInAnyOrder("rac_role"));
assertThat(profile3.user().roles(), containsInAnyOrder("rac_role"));
assertThat(profile3.access(), anEmptyMap());

// Manually inserting some application data
client().prepareUpdate(randomFrom(INTERNAL_SECURITY_PROFILE_INDEX_8, SECURITY_PROFILE_ALIAS), "profile_" + profile3.uid())
.setDoc("""
{
"user_profile": {
"access": {
"my_app": {
"tag": "prod"
}
},
"application_data": {
"my_app": {
"theme": "default"
}
}
}
}
""", XContentType.JSON)
.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL)
.get();

// Above manual update should be successful
final Profile profile4 = getProfile(profile3.uid(), Set.of("my_app"));
assertThat(profile4.uid(), equalTo(profile3.uid()));
assertThat(profile4.access(), equalTo(Map.of("my_app", Map.of("tag", "prod"))));
assertThat(profile4.applicationData(), equalTo(Map.of("my_app", Map.of("theme", "default"))));

// Update native rac user
final PutUserRequest putUserRequest2 = new PutUserRequest();
Expand All @@ -181,11 +209,15 @@ public void testActivateProfile() {
assertThat(client().execute(PutUserAction.INSTANCE, putUserRequest2).actionGet().created(), is(false));

// Activate again should see the updated user info
final Profile profile4 = doActivateProfile(RAC_USER_NAME, nativeRacUserPassword);
assertThat(profile4.uid(), equalTo(profile3.uid()));
assertThat(profile4.user().email(), nullValue());
assertThat(profile4.user().fullName(), equalTo("Native RAC User"));
assertThat(profile4.access().roles(), containsInAnyOrder("rac_role", "superuser"));
final Profile profile5 = doActivateProfile(RAC_USER_NAME, nativeRacUserPassword);
assertThat(profile5.uid(), equalTo(profile3.uid()));
assertThat(profile5.user().email(), nullValue());
assertThat(profile5.user().fullName(), equalTo("Native RAC User"));
assertThat(profile5.user().roles(), containsInAnyOrder("rac_role", "superuser"));
// Re-activate should not change access
assertThat(profile5.access(), equalTo(Map.of("my_app", Map.of("tag", "prod"))));
// Re-activate should not change application data
assertThat(getProfile(profile5.uid(), Set.of("my_app")).applicationData(), equalTo(Map.of("my_app", Map.of("theme", "default"))));
}

private Profile doActivateProfile(String username, SecureString password) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

Expand All @@ -35,12 +36,13 @@ public record ProfileDocument(
boolean enabled,
long lastSynchronized,
ProfileDocumentUser user,
Access access,
Map<String, Object> access,
BytesReference applicationData
) implements ToXContentObject {

public record ProfileDocumentUser(
String username,
List<String> roles,
Authentication.RealmRef realm,
String email,
String fullName,
Expand All @@ -52,6 +54,7 @@ public record ProfileDocumentUser(
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("user");
builder.field("username", username);
builder.field("roles", roles);
builder.startObject("realm");
builder.field("name", realm.getName());
builder.field("type", realm.getType());
Expand All @@ -72,23 +75,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
}

public Profile.ProfileUser toProfileUser(@Nullable String realmDomain) {
return new Profile.ProfileUser(username, realm.getName(), realmDomain, email, fullName, displayName, active);
}
}

public record Access(List<String> roles, Map<String, Object> applications) implements ToXContent {

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("access");
builder.field("roles", roles);
builder.field("applications", applications);
builder.endObject();
return builder;
}

public Profile.Access toProfileAccess() {
return new Profile.Access(roles, applications);
return new Profile.ProfileUser(username, roles, realm.getName(), realmDomain, email, fullName, displayName, active);
}
}

Expand All @@ -99,8 +86,13 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field("enabled", enabled);
builder.field("last_synchronized", lastSynchronized);
user.toXContent(builder, params);
access.toXContent(builder, params);
if (applicationData != null) {

if (params.paramAsBoolean("include_access", true) && access != null) {
builder.field("access", access);
} else {
builder.startObject("access").endObject();
}
if (params.paramAsBoolean("include_data", true) && applicationData != null) {
builder.field("application_data", applicationData);
} else {
builder.startObject("application_data").endObject();
Expand All @@ -118,13 +110,14 @@ static ProfileDocument fromSubject(Subject subject) {
Instant.now().toEpochMilli(),
new ProfileDocumentUser(
subjectUser.principal(),
Arrays.asList(subjectUser.roles()),
subject.getRealm(),
subjectUser.email(),
subjectUser.fullName(),
null,
subjectUser.enabled()
),
new Access(List.of(subjectUser.roles()), Map.of()),
Map.of(),
null
);
}
Expand All @@ -133,35 +126,31 @@ public static ProfileDocument fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}

static final ConstructingObjectParser<ProfileDocumentUser, Void> PROFILE_USER_PARSER = new ConstructingObjectParser<>(
@SuppressWarnings("unchecked")
static final ConstructingObjectParser<ProfileDocumentUser, Void> PROFILE_DOC_USER_PARSER = new ConstructingObjectParser<>(
"user_profile_document_user",
false,
(args, v) -> new ProfileDocumentUser(
(String) args[0],
(Authentication.RealmRef) args[1],
(String) args[2],
(List<String>) args[1],
(Authentication.RealmRef) args[2],
(String) args[3],
(String) args[4],
(Boolean) args[5]
(String) args[5],
(Boolean) args[6]
)
);

@SuppressWarnings("unchecked")
static final ConstructingObjectParser<Access, Void> ACCESS_PARSER = new ConstructingObjectParser<>(
"user_profile_document_access",
false,
(args, v) -> new Access((List<String>) args[0], (Map<String, Object>) args[1])
);

static final ConstructingObjectParser<ProfileDocument, Void> PROFILE_PARSER = new ConstructingObjectParser<>(
static final ConstructingObjectParser<ProfileDocument, Void> PROFILE_DOC_PARSER = new ConstructingObjectParser<>(
"user_profile_document",
false,
(args, v) -> new ProfileDocument(
(String) args[0],
(boolean) args[1],
(long) args[2],
(ProfileDocumentUser) args[3],
(Access) args[4],
(Map<String, Object>) args[4],
(BytesReference) args[5]
)
);
Expand All @@ -172,28 +161,37 @@ public static ProfileDocument fromXContent(XContentParser parser) {
(args, v) -> (ProfileDocument) args[0]
);

// TODO:This is a copy from Authentication class. This version ignores unknown fields so that it currently ignores the domain field
// The support will be added later when authentication update is finalised.
public static ConstructingObjectParser<Authentication.RealmRef, Void> REALM_REF_PARSER = new ConstructingObjectParser<>(
"realm_ref",
true,
(args, v) -> new Authentication.RealmRef((String) args[0], (String) args[1], (String) args[2])
);

static {
PROFILE_USER_PARSER.declareString(constructorArg(), new ParseField("username"));
PROFILE_USER_PARSER.declareObject(
constructorArg(),
(p, c) -> Authentication.REALM_REF_PARSER.parse(p, null),
new ParseField("realm")
);
PROFILE_USER_PARSER.declareString(optionalConstructorArg(), new ParseField("email"));
PROFILE_USER_PARSER.declareString(optionalConstructorArg(), new ParseField("full_name"));
PROFILE_USER_PARSER.declareString(optionalConstructorArg(), new ParseField("display_name"));
PROFILE_USER_PARSER.declareBoolean(constructorArg(), new ParseField("active"));
ACCESS_PARSER.declareStringArray(constructorArg(), new ParseField("roles"));
ACCESS_PARSER.declareObject(constructorArg(), (p, c) -> p.map(), new ParseField("applications"));

PROFILE_PARSER.declareString(constructorArg(), new ParseField("uid"));
PROFILE_PARSER.declareBoolean(constructorArg(), new ParseField("enabled"));
PROFILE_PARSER.declareLong(constructorArg(), new ParseField("last_synchronized"));
PROFILE_PARSER.declareObject(constructorArg(), (p, c) -> PROFILE_USER_PARSER.parse(p, null), new ParseField("user"));
PROFILE_PARSER.declareObject(constructorArg(), (p, c) -> ACCESS_PARSER.parse(p, null), new ParseField("access"));
REALM_REF_PARSER.declareString(constructorArg(), new ParseField("name"));
REALM_REF_PARSER.declareString(constructorArg(), new ParseField("type"));
REALM_REF_PARSER.declareString(constructorArg(), new ParseField("node_name"));
}

static {
PROFILE_DOC_USER_PARSER.declareString(constructorArg(), new ParseField("username"));
PROFILE_DOC_USER_PARSER.declareStringArray(constructorArg(), new ParseField("roles"));
PROFILE_DOC_USER_PARSER.declareObject(constructorArg(), (p, c) -> REALM_REF_PARSER.parse(p, null), new ParseField("realm"));
PROFILE_DOC_USER_PARSER.declareString(optionalConstructorArg(), new ParseField("email"));
PROFILE_DOC_USER_PARSER.declareString(optionalConstructorArg(), new ParseField("full_name"));
PROFILE_DOC_USER_PARSER.declareString(optionalConstructorArg(), new ParseField("display_name"));
PROFILE_DOC_USER_PARSER.declareBoolean(constructorArg(), new ParseField("active"));

PROFILE_DOC_PARSER.declareString(constructorArg(), new ParseField("uid"));
PROFILE_DOC_PARSER.declareBoolean(constructorArg(), new ParseField("enabled"));
PROFILE_DOC_PARSER.declareLong(constructorArg(), new ParseField("last_synchronized"));
PROFILE_DOC_PARSER.declareObject(constructorArg(), (p, c) -> PROFILE_DOC_USER_PARSER.parse(p, null), new ParseField("user"));
PROFILE_DOC_PARSER.declareObject(constructorArg(), (p, c) -> p.map(), new ParseField("access"));
ObjectParserHelper<ProfileDocument, Void> parserHelper = new ObjectParserHelper<>();
parserHelper.declareRawObject(PROFILE_PARSER, constructorArg(), new ParseField("application_data"));
parserHelper.declareRawObject(PROFILE_DOC_PARSER, constructorArg(), new ParseField("application_data"));

PARSER.declareObject(constructorArg(), (p, c) -> PROFILE_PARSER.parse(p, null), new ParseField("user_profile"));
PARSER.declareObject(constructorArg(), (p, c) -> PROFILE_DOC_PARSER.parse(p, null), new ParseField("user_profile"));
}
}
Loading