Procházet zdrojové kódy

fix api and services to use grantedUserGroups

Futa Arai před 2 roky
rodič
revize
c81994ddc9

+ 31 - 19
apps/app/src/server/routes/apiv3/page.js

@@ -2,12 +2,14 @@ import {
   pagePathUtils, AllSubscriptionStatusType, SubscriptionStatusType, ErrorV3,
   pagePathUtils, AllSubscriptionStatusType, SubscriptionStatusType, ErrorV3,
 } from '@growi/core';
 } from '@growi/core';
 
 
+import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
 import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
 import Subscription from '~/server/models/subscription';
 import Subscription from '~/server/models/subscription';
 import UserGroup from '~/server/models/user-group';
 import UserGroup from '~/server/models/user-group';
+import { divideByType } from '~/server/util/granted-group';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:routes:apiv3:page'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:page'); // eslint-disable-line no-unused-vars
@@ -194,7 +196,7 @@ module.exports = (crowi) => {
     updateGrant: [
     updateGrant: [
       param('pageId').isMongoId().withMessage('pageId is required'),
       param('pageId').isMongoId().withMessage('pageId is required'),
       body('grant').isInt().withMessage('grant is required'),
       body('grant').isInt().withMessage('grant is required'),
-      body('grantedGroup').optional().isMongoId().withMessage('grantedGroup must be a mongo id'),
+      body('grantedGroups').optional().isArray().withMessage('grantedGroups must be an array'),
     ],
     ],
     export: [
     export: [
       query('format').isString().isIn(['md', 'pdf']),
       query('format').isString().isIn(['md', 'pdf']),
@@ -454,27 +456,32 @@ module.exports = (crowi) => {
     }
     }
 
 
     const {
     const {
-      path, grant, grantedUsers, grantedGroup,
+      path, grant, grantedUsers, grantedGroups,
     } = page;
     } = page;
 
 
     let isGrantNormalized;
     let isGrantNormalized;
     try {
     try {
-      isGrantNormalized = await crowi.pageGrantService.isGrantNormalized(req.user, path, grant, grantedUsers, grantedGroup, false, false);
+      isGrantNormalized = await crowi.pageGrantService.isGrantNormalized(req.user, path, grant, grantedUsers, grantedGroups, false, false);
     }
     }
     catch (err) {
     catch (err) {
       logger.error('Error occurred while processing isGrantNormalized.', err);
       logger.error('Error occurred while processing isGrantNormalized.', err);
       return res.apiv3Err(err, 500);
       return res.apiv3Err(err, 500);
     }
     }
 
 
-    const currentPageUserGroup = await UserGroup.findOne({ _id: grantedGroup });
+    const { grantedUserGroups, grantedExternalUserGroups } = divideByType(grantedGroups);
+    const currentPageUserGroups = await UserGroup.find({ _id: { $in: grantedUserGroups } });
+    const currentPageExternalUserGroups = await ExternalUserGroup.find({ _id: { $in: grantedExternalUserGroups } });
+    currentPageUserGroups.map((group) => {
+      return { id: group._id, name: group.name };
+    });
     const currentPageGrant = {
     const currentPageGrant = {
       grant,
       grant,
-      grantedGroup: currentPageUserGroup != null
-        ? {
-          id: currentPageUserGroup._id,
-          name: currentPageUserGroup.name,
-        }
-        : null,
+      grantedUserGroups: currentPageUserGroups.map((group) => {
+        return { id: group._id, name: group.name };
+      }),
+      grantedExternalUserGroups: currentPageExternalUserGroups.map((group) => {
+        return { id: group._id, name: group.name };
+      }),
     };
     };
 
 
     // page doesn't have parent page
     // page doesn't have parent page
@@ -499,15 +506,20 @@ module.exports = (crowi) => {
       return res.apiv3({ isGrantNormalized, grantData });
       return res.apiv3({ isGrantNormalized, grantData });
     }
     }
 
 
-    const parentPageUserGroup = await UserGroup.findOne({ _id: parentPage.grantedGroup });
+    const {
+      grantedUserGroups: parentGrantedUserGroupIds,
+      grantedExternalUserGroups: parentGrantedExternalUserGroupIds,
+    } = divideByType(parentPage.grantedGroup);
+    const parentPageUserGroups = await UserGroup.find({ _id: { $in: parentGrantedUserGroupIds } });
+    const parentPageExternalUserGroups = await ExternalUserGroup.find({ _id: { $in: parentGrantedExternalUserGroupIds } });
     const parentPageGrant = {
     const parentPageGrant = {
       grant: parentPage.grant,
       grant: parentPage.grant,
-      grantedGroup: parentPageUserGroup != null
-        ? {
-          id: parentPageUserGroup._id,
-          name: parentPageUserGroup.name,
-        }
-        : null,
+      grantedUserGroups: parentPageUserGroups.map((group) => {
+        return { id: group._id, name: group.name };
+      }),
+      grantedExternalUserGroups: parentPageExternalUserGroups.map((group) => {
+        return { id: group._id, name: group.name };
+      }),
     };
     };
 
 
     const grantData = {
     const grantData = {
@@ -544,7 +556,7 @@ module.exports = (crowi) => {
 
 
   router.put('/:pageId/grant', loginRequiredStrictly, excludeReadOnlyUser, validator.updateGrant, apiV3FormValidator, async(req, res) => {
   router.put('/:pageId/grant', loginRequiredStrictly, excludeReadOnlyUser, validator.updateGrant, apiV3FormValidator, async(req, res) => {
     const { pageId } = req.params;
     const { pageId } = req.params;
-    const { grant, grantedGroup } = req.body;
+    const { grant, grantedGroups } = req.body;
 
 
     const Page = crowi.model('Page');
     const Page = crowi.model('Page');
 
 
@@ -558,7 +570,7 @@ module.exports = (crowi) => {
     let data;
     let data;
     try {
     try {
       const shouldUseV4Process = false;
       const shouldUseV4Process = false;
-      const grantData = { grant, grantedGroup };
+      const grantData = { grant, grantedGroups };
       data = await this.crowi.pageService.updateGrant(page, req.user, grantData, shouldUseV4Process);
       data = await this.crowi.pageService.updateGrant(page, req.user, grantData, shouldUseV4Process);
     }
     }
     catch (err) {
     catch (err) {

+ 5 - 1
apps/app/src/server/routes/apiv3/user-group.js

@@ -812,7 +812,11 @@ module.exports = (crowi) => {
     try {
     try {
       const { docs, totalDocs } = await Page.paginate({
       const { docs, totalDocs } = await Page.paginate({
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroup: { $in: [id] },
+        grantedGroups: {
+          $elemMatch: {
+            item: id,
+          },
+        },
       }, {
       }, {
         offset,
         offset,
         limit,
         limit,

+ 3 - 3
apps/app/src/server/routes/page.js

@@ -324,7 +324,7 @@ module.exports = function(crowi, app) {
     const body = req.body.body || null;
     const body = req.body.body || null;
     let pagePath = req.body.path || null;
     let pagePath = req.body.path || null;
     const grant = req.body.grant || null;
     const grant = req.body.grant || null;
-    const grantUserGroupId = req.body.grantUserGroupId || null;
+    const grantUserGroupIds = req.body.grantUserGroupIds || null;
     const overwriteScopesOfDescendants = req.body.overwriteScopesOfDescendants || null;
     const overwriteScopesOfDescendants = req.body.overwriteScopesOfDescendants || null;
     const isSlackEnabled = !!req.body.isSlackEnabled; // cast to boolean
     const isSlackEnabled = !!req.body.isSlackEnabled; // cast to boolean
     const slackChannels = req.body.slackChannels || null;
     const slackChannels = req.body.slackChannels || null;
@@ -346,7 +346,7 @@ module.exports = function(crowi, app) {
     const options = { overwriteScopesOfDescendants };
     const options = { overwriteScopesOfDescendants };
     if (grant != null) {
     if (grant != null) {
       options.grant = grant;
       options.grant = grant;
-      options.grantUserGroupId = grantUserGroupId;
+      options.grantUserGroupIds = grantUserGroupIds;
     }
     }
 
 
     const createdPage = await crowi.pageService.create(pagePath, body, req.user, options);
     const createdPage = await crowi.pageService.create(pagePath, body, req.user, options);
@@ -922,7 +922,7 @@ module.exports = function(crowi, app) {
     req.body.body = page.revision.body;
     req.body.body = page.revision.body;
     req.body.grant = page.grant;
     req.body.grant = page.grant;
     req.body.grantedUsers = page.grantedUsers;
     req.body.grantedUsers = page.grantedUsers;
-    req.body.grantUserGroupId = page.grantedGroup;
+    req.body.grantUserGroupIds = page.grantedGroups;
     req.body.pageTags = originTags;
     req.body.pageTags = originTags;
 
 
     return api.create(req, res);
     return api.create(req, res);

+ 7 - 5
apps/app/src/server/service/search-delegator/elasticsearch.ts

@@ -381,16 +381,18 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
       });
       });
     }
     }
 
 
-    let grantedGroupId = null;
-    if (page.grantedGroup != null) {
-      const groupId = (page.grantedGroup._id == null) ? page.grantedGroup : page.grantedGroup._id;
-      grantedGroupId = groupId.toString();
+    let grantedGroupIds = null;
+    if (page.grantedGroups != null) {
+      grantedGroupIds = page.grantedGroups.map((group) => {
+        const groupId = (group.item._id == null) ? group.item : group.item._id;
+        return groupId.toString();
+      });
     }
     }
 
 
     return {
     return {
       grant: page.grant,
       grant: page.grant,
       granted_users: grantedUserIds,
       granted_users: grantedUserIds,
-      granted_group: grantedGroupId,
+      granted_groups: grantedGroupIds,
     };
     };
   }
   }
 
 

+ 3 - 2
apps/app/src/server/service/search.ts

@@ -14,6 +14,7 @@ import NamedQuery from '../models/named-query';
 import { PageModel } from '../models/page';
 import { PageModel } from '../models/page';
 import { serializeUserSecurely } from '../models/serializers/user-serializer';
 import { serializeUserSecurely } from '../models/serializers/user-serializer';
 import { SearchError } from '../models/vo/search-error';
 import { SearchError } from '../models/vo/search-error';
+import { hasIntersection } from '../util/compare-objectId';
 
 
 import ElasticsearchDelegator from './search-delegator/elasticsearch';
 import ElasticsearchDelegator from './search-delegator/elasticsearch';
 import PrivateLegacyPagesDelegator from './search-delegator/private-legacy-pages';
 import PrivateLegacyPagesDelegator from './search-delegator/private-legacy-pages';
@@ -491,7 +492,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
 
     const testGrant = pageData.grant;
     const testGrant = pageData.grant;
     const testGrantedUser = pageData.grantedUsers?.[0];
     const testGrantedUser = pageData.grantedUsers?.[0];
-    const testGrantedGroup = pageData.grantedGroup;
+    const testGrantedGroups = pageData.grantedGroups;
 
 
     if (testGrant === Page.GRANT_RESTRICTED) {
     if (testGrant === Page.GRANT_RESTRICTED) {
       return false;
       return false;
@@ -506,7 +507,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
     if (testGrant === Page.GRANT_USER_GROUP) {
     if (testGrant === Page.GRANT_USER_GROUP) {
       if (userGroups == null) return false;
       if (userGroups == null) return false;
 
 
-      return userGroups.map(id => id.toString()).includes(testGrantedGroup.toString());
+      return hasIntersection(userGroups.map(id => id.toString()), testGrantedGroups);
     }
     }
 
 
     return true;
     return true;

+ 35 - 12
apps/app/src/server/util/compare-objectId.spec.ts

@@ -1,28 +1,51 @@
 import { Types } from 'mongoose';
 import { Types } from 'mongoose';
 
 
-import { includesObjectIds } from './compare-objectId';
+import { hasIntersection, includesObjectIds } from './compare-objectId';
 
 
-describe('includesObjectIds', () => {
+describe('Objectid comparison utils', () => {
   const id1 = new Types.ObjectId();
   const id1 = new Types.ObjectId();
   const id2 = new Types.ObjectId();
   const id2 = new Types.ObjectId();
   const id3 = new Types.ObjectId();
   const id3 = new Types.ObjectId();
   const id4 = new Types.ObjectId();
   const id4 = new Types.ObjectId();
 
 
-  describe('When subset of array given', () => {
-    const arr = [id1, id2, id3, id4];
-    const subset = [id1, id4];
+  describe('includesObjectIds', () => {
+    describe('When subset of array given', () => {
+      const arr = [id1, id2, id3, id4];
+      const subset = [id1, id4];
 
 
-    it('returns true', () => {
-      expect(includesObjectIds(arr, subset)).toBe(true);
+      it('returns true', () => {
+        expect(includesObjectIds(arr, subset)).toBe(true);
+      });
+    });
+
+    describe('When set that intersects with array given', () => {
+      const arr = [id1, id2, id3];
+      const subset = [id1, id4];
+
+      it('returns false', () => {
+        expect(includesObjectIds(arr, subset)).toBe(false);
+      });
     });
     });
   });
   });
 
 
-  describe('When set that intersects with array given', () => {
-    const arr = [id1, id2, id3];
-    const subset = [id1, id4];
+  describe('hasIntersection', () => {
+    describe('When arrays have intersection', () => {
+      const arr1 = [id1, id2, id3, id4];
+      const arr2 = [id1, id4];
 
 
-    it('returns false', () => {
-      expect(includesObjectIds(arr, subset)).toBe(false);
+      it('returns true', () => {
+        expect(hasIntersection(arr1, arr2)).toBe(true);
+      });
+    });
+
+    describe('When arrays don\'t have intersection', () => {
+      const arr1 = [id1, id2];
+      const arr2 = [id3, id4];
+
+      it('returns false', () => {
+        expect(hasIntersection(arr1, arr2)).toBe(false);
+      });
     });
     });
   });
   });
+
 });
 });

+ 13 - 0
apps/app/src/server/util/compare-objectId.ts

@@ -18,6 +18,19 @@ export const includesObjectIds = (arr: ObjectIdLike[], potentialSubset: ObjectId
   return _potentialSubset.every(id => _arr.includes(id));
   return _potentialSubset.every(id => _arr.includes(id));
 };
 };
 
 
+/**
+ * Check if 2 arrays have an intersection
+ * @param arr1 an array with ObjectIds
+ * @param arr2 another array with ObjectIds
+ * @returns Whether or not arr1 and arr2 have an intersection
+ */
+export const hasIntersection = (arr1: ObjectIdLike[], arr2: ObjectIdLike[]): boolean => {
+  const _arr1 = arr1.map(i => i.toString());
+  const _arr2 = arr2.map(i => i.toString());
+
+  return _arr1.some(item => _arr2.includes(item));
+};
+
 /**
 /**
  * Exclude ObjectIds which exist in testIds from targetIds
  * Exclude ObjectIds which exist in testIds from targetIds
  * @param targetIds Array of mongoose.Types.ObjectId
  * @param targetIds Array of mongoose.Types.ObjectId

+ 5 - 1
apps/app/src/server/util/granted-group.ts

@@ -2,13 +2,17 @@ import { GrantedGroup, GroupType } from '@growi/core';
 
 
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
 
 
-export const divideByType = (grantedGroups: GrantedGroup[]): {
+export const divideByType = (grantedGroups: GrantedGroup[] | null): {
   grantedUserGroups: ObjectIdLike[];
   grantedUserGroups: ObjectIdLike[];
   grantedExternalUserGroups: ObjectIdLike[];
   grantedExternalUserGroups: ObjectIdLike[];
 } => {
 } => {
   const grantedUserGroups: ObjectIdLike[] = [];
   const grantedUserGroups: ObjectIdLike[] = [];
   const grantedExternalUserGroups: ObjectIdLike[] = [];
   const grantedExternalUserGroups: ObjectIdLike[] = [];
 
 
+  if (grantedGroups == null) {
+    return { grantedUserGroups, grantedExternalUserGroups };
+  }
+
   grantedGroups.forEach((group) => {
   grantedGroups.forEach((group) => {
     const id = typeof group.item === 'string' ? group.item : group.item._id;
     const id = typeof group.item === 'string' ? group.item : group.item._id;
     if (group.type === GroupType.userGroup) {
     if (group.type === GroupType.userGroup) {

+ 4 - 4
apps/app/test/integration/service/v5.page.test.ts

@@ -385,7 +385,7 @@ describe('Test page service methods', () => {
           status: 'published',
           status: 'published',
           grant: 1,
           grant: 1,
           grantedUsers: [],
           grantedUsers: [],
-          grantedGroup: null,
+          grantedGroups: null,
           creator: dummyUser1._id,
           creator: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
         },
         },
@@ -414,7 +414,7 @@ describe('Test page service methods', () => {
           status: 'published',
           status: 'published',
           grant: 1,
           grant: 1,
           grantedUsers: [],
           grantedUsers: [],
-          grantedGroup: null,
+          grantedGroups: null,
           creator: dummyUser1._id,
           creator: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
         },
         },
@@ -443,7 +443,7 @@ describe('Test page service methods', () => {
           status: 'published',
           status: 'published',
           grant: 1,
           grant: 1,
           grantedUsers: [],
           grantedUsers: [],
-          grantedGroup: null,
+          grantedGroups: null,
           creator: dummyUser1._id,
           creator: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
         },
         },
@@ -472,7 +472,7 @@ describe('Test page service methods', () => {
           status: 'published',
           status: 'published',
           grant: Page.GRANT_PUBLIC,
           grant: Page.GRANT_PUBLIC,
           grantedUsers: [],
           grantedUsers: [],
-          grantedGroup: null,
+          grantedGroups: null,
           creator: dummyUser1._id,
           creator: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
         },
         },

+ 1 - 1
apps/app/test/integration/service/v5.public-page.test.ts

@@ -432,7 +432,7 @@ describe('PageService page operations with only public pages', () => {
           status: 'published',
           status: 'published',
           grant: 1,
           grant: 1,
           grantedUsers: [],
           grantedUsers: [],
-          grantedGroup: null,
+          grantedGroups: null,
           creator: dummyUser1._id,
           creator: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
           lastUpdateUser: dummyUser1._id,
         },
         },

+ 2 - 2
packages/core/src/interfaces/page.ts

@@ -48,11 +48,11 @@ export type IPagePopulatedToList = Omit<IPageHasId, 'lastUpdateUser'> & {
   lastUpdateUser: IUserHasId,
   lastUpdateUser: IUserHasId,
 }
 }
 
 
-export type IPagePopulatedToShowRevision = Omit<IPageHasId, 'lastUpdateUser'|'creator'|'deleteUser'|'grantedGroup'|'revision'|'author'> & {
+export type IPagePopulatedToShowRevision = Omit<IPageHasId, 'lastUpdateUser'|'creator'|'deleteUser'|'grantedGroups'|'revision'|'author'> & {
   lastUpdateUser: IUserHasId,
   lastUpdateUser: IUserHasId,
   creator: IUserHasId | null,
   creator: IUserHasId | null,
   deleteUser: IUserHasId,
   deleteUser: IUserHasId,
-  grantedGroup: IUserGroupHasId,
+  grantedGroups: { type: string, item: IUserGroupHasId }[],
   revision: IRevisionHasId,
   revision: IRevisionHasId,
   author: IUserHasId,
   author: IUserHasId,
 }
 }