Просмотр исходного кода

add userRelatedGroups as params to canDeleteCompletely

Futa Arai 2 лет назад
Родитель
Сommit
61bc5268ed

+ 7 - 2
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -8,6 +8,7 @@ import { query, oneOf } from 'express-validator';
 import mongoose from 'mongoose';
 
 
+import { IPageGrantService } from '~/server/service/page-grant';
 import loggerFactory from '~/utils/logger';
 
 import Crowi from '../../crowi';
@@ -119,6 +120,8 @@ const routerFactory = (crowi: Crowi): Router => {
     const Bookmark = crowi.model('Bookmark');
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const pageService: PageService = crowi.pageService!;
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const pageGrantService: IPageGrantService = crowi.pageGrantService!;
 
     try {
       const pages = pageIds != null
@@ -140,12 +143,14 @@ const routerFactory = (crowi: Crowi): Router => {
       const idToPageInfoMap: Record<string, IPageInfo | IPageInfoForListing> = {};
 
       const isGuestUser = req.user == null;
+
+      const userRelatedGroups = await pageGrantService.getUserRelatedGroups(req.user);
+
       for (const page of pages) {
         // construct isIPageInfoForListing
         const basicPageInfo = pageService.constructBasicPageInfo(page, isGuestUser);
 
-        // eslint-disable-next-line no-await-in-loop
-        const canDeleteCompletely = await pageService.canDeleteCompletely(page, req.user, false); // use normal delete config
+        const canDeleteCompletely = pageService.canDeleteCompletely(page, req.user, false, userRelatedGroups); // use normal delete config
 
         const pageInfo = (!isIPageInfoForEntity(basicPageInfo))
           ? basicPageInfo

+ 2 - 1
apps/app/src/server/routes/page.js

@@ -753,7 +753,8 @@ module.exports = function(crowi, app) {
 
     try {
       if (isCompletely) {
-        const canDeleteCompletely = await crowi.pageService.canDeleteCompletely(page, req.user, isRecursively);
+        const userRelatedGroups = await crowi.pageGrantService.getUserRelatedGroups(req.user);
+        const canDeleteCompletely = crowi.pageService.canDeleteCompletely(page, req.user, isRecursively, userRelatedGroups);
         if (!canDeleteCompletely) {
           return res.json(ApiResponse.error('You cannot delete this page completely', 'complete_deletion_not_allowed_for_user'));
         }

+ 19 - 2
apps/app/src/server/service/page-grant.ts

@@ -1,6 +1,6 @@
 import {
   type IGrantedGroup,
-  PageGrant, GroupType, getIdForRef,
+  PageGrant, GroupType, getIdForRef, isPopulated,
 } from '@growi/core';
 import {
   pagePathUtils, pathUtils, pageUtils,
@@ -93,7 +93,9 @@ export interface IPageGrantService {
     operator, updateGrant?: PageGrant, grantGroupIds?: IGrantedGroup[],
   ) => Promise<UpdateGrantInfo>,
   canOverwriteDescendants: (targetPath: string, operator: { _id: ObjectIdLike }, updateGrantInfo: UpdateGrantInfo) => Promise<boolean>,
-  validateGrantChange: (user, previousGrantedGroupIds: IGrantedGroup[], grant?: PageGrant, grantedGroupIds?: IGrantedGroup[]) => Promise<boolean>
+  validateGrantChange: (user, previousGrantedGroupIds: IGrantedGroup[], grant?: PageGrant, grantedGroupIds?: IGrantedGroup[]) => Promise<boolean>,
+  getUserRelatedGroups: (user) => Promise<PopulatedGrantedGroup[]>,
+  filterGrantedGroupsByIds: (page: PageDocument, groupIds: string[]) => IGrantedGroup[],
 }
 
 class PageGrantService implements IPageGrantService {
@@ -642,6 +644,9 @@ class PageGrantService implements IPageGrantService {
     return data;
   }
 
+  /*
+   * get all groups that user is related to
+   */
   async getUserRelatedGroups(user): Promise<PopulatedGrantedGroup[]> {
     const userRelatedUserGroups = await UserGroupRelation.findAllGroupsForUser(user);
     const userRelatedExternalUserGroups = await ExternalUserGroupRelation.findAllGroupsForUser(user);
@@ -655,6 +660,18 @@ class PageGrantService implements IPageGrantService {
     ];
   }
 
+  /*
+   * filter page.grantedGroups to groups with id inside groupIds
+   */
+  filterGrantedGroupsByIds(page: PageDocument, groupIds: string[]): IGrantedGroup[] {
+    return page.grantedGroups?.filter((group) => {
+      if (isPopulated(group.item)) {
+        return groupIds.includes(group.item._id.toString());
+      }
+      return groupIds.includes(group.item);
+    }) || [];
+  }
+
   /**
    * see: https://dev.growi.org/635a314eac6bcd85cbf359fc
    * @param {string} targetPath

+ 25 - 6
apps/app/src/server/service/page/index.ts

@@ -23,6 +23,7 @@ import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
 import {
   PageDeleteConfigValue, IPageDeleteConfigValueToProcessValidation, PageSingleDeleteCompConfigValue,
 } from '~/interfaces/page-delete-config';
+import { PopulatedGrantedGroup } from '~/interfaces/page-grant';
 import {
   type IPageOperationProcessInfo, type IPageOperationProcessData, PageActionStage, PageActionType,
 } from '~/interfaces/page-operation';
@@ -192,7 +193,19 @@ class PageService implements IPageService {
     return this.pageEvent;
   }
 
-  async canDeleteCompletely(page: PageDocument, operator: any | null, isRecursively: boolean): Promise<boolean> {
+  /**
+   * Check if page can be deleted completely.
+   * Use pageGrantService.getUserRelatedGroups before execution of canDeleteCompletely to get value for userRelatedGroups.
+   * Do NOT use getUserRelatedGrantedGroups inside this method, because canDeleteCompletely should not be async as for now.
+   * The reason for this is because canDeleteCompletely is called in /page-listing/info in a for loop,
+   * and /page-listing/info should not be an execution heavy API.
+   */
+  canDeleteCompletely(
+      page: PageDocument,
+      operator: any | null,
+      isRecursively: boolean,
+      userRelatedGroups: PopulatedGrantedGroup[],
+  ): boolean {
     if (operator == null || isTopPage(page.path) || isUsersTopPage(page.path)) return false;
 
     const pageCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority');
@@ -205,8 +218,10 @@ class PageService implements IPageService {
     const isAuthor = operator?._id == null ? false : operator._id.equals(page.creator);
     const isAdminOrAuthor = isAdmin || isAuthor;
 
-    if (!isAdminOrAuthor && pageCompleteDeletionAuthority === PageSingleDeleteCompConfigValue.Anyone && isAllGroupMembershipRequiredForPageCompleteDeletion) {
-      const userRelatedGrantedGroups = await this.getUserRelatedGrantedGroups(page, operator);
+    if (page.grant === PageGrant.GRANT_USER_GROUP
+      && !isAdminOrAuthor && pageCompleteDeletionAuthority === PageSingleDeleteCompConfigValue.Anyone
+      && isAllGroupMembershipRequiredForPageCompleteDeletion) {
+      const userRelatedGrantedGroups = this.pageGrantService.filterGrantedGroupsByIds(page, userRelatedGroups.map(ug => ug.item._id.toString()));
       if (userRelatedGrantedGroups.length !== page.grantedGroups.length) {
         return false;
       }
@@ -297,11 +312,12 @@ class PageService implements IPageService {
       pages: PageDocument[],
       user: IUserHasId,
       isRecursively: boolean,
-      canDeleteFunction: (page: PageDocument, operator: any, isRecursively: boolean) => Promise<boolean> | boolean,
+      canDeleteFunction: (page: PageDocument, operator: any, isRecursively: boolean, userRelatedGroups: PopulatedGrantedGroup[]) => boolean,
   ): Promise<PageDocument[]> {
+    const userRelatedGroups = await this.pageGrantService.getUserRelatedGroups(user);
     const filteredPages = pages.filter(async(p) => {
       if (p.isEmpty) return true;
-      const canDelete = await canDeleteFunction(p, user, isRecursively);
+      const canDelete = await canDeleteFunction(p, user, isRecursively, userRelatedGroups);
       return canDelete;
     });
 
@@ -394,8 +410,11 @@ class PageService implements IPageService {
       const notEmptyClosestAncestor = await Page.findNonEmptyClosestAncestor(page.path);
       creatorId = notEmptyClosestAncestor.creator;
     }
+
+    const userRelatedGroups = await this.pageGrantService.getUserRelatedGroups(user);
+
     const isDeletable = this.canDelete(page, user, false);
-    const isAbleToDeleteCompletely = await this.canDeleteCompletely(page, user, false); // use normal delete config
+    const isAbleToDeleteCompletely = this.canDeleteCompletely(page, user, false, userRelatedGroups); // use normal delete config
 
     return {
       data: page,

+ 8 - 4
apps/app/test/integration/service/page.test.js

@@ -770,7 +770,8 @@ describe('PageService', () => {
         });
 
         test('is not deletable', async() => {
-          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser1);
+          const userRelatedGroups = await crowi.pageGrantService.getUserRelatedGroups(testUser1);
+          const isDeleteable = crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser1, false, userRelatedGroups);
           expect(isDeleteable).toBe(false);
         });
       });
@@ -789,7 +790,8 @@ describe('PageService', () => {
         });
 
         test('is not deletable', async() => {
-          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser3);
+          const userRelatedGroups = await crowi.pageGrantService.getUserRelatedGroups(testUser3);
+          const isDeleteable = crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser3, false, userRelatedGroups);
           expect(isDeleteable).toBe(true);
         });
       });
@@ -808,7 +810,8 @@ describe('PageService', () => {
         });
 
         test('is deletable', async() => {
-          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser1);
+          const userRelatedGroups = await crowi.pageGrantService.getUserRelatedGroups(testUser1);
+          const isDeleteable = crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser1, false, userRelatedGroups);
           expect(isDeleteable).toBe(true);
         });
       });
@@ -827,7 +830,8 @@ describe('PageService', () => {
         });
 
         test('is deletable', async() => {
-          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser2);
+          const userRelatedGroups = await crowi.pageGrantService.getUserRelatedGroups(testUser2);
+          const isDeleteable = crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser2, false, userRelatedGroups);
           expect(isDeleteable).toBe(true);
         });
       });