Browse Source

User Profile: mappings update (#82700)

This PR updates the profile document mappings with following changes:
* The roles field is now nested under user rather than access
* As a result, the access.applications field is now removed and
  application specific access data is directly populated under acess
* Add a domain field under user.realm which contains both the domain
  name and full realm list of the domain
Yang Wang 3 years ago
parent
commit
3001e63cda

+ 8 - 26
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/profile/Profile.java

@@ -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 {
@@ -33,6 +33,7 @@ public record Profile(
 
     public record ProfileUser(
         String username,
+        List<String> roles,
         String realmName,
         @Nullable String realmDomain,
         String email,
@@ -44,6 +45,7 @@ public record Profile(
         public ProfileUser(StreamInput in) throws IOException {
             this(
                 in.readString(),
+                in.readStringList(),
                 in.readString(),
                 in.readOptionalString(),
                 in.readOptionalString(),
@@ -61,6 +63,7 @@ public record Profile(
         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);
@@ -82,6 +85,7 @@ public record Profile(
         @Override
         public void writeTo(StreamOutput out) throws IOException {
             out.writeString(username);
+            out.writeStringCollection(roles);
             out.writeString(realmName);
             out.writeOptionalString(realmDomain);
             out.writeOptionalString(email);
@@ -91,28 +95,6 @@ public record Profile(
         }
     }
 
-    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 {
@@ -136,7 +118,7 @@ public record Profile(
     }
 
     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
@@ -146,7 +128,7 @@ public record Profile(
         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();
@@ -159,7 +141,7 @@ public record Profile(
         out.writeBoolean(enabled);
         out.writeLong(lastSynchronized);
         user.writeTo(out);
-        access.writeTo(out);
+        out.writeMap(access);
         out.writeMap(applicationData);
         versionControl.writeTo(out);
     }

+ 13 - 7
x-pack/plugin/security/qa/profile/src/javaRestTest/java/org/elasticsearch/xpack/security/profile/ProfileIT.java

@@ -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",
@@ -44,11 +55,6 @@ public class ProfileIT extends ESRestTestCase {
             },
             "last_synchronized": %s,
             "access": {
-              "roles": [
-                "role1",
-                "role2"
-              ],
-              "applications": {}
             },
             "application_data": {
               "app1": { "name": "app1" },

+ 39 - 7
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/profile/ProfileSingleNodeTests.java

@@ -112,7 +112,7 @@ public class ProfileSingleNodeTests extends SecuritySingleNodeTestCase {
         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
         );
 
@@ -170,7 +170,35 @@ public class ProfileSingleNodeTests extends SecuritySingleNodeTestCase {
         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();
@@ -181,11 +209,15 @@ public class ProfileSingleNodeTests extends SecuritySingleNodeTestCase {
         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) {

+ 51 - 53
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileDocument.java

@@ -24,6 +24,7 @@ import org.elasticsearch.xpack.core.security.user.User;
 
 import java.io.IOException;
 import java.time.Instant;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -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,
@@ -52,6 +54,7 @@ public record ProfileDocument(
         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());
@@ -72,23 +75,7 @@ public record ProfileDocument(
         }
 
         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);
         }
     }
 
@@ -99,8 +86,13 @@ public record ProfileDocument(
         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();
@@ -118,13 +110,14 @@ public record ProfileDocument(
             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
         );
     }
@@ -133,27 +126,23 @@ public record ProfileDocument(
         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(
@@ -161,7 +150,7 @@ public record ProfileDocument(
             (boolean) args[1],
             (long) args[2],
             (ProfileDocumentUser) args[3],
-            (Access) args[4],
+            (Map<String, Object>) args[4],
             (BytesReference) args[5]
         )
     );
@@ -172,28 +161,37 @@ public record ProfileDocument(
         (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"));
     }
 }

+ 22 - 8
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java

@@ -35,6 +35,7 @@ import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHits;
 import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.XContentBuilder;
 import org.elasticsearch.xcontent.XContentFactory;
 import org.elasticsearch.xcontent.XContentParser;
@@ -51,7 +52,6 @@ import java.io.IOException;
 import java.time.Clock;
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
@@ -230,12 +230,12 @@ public class ProfileService {
 
     private void updateProfileForActivate(Subject subject, VersionedDocument versionedDocument, ActionListener<Profile> listener)
         throws IOException {
-        final ProfileDocument profileDocument = updateWithSubjectAndStripApplicationData(versionedDocument.doc, subject);
+        final ProfileDocument profileDocument = updateWithSubject(versionedDocument.doc, subject);
 
         doUpdate(
             buildUpdateRequest(
                 profileDocument.uid(),
-                wrapProfileDocument(profileDocument),
+                wrapProfileDocumentWithoutApplicationData(profileDocument),
                 RefreshPolicy.WAIT_UNTIL,
                 versionedDocument.primaryTerm,
                 versionedDocument.seqNo
@@ -305,7 +305,7 @@ public class ProfileService {
         }
     }
 
-    XContentBuilder wrapProfileDocument(ProfileDocument profileDocument) throws IOException {
+    private XContentBuilder wrapProfileDocument(ProfileDocument profileDocument) throws IOException {
         final XContentBuilder builder = XContentFactory.jsonBuilder();
         builder.startObject();
         builder.field("user_profile", profileDocument);
@@ -313,6 +313,19 @@ public class ProfileService {
         return builder;
     }
 
+    private XContentBuilder wrapProfileDocumentWithoutApplicationData(ProfileDocument profileDocument) throws IOException {
+        final XContentBuilder builder = XContentFactory.jsonBuilder();
+        builder.startObject();
+        builder.field(
+            "user_profile",
+            profileDocument,
+            // NOT including the access and data in the update request so they will not be changed
+            new ToXContent.MapParams(Map.of("include_access", Boolean.FALSE.toString(), "include_data", Boolean.FALSE.toString()))
+        );
+        builder.endObject();
+        return builder;
+    }
+
     /**
      * Freeze the profile index check its availability and return it if everything is ok.
      * Otherwise it returns null.
@@ -330,7 +343,7 @@ public class ProfileService {
         return Optional.of(frozenProfileIndex);
     }
 
-    private ProfileDocument updateWithSubjectAndStripApplicationData(ProfileDocument doc, Subject subject) {
+    private ProfileDocument updateWithSubject(ProfileDocument doc, Subject subject) {
         final User subjectUser = subject.getUser();
         return new ProfileDocument(
             doc.uid(),
@@ -338,6 +351,7 @@ public class ProfileService {
             Instant.now().toEpochMilli(),
             new ProfileDocument.ProfileDocumentUser(
                 subjectUser.principal(),
+                Arrays.asList(subjectUser.roles()),
                 subject.getRealm(),
                 // Replace with incoming information even when they are null
                 subjectUser.email(),
@@ -346,8 +360,8 @@ public class ProfileService {
                 doc.user().displayName(),
                 subjectUser.enabled()
             ),
-            new ProfileDocument.Access(List.of(subjectUser.roles()), doc.access().applications()),
-            null
+            doc.access(),
+            doc.applicationData()
         );
     }
 
@@ -372,7 +386,7 @@ public class ProfileService {
                 doc.enabled(),
                 doc.lastSynchronized(),
                 doc.user().toProfileUser(realmDomain),
-                doc.access().toProfileAccess(),
+                doc.access(),
                 applicationData,
                 new Profile.VersionControl(primaryTerm, seqNo)
             );

+ 37 - 16
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java

@@ -761,6 +761,10 @@ public class SecuritySystemIndices {
                                     builder.field("type", "search_as_you_type");
                                     builder.endObject();
 
+                                    builder.startObject("roles");
+                                    builder.field("type", "keyword");
+                                    builder.endObject();
+
                                     builder.startObject("realm");
                                     {
                                         builder.field("type", "object");
@@ -774,6 +778,36 @@ public class SecuritySystemIndices {
                                             builder.field("type", "keyword");
                                             builder.endObject();
 
+                                            builder.startObject("domain");
+                                            {
+                                                builder.field("type", "object");
+                                                builder.startObject("properties");
+                                                {
+                                                    builder.startObject("name");
+                                                    builder.field("type", "keyword");
+                                                    builder.endObject();
+
+                                                    builder.startObject("realms");
+                                                    {
+                                                        builder.field("type", "nested");
+                                                        builder.startObject("properties");
+                                                        {
+                                                            builder.startObject("name");
+                                                            builder.field("type", "keyword");
+                                                            builder.endObject();
+
+                                                            builder.startObject("type");
+                                                            builder.field("type", "keyword");
+                                                            builder.endObject();
+                                                        }
+                                                        builder.endObject();
+                                                    }
+                                                    builder.endObject();
+                                                }
+                                                builder.endObject();
+                                            }
+                                            builder.endObject();
+
                                             builder.startObject("node_name");
                                             builder.field("type", "keyword");
                                             builder.endObject();
@@ -808,25 +842,12 @@ public class SecuritySystemIndices {
                             builder.field("format", "epoch_millis");
                             builder.endObject();
 
+                            // Searchable application specific data
                             builder.startObject("access");
-                            {
-                                builder.field("type", "object");
-                                builder.startObject("properties");
-                                {
-                                    builder.startObject("roles");
-                                    builder.field("type", "keyword");
-                                    builder.endObject();
-
-                                    // Application specific access data, e.g. kibana spaces
-                                    builder.startObject("applications");
-                                    builder.field("type", "flattened");
-                                    builder.endObject();
-                                }
-                                builder.endObject();
-                            }
+                            builder.field("type", "flattened");
                             builder.endObject();
 
-                            // Application data, retrievable but not searchable
+                            // Non-searchable application specific data, retrievable but not searchable
                             builder.startObject("application_data");
                             {
                                 builder.field("type", "object");

+ 24 - 9
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/profile/ProfileServiceTests.java

@@ -60,9 +60,20 @@ public class ProfileServiceTests extends ESTestCase {
             "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",
@@ -72,11 +83,6 @@ public class ProfileServiceTests extends ESTestCase {
             },
             "last_synchronized": %s,
             "access": {
-              "roles": [
-                "role1",
-                "role2"
-              ],
-              "applications": {}
             },
             "application_data": {
               "app1": { "name": "app1" },
@@ -153,8 +159,17 @@ public class ProfileServiceTests extends ESTestCase {
                     uid,
                     true,
                     lastSynchronized,
-                    new Profile.ProfileUser("foo", "realm_name", null, "foo@example.com", "User Foo", "Curious Foo", true),
-                    new Profile.Access(List.of("role1", "role2"), Map.of()),
+                    new Profile.ProfileUser(
+                        "foo",
+                        List.of("role1", "role2"),
+                        "realm_name_1",
+                        null,
+                        "foo@example.com",
+                        "User Foo",
+                        "Curious Foo",
+                        true
+                    ),
+                    Map.of(),
                     applicationData,
                     new Profile.VersionControl(1, 0)
                 )