Răsfoiți Sursa

Merge pull request #8189 from weseek/fix/133816-133817-group-to-select-in-page-grant-fix-modal-not-fetched

fix group fetch for page grant fix page
Ryoji Shimizu 2 ani în urmă
părinte
comite
dc0c703a5e

+ 7 - 8
apps/app/src/components/PageAlert/FixPageGrantAlert.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState, useCallback } from 'react';
 
-import { GroupType, PageGrant } from '@growi/core';
+import { PageGrant } from '@growi/core';
 import { useTranslation } from 'react-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
@@ -9,8 +9,7 @@ import {
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { IPageGrantData } from '~/interfaces/page';
-import { IRecordApplicableGrant, IResIsGrantNormalizedGrantData } from '~/interfaces/page-grant';
-import { UserGroupDocument } from '~/server/models/user-group';
+import { ApplicableGroup, IRecordApplicableGrant, IResIsGrantNormalizedGrantData } from '~/interfaces/page-grant';
 import { useCurrentUser } from '~/stores/context';
 import { useSWRxApplicableGrant, useSWRxIsGrantNormalized, useSWRxCurrentPage } from '~/stores/page';
 
@@ -30,7 +29,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
   } = props;
 
   const [selectedGrant, setSelectedGrant] = useState<PageGrant>(PageGrant.GRANT_RESTRICTED);
-  const [selectedGroup, setSelectedGroup] = useState<UserGroupDocument | undefined>(undefined);
+  const [selectedGroup, setSelectedGroup] = useState<ApplicableGroup | undefined>(undefined);
 
   // Alert message state
   const [shouldShowModalAlert, setShowModalAlert] = useState<boolean>(false);
@@ -58,7 +57,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
     try {
       await apiv3Put(`/page/${pageId}/grant`, {
         grant: selectedGrant,
-        grantedGroups: selectedGroup?._id != null ? [{ item: selectedGroup?._id, type: GroupType.userGroup }] : null,
+        grantedGroups: selectedGroup?.item._id != null ? [{ item: selectedGroup?.item._id, type: selectedGroup.type }] : null,
       });
 
       toastSuccess(t('Successfully updated'));
@@ -186,7 +185,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
                       {
                         selectedGroup == null
                           ? t('fix_page_grant.modal.select_group_default_text')
-                          : selectedGroup.name
+                          : selectedGroup.item.name
                       }
                     </span>
                   </button>
@@ -194,12 +193,12 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
                     {
                       applicableGroups != null && applicableGroups.map(g => (
                         <button
-                          key={g._id}
+                          key={g.item._id}
                           className="dropdown-item"
                           type="button"
                           onClick={() => setSelectedGroup(g)}
                         >
-                          {g.name}
+                          {g.item.name}
                         </button>
                       ))
                     }

+ 8 - 12
apps/app/src/features/external-user-group/server/models/external-user-group-relation.integ.ts

@@ -107,24 +107,20 @@ describe('ExternalUserGroupRelation model', () => {
     });
   });
 
-  describe('findAllRelationForUser', () => {
+  describe('findAllGroupsForUser', () => {
     beforeAll(async() => {
       await ExternalUserGroupRelation.createRelations([groupId1, groupId2], user1);
       await ExternalUserGroupRelation.create({ relatedGroup: groupId3, relatedUser: user2._id });
     });
 
-    it('finds all relations for user with group populated', async() => {
-      const relations = await ExternalUserGroupRelation.findAllRelationForUser(user1);
-      const populatedGroupIds = relations.map((relation) => {
-        return typeof relation.relatedGroup !== 'string' ? relation.relatedGroup._id : null;
-      });
-      expect(populatedGroupIds).toStrictEqual([groupId1, groupId2]);
+    it('finds all groups related to user', async() => {
+      const groups = await ExternalUserGroupRelation.findAllGroupsForUser(user1);
+      const groupIds = groups.map(group => group._id);
+      expect(groupIds).toStrictEqual([groupId1, groupId2]);
 
-      const relations2 = await ExternalUserGroupRelation.findAllRelationForUser(user2);
-      const populatedGroupIds2 = relations2.map((relation) => {
-        return typeof relation.relatedGroup !== 'string' ? relation.relatedGroup._id : null;
-      });
-      expect(populatedGroupIds2).toStrictEqual([groupId3]);
+      const groups2 = await ExternalUserGroupRelation.findAllGroupsForUser(user2);
+      const groupIds2 = groups2.map(group => group._id);
+      expect(groupIds2).toStrictEqual([groupId3]);
     });
   });
 });

+ 2 - 2
apps/app/src/features/external-user-group/server/models/external-user-group-relation.ts

@@ -23,7 +23,7 @@ export interface ExternalUserGroupRelationModel extends Model<ExternalUserGroupR
 
   countByGroupIdsAndUser: (userGroupIds: ObjectIdLike[], userData) => Promise<number>
 
-  findAllRelationForUser: (user) => Promise<ExternalUserGroupRelationDocument[]>
+  findAllGroupsForUser: (user) => Promise<ExternalUserGroupDocument[]>
 }
 
 const schema = new Schema<ExternalUserGroupRelationDocument, ExternalUserGroupRelationModel>({
@@ -49,6 +49,6 @@ schema.statics.findAllUserIdsForUserGroups = UserGroupRelation.findAllUserIdsFor
 
 schema.statics.findAllUserGroupIdsRelatedToUser = UserGroupRelation.findAllUserGroupIdsRelatedToUser;
 
-schema.statics.findAllRelationForUser = UserGroupRelation.findAllRelationForUser;
+schema.statics.findAllGroupsForUser = UserGroupRelation.findAllGroupsForUser;
 
 export default getOrCreateModel<ExternalUserGroupRelationDocument, ExternalUserGroupRelationModel>('ExternalUserGroupRelation', schema);

+ 7 - 2
apps/app/src/interfaces/page-grant.ts

@@ -1,11 +1,16 @@
-import { PageGrant } from '@growi/core';
+import { PageGrant, GroupType } from '@growi/core';
 
+import { ExternalUserGroupDocument } from '~/features/external-user-group/server/models/external-user-group';
 import { UserGroupDocument } from '~/server/models/user-group';
 
 import { IPageGrantData } from './page';
 
+
+type UserGroupType = typeof GroupType.userGroup;
+type ExternalUserGroupType = typeof GroupType.externalUserGroup;
+export type ApplicableGroup = {type: UserGroupType, item: UserGroupDocument } | {type: ExternalUserGroupType, item: ExternalUserGroupDocument }
 export type IDataApplicableGroup = {
-  applicableGroups?: UserGroupDocument[]
+  applicableGroups?: ApplicableGroup[]
 }
 
 export type IDataApplicableGrant = null | IDataApplicableGroup;

+ 10 - 14
apps/app/src/server/models/user-group-relation.ts

@@ -1,4 +1,4 @@
-import type { IUserGroupRelation } from '@growi/core';
+import { isPopulated, type IUserGroupHasId, type IUserGroupRelation } from '@growi/core';
 import mongoose, { Model, Schema, Document } from 'mongoose';
 
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
@@ -27,7 +27,7 @@ export interface UserGroupRelationModel extends Model<UserGroupRelationDocument>
 
   countByGroupIdsAndUser: (userGroupIds: ObjectIdLike[], userData) => Promise<number>
 
-  findAllRelationForUser: (user) => Promise<UserGroupRelationDocument[]>
+  findAllGroupsForUser: (user) => Promise<UserGroupDocument[]>
 }
 
 /*
@@ -116,23 +116,19 @@ schema.statics.findAllRelationForUserGroups = function(userGroups) {
 };
 
 /**
- * find all user and group relation of User
+ * find all groups of User
  *
  * @static
  * @param {User} user
- * @returns {Promise<UserGroupRelation[]>}
+ * @returns {Promise<UserGroupDocument[]>}
  * @memberof UserGroupRelation
  */
-schema.statics.findAllRelationForUser = function(user): Promise<UserGroupRelationDocument[]> {
-  return this
-    .find({ relatedUser: user.id })
-    .populate('relatedGroup')
-    // filter documents only relatedGroup is not null
-    .then((userGroupRelations) => {
-      return userGroupRelations.filter((relation) => {
-        return relation.relatedGroup != null;
-      });
-    });
+schema.statics.findAllGroupsForUser = async function(user): Promise<UserGroupDocument[]> {
+  const userGroupRelations = await this.find({ relatedUser: user.id }).populate('relatedGroup');
+  const userGroups = userGroupRelations.map((relation) => {
+    return isPopulated(relation.relatedGroup) ? relation.relatedGroup as UserGroupDocument : null;
+  });
+  return userGroups.filter((group): group is NonNullable<UserGroupDocument> => group != null);
 };
 
 /**

+ 3 - 15
apps/app/src/server/routes/apiv3/me.ts

@@ -1,4 +1,4 @@
-import { type IUserHasId, isPopulated } from '@growi/core';
+import { type IUserHasId } from '@growi/core';
 import { Router, Request } from 'express';
 
 import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
@@ -27,13 +27,7 @@ module.exports = function(crowi) {
    */
   router.get('/user-groups', accessTokenParser, loginRequiredStrictly, async(req: AuthorizedRequest, res: ApiV3Response) => {
     try {
-      const userGroupRelations = await UserGroupRelation.findAllRelationForUser(req.user);
-      const userGroups = userGroupRelations.map((relation) => {
-        // relation.relatedGroup should be populated
-        return isPopulated(relation.relatedGroup) ? relation.relatedGroup : undefined;
-      })
-        // exclude undefined elements
-        .filter(elem => elem != null);
+      const userGroups = await UserGroupRelation.findAllGroupsForUser(req.user);
       return res.json(ApiResponse.success({ userGroups }));
     }
     catch (e) {
@@ -47,13 +41,7 @@ module.exports = function(crowi) {
    */
   router.get('/external-user-groups', accessTokenParser, loginRequiredStrictly, async(req: AuthorizedRequest, res: ApiV3Response) => {
     try {
-      const userGroupRelations = await ExternalUserGroupRelation.findAllRelationForUser(req.user);
-      const userGroups = userGroupRelations.map((relation) => {
-        // relation.relatedGroup should be populated
-        return isPopulated(relation.relatedGroup) ? relation.relatedGroup : undefined;
-      })
-      // exclude undefined elements
-        .filter(elem => elem != null);
+      const userGroups = await ExternalUserGroupRelation.findAllGroupsForUser(req.user);
       return res.json(ApiResponse.success({ userGroups }));
     }
     catch (e) {

+ 36 - 13
apps/app/src/server/service/page-grant.ts

@@ -1,6 +1,6 @@
 import {
   type IGrantedGroup,
-  PageGrant, type PageGrantCanBeOnTree,
+  PageGrant, type PageGrantCanBeOnTree, GroupType,
 } from '@growi/core';
 import {
   pagePathUtils, pathUtils, pageUtils,
@@ -9,11 +9,11 @@ import { et } from 'date-fns/locale';
 import escapeStringRegexp from 'escape-string-regexp';
 import mongoose from 'mongoose';
 
-import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
+import ExternalUserGroup, { ExternalUserGroupDocument } from '~/features/external-user-group/server/models/external-user-group';
 import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import { IRecordApplicableGrant } from '~/interfaces/page-grant';
 import { PageDocument, PageModel } from '~/server/models/page';
-import UserGroup from '~/server/models/user-group';
+import UserGroup, { UserGroupDocument } from '~/server/models/user-group';
 import { includesObjectIds, excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
@@ -493,12 +493,14 @@ class PageGrantService {
       [PageGrant.GRANT_RESTRICTED]: null, // any page can be restricted
     };
 
+    const userPossessedGroups = await this.getUserPossessedGroups(user);
+
     // -- Any grant is allowed if parent is null
     const isAnyGrantApplicable = page.parent == null;
     if (isAnyGrantApplicable) {
       data[PageGrant.GRANT_PUBLIC] = null;
       data[PageGrant.GRANT_OWNER] = null;
-      data[PageGrant.GRANT_USER_GROUP] = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
+      data[PageGrant.GRANT_USER_GROUP] = { applicableGroups: userPossessedGroups };
       return data;
     }
 
@@ -514,7 +516,7 @@ class PageGrantService {
     if (grant === PageGrant.GRANT_PUBLIC) {
       data[PageGrant.GRANT_PUBLIC] = null;
       data[PageGrant.GRANT_OWNER] = null;
-      data[PageGrant.GRANT_USER_GROUP] = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
+      data[PageGrant.GRANT_USER_GROUP] = { applicableGroups: userPossessedGroups };
     }
     else if (grant === PageGrant.GRANT_OWNER) {
       const grantedUser = grantedUsers[0];
@@ -533,14 +535,6 @@ class PageGrantService {
         throw Error('Group not found to calculate grant data.');
       }
 
-      const applicableUserGroups = (await Promise.all(targetUserGroups.map((group) => {
-        return UserGroupRelation.findGroupsWithDescendantsByGroupAndUser(group, user);
-      }))).flat();
-      const applicableExternalUserGroups = (await Promise.all(targetExternalUserGroups.map((group) => {
-        return ExternalUserGroupRelation.findGroupsWithDescendantsByGroupAndUser(group, user);
-      }))).flat();
-      const applicableGroups = [...applicableUserGroups, ...applicableExternalUserGroups];
-
       const isUserExistInUserGroup = (await Promise.all(targetUserGroups.map((group) => {
         return UserGroupRelation.countByGroupIdsAndUser([group._id], user);
       }))).some(count => count > 0);
@@ -552,12 +546,41 @@ class PageGrantService {
       if (isUserExistInGroup) {
         data[PageGrant.GRANT_OWNER] = null;
       }
+
+      const applicableUserGroups = (await Promise.all(targetUserGroups.map((group) => {
+        return UserGroupRelation.findGroupsWithDescendantsByGroupAndUser(group, user);
+      }))).flat();
+      const applicableExternalUserGroups = (await Promise.all(targetExternalUserGroups.map((group) => {
+        return ExternalUserGroupRelation.findGroupsWithDescendantsByGroupAndUser(group, user);
+      }))).flat();
+
+      const applicableGroups = [
+        ...applicableUserGroups.map((group) => {
+          return { type: GroupType.userGroup, item: group };
+        }),
+        ...applicableExternalUserGroups.map((group) => {
+          return { type: GroupType.externalUserGroup, item: group };
+        }),
+      ];
       data[PageGrant.GRANT_USER_GROUP] = { applicableGroups };
     }
 
     return data;
   }
 
+  async getUserPossessedGroups(user) {
+    const userPossessedUserGroups = await UserGroupRelation.findAllGroupsForUser(user);
+    const userPossessedExternalUserGroups = await ExternalUserGroupRelation.findAllGroupsForUser(user);
+    return [
+      ...userPossessedUserGroups.map((group) => {
+        return { type: GroupType.userGroup, item: group };
+      }),
+      ...userPossessedExternalUserGroups.map((group) => {
+        return { type: GroupType.externalUserGroup, item: group };
+      }),
+    ];
+  }
+
   /**
    * see: https://dev.growi.org/635a314eac6bcd85cbf359fc
    * @param {string} targetPath

+ 88 - 19
apps/app/test/integration/service/page-grant.test.js

@@ -1,10 +1,14 @@
-import { PageGrant } from '@growi/core';
+import { GroupType, PageGrant } from '@growi/core';
 import mongoose from 'mongoose';
 
+import { ExternalGroupProviderType } from '~/features/external-user-group/interfaces/external-user-group';
+import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
+import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import UserGroup from '~/server/models/user-group';
 
 import { getInstance } from '../setup-crowi';
 
+
 /*
  * There are 3 grant types to test.
  * GRANT_PUBLIC, GRANT_OWNER, GRANT_USER_GROUP
@@ -30,7 +34,11 @@ describe('PageGrantService', () => {
   let groupParent;
   let groupChild;
 
+  let externalGroupParent;
+  let externalGroupChild;
+
   const userGroupIdParent = new mongoose.Types.ObjectId();
+  const externalUserGroupIdParent = new mongoose.Types.ObjectId();
 
   let rootPage;
   let rootPublicPage;
@@ -115,6 +123,40 @@ describe('PageGrantService', () => {
       },
     ]);
 
+    await ExternalUserGroup.insertMany([
+      {
+        _id: externalUserGroupIdParent,
+        name: 'ExternalGroupParent',
+        externalId: 'ExternalGroupParent',
+        provider: ExternalGroupProviderType.ldap,
+        parent: null,
+      },
+      {
+        name: 'ExternalGroupChild',
+        externalId: 'ExternalGroupChild',
+        provider: ExternalGroupProviderType.ldap,
+        parent: externalUserGroupIdParent,
+      },
+    ]);
+
+    externalGroupParent = await ExternalUserGroup.findOne({ name: 'ExternalGroupParent' });
+    externalGroupChild = await ExternalUserGroup.findOne({ name: 'ExternalGroupChild' });
+
+    await ExternalUserGroupRelation.insertMany([
+      {
+        relatedGroup: externalGroupParent._id,
+        relatedUser: user1._id,
+      },
+      {
+        relatedGroup: externalGroupParent._id,
+        relatedUser: user2._id,
+      },
+      {
+        relatedGroup: externalGroupChild._id,
+        relatedUser: user1._id,
+      },
+    ]);
+
     // Root page (Depth: 0)
     rootPage = await Page.findOne({ path: '/' });
 
@@ -153,7 +195,7 @@ describe('PageGrantService', () => {
         creator: user1,
         lastUpdateUser: user1,
         grantedUsers: null,
-        grantedGroups: [{ item: groupParent._id, type: 'UserGroup' }],
+        grantedGroups: [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }],
         parent: rootPage._id,
       },
     ]);
@@ -183,7 +225,7 @@ describe('PageGrantService', () => {
         path: v4PageRootOnlyInsideTheGroupPagePath,
         grant: Page.GRANT_USER_GROUP,
         parent: null,
-        grantedGroups: [{ item: groupParent._id, type: 'UserGroup' }],
+        grantedGroups: [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }],
       },
     ]);
 
@@ -280,7 +322,7 @@ describe('PageGrantService', () => {
         creator: user1,
         lastUpdateUser: user1,
         grantedUsers: null,
-        grantedGroups: [{ item: groupParent._id, type: 'UserGroup' }],
+        grantedGroups: [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }],
         parent: emptyPage3._id,
       },
       {
@@ -289,7 +331,7 @@ describe('PageGrantService', () => {
         creator: user1,
         lastUpdateUser: user1,
         grantedUsers: null,
-        grantedGroups: [{ item: groupChild._id, type: 'UserGroup' }],
+        grantedGroups: [{ item: groupChild._id, type: GroupType.userGroup }, { item: externalGroupChild._id, type: GroupType.externalUserGroup }],
         parent: emptyPage3._id,
       },
       {
@@ -345,10 +387,10 @@ describe('PageGrantService', () => {
       const targetPath = '/NEW_GroupParent';
       const grant = Page.GRANT_USER_GROUP;
       const grantedUserIds = null;
-      const grantedGroupIdš = [{ item: groupParent._id, type: 'UserGroup' }];
+      const grantedGroupIds = [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }];
       const shouldCheckDescendants = false;
 
-      const result = await pageGrantService.isGrantNormalized(user1, targetPath, grant, grantedUserIds, grantedGroupIdš, shouldCheckDescendants);
+      const result = await pageGrantService.isGrantNormalized(user1, targetPath, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
 
       expect(result).toBe(true);
     });
@@ -369,7 +411,7 @@ describe('PageGrantService', () => {
       const targetPath = `${pageRootGroupParentPath}/NEW`;
       const grant = Page.GRANT_USER_GROUP;
       const grantedUserIds = null;
-      const grantedGroupIds = [{ item: groupParent._id, type: 'UserGroup' }];
+      const grantedGroupIds = [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }];
       const shouldCheckDescendants = false;
 
       const result = await pageGrantService.isGrantNormalized(user1, targetPath, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
@@ -417,7 +459,7 @@ describe('PageGrantService', () => {
       const targetPath = `${pageE3GroupChildPath}/NEW`;
       const grant = Page.GRANT_USER_GROUP;
       const grantedUserIds = null;
-      const grantedGroupIds = [{ item: groupParent._id, type: 'UserGroup' }];
+      const grantedGroupIds = [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }];
       const shouldCheckDescendants = false;
 
       const result = await pageGrantService.isGrantNormalized(user1, targetPath, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
@@ -455,7 +497,7 @@ describe('PageGrantService', () => {
       const targetPath = emptyPagePath3;
       const grant = Page.GRANT_USER_GROUP;
       const grantedUserIds = null;
-      const grantedGroupIds = [{ item: groupParent._id, type: 'UserGroup' }];
+      const grantedGroupIds = [{ item: groupParent._id, type: GroupType.userGroup }, { item: externalGroupParent._id, type: GroupType.externalUserGroup }];
       const shouldCheckDescendants = true;
 
       const result = await pageGrantService.isGrantNormalized(user1, targetPath, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
@@ -490,7 +532,16 @@ describe('PageGrantService', () => {
 
     // parent property of all private pages is null
     test('Any grant is allowed if parent is null', async() => {
-      const userGroupRelation = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user1);
+      const userPossessedUserGroups = await UserGroupRelation.findAllGroupsForUser(user1);
+      const userPossessedExternalUserGroups = await ExternalUserGroupRelation.findAllGroupsForUser(user1);
+      const userPossessedGroups = [
+        ...userPossessedUserGroups.map((group) => {
+          return { type: GroupType.userGroup, item: group };
+        }),
+        ...userPossessedExternalUserGroups.map((group) => {
+          return { type: GroupType.externalUserGroup, item: group };
+        }),
+      ];
 
       // OnlyMe
       const rootOnlyMePage = await Page.findOne({ path: v4PageRootOnlyMePagePath });
@@ -500,7 +551,7 @@ describe('PageGrantService', () => {
           [PageGrant.GRANT_PUBLIC]: null,
           [PageGrant.GRANT_RESTRICTED]: null,
           [PageGrant.GRANT_OWNER]: null,
-          [PageGrant.GRANT_USER_GROUP]: userGroupRelation,
+          [PageGrant.GRANT_USER_GROUP]: { applicableGroups: userPossessedGroups },
         },
       );
 
@@ -512,7 +563,7 @@ describe('PageGrantService', () => {
           [PageGrant.GRANT_PUBLIC]: null,
           [PageGrant.GRANT_RESTRICTED]: null,
           [PageGrant.GRANT_OWNER]: null,
-          [PageGrant.GRANT_USER_GROUP]: userGroupRelation,
+          [PageGrant.GRANT_USER_GROUP]: { applicableGroups: userPossessedGroups },
         },
       );
 
@@ -524,14 +575,23 @@ describe('PageGrantService', () => {
           [PageGrant.GRANT_PUBLIC]: null,
           [PageGrant.GRANT_RESTRICTED]: null,
           [PageGrant.GRANT_OWNER]: null,
-          [PageGrant.GRANT_USER_GROUP]: userGroupRelation,
+          [PageGrant.GRANT_USER_GROUP]: { applicableGroups: userPossessedGroups },
         },
       );
     });
 
 
     test('Any grant is allowed if parent is public', async() => {
-      const userGroupRelation = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user1);
+      const userPossessedUserGroups = await UserGroupRelation.findAllGroupsForUser(user1);
+      const userPossessedExternalUserGroups = await ExternalUserGroupRelation.findAllGroupsForUser(user1);
+      const userPossessedGroups = [
+        ...userPossessedUserGroups.map((group) => {
+          return { type: GroupType.userGroup, item: group };
+        }),
+        ...userPossessedExternalUserGroups.map((group) => {
+          return { type: GroupType.externalUserGroup, item: group };
+        }),
+      ];
 
       // OnlyMe
       const publicOnlyMePage = await Page.findOne({ path: pagePublicOnlyMePath });
@@ -541,7 +601,7 @@ describe('PageGrantService', () => {
           [PageGrant.GRANT_PUBLIC]: null,
           [PageGrant.GRANT_RESTRICTED]: null,
           [PageGrant.GRANT_OWNER]: null,
-          [PageGrant.GRANT_USER_GROUP]: userGroupRelation,
+          [PageGrant.GRANT_USER_GROUP]: { applicableGroups: userPossessedGroups },
         },
       );
 
@@ -553,7 +613,7 @@ describe('PageGrantService', () => {
           [PageGrant.GRANT_PUBLIC]: null,
           [PageGrant.GRANT_RESTRICTED]: null,
           [PageGrant.GRANT_OWNER]: null,
-          [PageGrant.GRANT_USER_GROUP]: userGroupRelation,
+          [PageGrant.GRANT_USER_GROUP]: { applicableGroups: userPossessedGroups },
         },
       );
 
@@ -565,7 +625,7 @@ describe('PageGrantService', () => {
           [PageGrant.GRANT_PUBLIC]: null,
           [PageGrant.GRANT_RESTRICTED]: null,
           [PageGrant.GRANT_OWNER]: null,
-          [PageGrant.GRANT_USER_GROUP]: userGroupRelation,
+          [PageGrant.GRANT_USER_GROUP]: { applicableGroups: userPossessedGroups },
         },
       );
     });
@@ -633,7 +693,16 @@ describe('PageGrantService', () => {
     });
 
     test('"GRANT_USER_GROUP" is allowed if the parent\'s grant is GRANT_USER_GROUP and the user is included in the group', async() => {
-      const applicableGroups = await UserGroupRelation.findGroupsWithDescendantsByGroupAndUser(groupParent, user1);
+      const userGroups = await UserGroupRelation.findGroupsWithDescendantsByGroupAndUser(groupParent, user1);
+      const externalUserGroups = await ExternalUserGroupRelation.findGroupsWithDescendantsByGroupAndUser(externalGroupParent, user1);
+      const applicableGroups = [
+        ...userGroups.map((group) => {
+          return { type: GroupType.userGroup, item: group };
+        }),
+        ...externalUserGroups.map((group) => {
+          return { type: GroupType.externalUserGroup, item: group };
+        }),
+      ];
 
       // Public
       const onlyInsideGroupPublicPage = await Page.findOne({ path: pageOnlyInsideTheGroupPublicPath });