Quellcode durchsuchen

Merge pull request #8148 from weseek/feat/123280-130657-keycloak-large-group-tree-sync

auth before every request to keycloak server & set offset to group us…
Futa Arai vor 2 Jahren
Ursprung
Commit
e7835498d5

+ 6 - 6
apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.integ.ts

@@ -88,7 +88,8 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
 
 
         // mock group users
         // mock group users
         listMembers: (payload) => {
         listMembers: (payload) => {
-          if (payload?.id === 'groupId1') {
+          // set 'first' condition to 0 (the first member request to server) or else it will result in infinite loop
+          if (payload?.id === 'groupId1' && payload?.first === 0) {
             return Promise.resolve([
             return Promise.resolve([
               {
               {
                 id: 'userId1',
                 id: 'userId1',
@@ -97,7 +98,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
               },
             ]);
             ]);
           }
           }
-          if (payload?.id === 'groupId2') {
+          if (payload?.id === 'groupId2' && payload?.first === 0) {
             return Promise.resolve([
             return Promise.resolve([
               {
               {
                 id: 'userId2',
                 id: 'userId2',
@@ -106,7 +107,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
               },
             ]);
             ]);
           }
           }
-          if (payload?.id === 'groupId3') {
+          if (payload?.id === 'groupId3' && payload?.first === 0) {
             return Promise.resolve([
             return Promise.resolve([
               {
               {
                 id: 'userId3',
                 id: 'userId3',
@@ -115,7 +116,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
               },
             ]);
             ]);
           }
           }
-          if (payload?.id === 'groupId4') {
+          if (payload?.id === 'groupId4' && payload?.first === 0) {
             return Promise.resolve([
             return Promise.resolve([
               {
               {
                 id: 'userId4',
                 id: 'userId4',
@@ -124,7 +125,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
               },
             ]);
             ]);
           }
           }
-          return Promise.reject(new Error('not found'));
+          return Promise.resolve([]);
         },
         },
       };
       };
 
 
@@ -150,7 +151,6 @@ describe('KeycloakUserGroupSyncService.generateExternalUserGroupTrees', () => {
   });
   });
 
 
   it('creates ExternalUserGroupTrees', async() => {
   it('creates ExternalUserGroupTrees', async() => {
-
     const rootNodes = await keycloakUserGroupSyncService?.generateExternalUserGroupTrees();
     const rootNodes = await keycloakUserGroupSyncService?.generateExternalUserGroupTrees();
 
 
     expect(rootNodes?.length).toBe(2);
     expect(rootNodes?.length).toBe(2);

+ 25 - 1
apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.ts

@@ -59,6 +59,7 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
     await this.auth();
     await this.auth();
 
 
     // Type is 'GroupRepresentation', but 'find' does not return 'attributes' field. Hence, attribute for description is not present.
     // Type is 'GroupRepresentation', but 'find' does not return 'attributes' field. Hence, attribute for description is not present.
+    logger.info('Get groups from keycloak server');
     const rootGroups = await this.kcAdminClient.groups.find({ realm: this.realm });
     const rootGroups = await this.kcAdminClient.groups.find({ realm: this.realm });
 
 
     return (await batchProcessPromiseAll(rootGroups, TREES_BATCH_SIZE, group => this.groupRepresentationToTreeNode(group)))
     return (await batchProcessPromiseAll(rootGroups, TREES_BATCH_SIZE, group => this.groupRepresentationToTreeNode(group)))
@@ -85,7 +86,8 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
   private async groupRepresentationToTreeNode(group: GroupRepresentation): Promise<ExternalUserGroupTreeNode | null> {
   private async groupRepresentationToTreeNode(group: GroupRepresentation): Promise<ExternalUserGroupTreeNode | null> {
     if (group.id == null || group.name == null) return null;
     if (group.id == null || group.name == null) return null;
 
 
-    const userRepresentations = await this.kcAdminClient.groups.listMembers({ id: group.id, realm: this.realm });
+    logger.info('Get users from keycloak server');
+    const userRepresentations = await this.getMembers(group.id);
 
 
     const userInfos = userRepresentations != null ? this.userRepresentationsToExternalUserInfos(userRepresentations) : [];
     const userInfos = userRepresentations != null ? this.userRepresentationsToExternalUserInfos(userRepresentations) : [];
     const description = await this.getGroupDescription(group.id) || undefined;
     const description = await this.getGroupDescription(group.id) || undefined;
@@ -111,12 +113,34 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
     };
     };
   }
   }
 
 
+  private async getMembers(groupId: string): Promise<UserRepresentation[]> {
+    let allUsers: UserRepresentation[] = [];
+
+    const fetchUsersWithOffset = async(offset: number) => {
+      await this.auth();
+      const response = await this.kcAdminClient.groups.listMembers({
+        id: groupId, realm: this.realm, first: offset,
+      });
+
+      if (response != null && response.length > 0) {
+        allUsers = allUsers.concat(response);
+        return fetchUsersWithOffset(offset + response.length);
+      }
+    };
+
+    await fetchUsersWithOffset(0);
+
+    return allUsers;
+  }
+
+
   /**
   /**
    * Fetch group detail from Keycloak and return group description
    * Fetch group detail from Keycloak and return group description
    */
    */
   private async getGroupDescription(groupId: string): Promise<string | null> {
   private async getGroupDescription(groupId: string): Promise<string | null> {
     if (this.groupDescriptionAttribute == null) return null;
     if (this.groupDescriptionAttribute == null) return null;
 
 
+    await this.auth();
     const groupDetail = await this.kcAdminClient.groups.findOne({ id: groupId, realm: this.realm });
     const groupDetail = await this.kcAdminClient.groups.findOne({ id: groupId, realm: this.realm });
 
 
     const description = groupDetail?.attributes?.[this.groupDescriptionAttribute]?.[0];
     const description = groupDetail?.attributes?.[this.groupDescriptionAttribute]?.[0];