ryoji-s 2 лет назад
Родитель
Сommit
89097ea01a

+ 6 - 4
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -140,22 +140,24 @@ const routerFactory = (crowi: Crowi): Router => {
       const idToPageInfoMap: Record<string, IPageInfo | IPageInfoForListing> = {};
 
       const isGuestUser = req.user == null;
-      for (const page of pages) {
+      const promiseArray = pages.map(async(page) => {
         // construct isIPageInfoForListing
-        const basicPageInfo = pageService.constructBasicPageInfo(page, isGuestUser);
+        const basicPageInfo = await pageService.constructBasicPageInfo(page, isGuestUser);
 
         const pageInfo = (!isIPageInfoForEntity(basicPageInfo))
           ? basicPageInfo
           // create IPageInfoForListing
           : {
             ...basicPageInfo,
-            isAbleToDeleteCompletely: pageService.canDeleteCompletely(page.path, (page.creator as IUserHasId)?._id, req.user, false), // use normal delete config
+            isAbleToDeleteCompletely: await pageService.canDeleteCompletely(page.path, (page.creator as IUserHasId)?._id, page.creator.status, req.user, false), // use normal delete config
             bookmarkCount: bookmarkCountMap != null ? bookmarkCountMap[page._id] : undefined,
             revisionShortBody: shortBodiesMap != null ? shortBodiesMap[page._id] : undefined,
           } as IPageInfoForListing;
 
         idToPageInfoMap[page._id] = pageInfo;
-      }
+      });
+
+      await Promise.all(promiseArray);
 
       return res.apiv3(idToPageInfoMap);
     }

+ 3 - 3
apps/app/src/server/routes/apiv3/pages.js

@@ -641,7 +641,7 @@ module.exports = (crowi) => {
 
     const pagesInTrash = await crowi.pageService.findAllTrashPages(req.user);
 
-    const deletablePages = crowi.pageService.filterPagesByCanDeleteCompletely(pagesInTrash, req.user, true);
+    const deletablePages = await crowi.pageService.filterPagesByCanDeleteCompletely(pagesInTrash, req.user, true);
 
     if (deletablePages.length === 0) {
       const msg = 'No pages can be deleted.';
@@ -910,14 +910,14 @@ module.exports = (crowi) => {
      * Delete Completely
      */
     if (isCompletely) {
-      pagesCanBeDeleted = crowi.pageService.filterPagesByCanDeleteCompletely(pagesToDelete, req.user, isRecursively);
+      pagesCanBeDeleted = await crowi.pageService.filterPagesByCanDeleteCompletely(pagesToDelete, req.user, isRecursively);
     }
     /*
      * Trash
      */
     else {
       pagesCanBeDeleted = pagesToDelete.filter(p => p.isEmpty || p.isUpdatable(pageIdToRevisionIdMap[p._id].toString()));
-      pagesCanBeDeleted = crowi.pageService.filterPagesByCanDelete(pagesToDelete, req.user, isRecursively);
+      pagesCanBeDeleted = await crowi.pageService.filterPagesByCanDelete(pagesToDelete, req.user, isRecursively);
     }
 
     if (pagesCanBeDeleted.length === 0) {

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

@@ -762,7 +762,7 @@ module.exports = function(crowi, app) {
 
     try {
       if (isCompletely) {
-        if (!crowi.pageService.canDeleteCompletely(page.path, creator, req.user, isRecursively)) {
+        if (!await crowi.pageService.canDeleteCompletely(page.path, creator, req.user, isRecursively)) {
           return res.json(ApiResponse.error('You can not delete this page completely', 'user_not_admin'));
         }
         await crowi.pageService.deleteCompletely(page, req.user, options, isRecursively, false, activityParameters);
@@ -778,7 +778,7 @@ module.exports = function(crowi, app) {
           return res.json(ApiResponse.error('Someone could update this page, so couldn\'t delete.', 'outdated'));
         }
 
-        if (!crowi.pageService.canDelete(page.path, creator, req.user, isRecursively)) {
+        if (!await crowi.pageService.canDelete(page.path, creator, req.user, isRecursively)) {
           return res.json(ApiResponse.error('You can not delete this page', 'user_not_admin'));
         }
 

+ 88 - 18
apps/app/src/server/service/page.ts

@@ -5,7 +5,7 @@ import type {
   Ref, HasObjectId, IUserHasId,
   IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta,
 } from '@growi/core';
-import { PageGrant, PageStatus } from '@growi/core';
+import { PageGrant, PageStatus, USER_STATUS } from '@growi/core';
 import {
   pagePathUtils, pathUtils,
 } from '@growi/core/dist/utils';
@@ -40,12 +40,14 @@ import ShareLink from '../models/share-link';
 import Subscription from '../models/subscription';
 import { V5ConversionError } from '../models/vo/v5-conversion-error';
 
+import { configManager } from './config-manager';
+
 const debug = require('debug')('growi:services:page');
 
 const logger = loggerFactory('growi:services:page');
 const {
   isTrashPage, isTopPage, omitDuplicateAreaPageFromPages,
-  isMovablePage, canMoveByPath, isUsersProtectedPages, hasSlash, generateChildrenRegExp,
+  canMoveByPath, isUsersProtectedPages, hasSlash, generateChildrenRegExp,
 } = pagePathUtils;
 
 const { addTrailingSlash } = pathUtils;
@@ -163,8 +165,17 @@ class PageService {
     this.pageEvent.on('addSeenUsers', this.pageEvent.onAddSeenUsers);
   }
 
-  canDeleteCompletely(path: string, creatorId: ObjectIdLike, operator: any | null, isRecursively: boolean): boolean {
-    if (operator == null || isTopPage(path) || isUsersProtectedPages(path)) return false;
+  async canDeleteCompletely(path: string, creatorId: ObjectIdLike, operator: any | null, isRecursively: boolean): Promise<boolean> {
+    if (operator == null || isTopPage(path)) return false;
+
+    if (isUsersProtectedPages(path)) {
+      const isUsersHomepageDeletionEnabled = configManager.getConfig('crowi', 'security:isUsersHomepageDeletionEnabled');
+      const User = mongoose.model('User');
+      const creator = await User.findById(creatorId);
+      if (!(isUsersHomepageDeletionEnabled && creator.status === USER_STATUS.DELETED)) {
+        return false;
+      }
+    }
 
     const pageCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority');
     const pageRecursiveCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority');
@@ -174,8 +185,17 @@ class PageService {
     return this.canDeleteLogic(creatorId, operator, isRecursively, singleAuthority, recursiveAuthority);
   }
 
-  canDelete(path: string, creatorId: ObjectIdLike, operator: any | null, isRecursively: boolean): boolean {
-    if (operator == null || isUsersProtectedPages(path) || isTopPage(path)) return false;
+  async canDelete(path: string, creatorId: ObjectIdLike, operator: any | null, isRecursively: boolean): Promise<boolean> {
+    if (operator == null || isTopPage(path)) return false;
+
+    if (isUsersProtectedPages(path)) {
+      const isUsersHomepageDeletionEnabled = configManager.getConfig('crowi', 'security:isUsersHomepageDeletionEnabled');
+      const User = mongoose.model('User');
+      const creator = await User.findById(creatorId);
+      if (!(isUsersHomepageDeletionEnabled && creator.status === USER_STATUS.DELETED)) {
+        return false;
+      }
+    }
 
     const pageDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageDeletionAuthority');
     const pageRecursiveDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageRecursiveDeletionAuthority');
@@ -217,12 +237,22 @@ class PageService {
     return false;
   }
 
-  filterPagesByCanDeleteCompletely(pages, user, isRecursively: boolean) {
-    return pages.filter(p => p.isEmpty || this.canDeleteCompletely(p.path, p.creator, user, isRecursively));
+  async filterPagesByCanDeleteCompletely(pages, user, isRecursively: boolean): Promise<boolean[]> {
+    const filteredPages = await Promise.all(pages.map(async(p) => {
+      const canDeleteCompletely = await this.canDeleteCompletely(p.path, p.creator, user, isRecursively);
+      return p.isEmpty || canDeleteCompletely;
+    }));
+
+    return filteredPages;
   }
 
-  filterPagesByCanDelete(pages, user, isRecursively: boolean) {
-    return pages.filter(p => p.isEmpty || this.canDelete(p.path, p.creator, user, isRecursively));
+  async filterPagesByCanDelete(pages, user, isRecursively: boolean): Promise<boolean[]> {
+    const filteredPages = await Promise.all(pages.map(async(p) => {
+      const canDelete = await this.canDelete(p.path, p.creator, user, isRecursively);
+      return p.isEmpty || canDelete;
+    }));
+
+    return filteredPages;
   }
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@@ -258,7 +288,7 @@ class PageService {
     }
 
     const isGuestUser = user == null;
-    const pageInfo = this.constructBasicPageInfo(page, isGuestUser);
+    const pageInfo = await this.constructBasicPageInfo(page, isGuestUser);
 
     const Bookmark = this.crowi.model('Bookmark');
     const bookmarkCount = await Bookmark.countByPageId(pageId);
@@ -287,8 +317,8 @@ class PageService {
       const notEmptyClosestAncestor = await Page.findNonEmptyClosestAncestor(page.path);
       creatorId = notEmptyClosestAncestor.creator;
     }
-    const isDeletable = this.canDelete(page.path, creatorId, user, false);
-    const isAbleToDeleteCompletely = this.canDeleteCompletely(page.path, creatorId, user, false); // use normal delete config
+    const isDeletable = await this.canDelete(page.path, creatorId, user, false);
+    const isAbleToDeleteCompletely = await this.canDeleteCompletely(page.path, creatorId, user, false); // use normal delete config
 
     return {
       data: page,
@@ -1384,10 +1414,22 @@ class PageService {
       throw new Error('This method does NOT support deleting trashed pages.');
     }
 
-    if (!isMovablePage(page.path)) {
+    if (isTopPage(page.path)) {
       throw new Error('Page is not deletable.');
     }
 
+    if (isUsersProtectedPages(page.path)) {
+      if (page.creator == null) {
+        throw new Error('Page is not deletable.');
+      }
+
+      const isUsersHomepageDeletionEnabled = configManager.getConfig('crowi', 'security:isUsersHomepageDeletionEnabled');
+      const populatedPage = await page.populate('creator');
+      if (!(isUsersHomepageDeletionEnabled && populatedPage.creator.status === USER_STATUS.DELETED)) {
+        throw new Error('Page is not deletable.');
+      }
+    }
+
     const newPath = Page.getDeletedPageName(page.path);
 
     const canOperate = await this.crowi.pageOperationService.canOperate(isRecursively, page.path, newPath);
@@ -1532,10 +1574,22 @@ class PageService {
       throw new Error('This method does NOT support deleting trashed pages.');
     }
 
-    if (!isMovablePage(page.path)) {
+    if (isTopPage(page.path)) {
       throw new Error('Page is not deletable.');
     }
 
+    if (isUsersProtectedPages(page.path)) {
+      if (page.creator == null) {
+        throw new Error('Page is not deletable.');
+      }
+
+      const isUsersHomepageDeletionEnabled = configManager.getConfig('crowi', 'security:isUsersHomepageDeletionEnabled');
+      const populatedPage = await page.populate('creator');
+      if (!(isUsersHomepageDeletionEnabled && populatedPage.creator.status === USER_STATUS.DELETED)) {
+        throw new Error('Page is not deletable.');
+      }
+    }
+
     if (isRecursively) {
       this.deleteDescendantsWithStream(page, user);
     }
@@ -2382,8 +2436,24 @@ class PageService {
     });
   }
 
-  constructBasicPageInfo(page: PageDocument, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
-    const isMovable = isGuestUser ? false : isMovablePage(page.path);
+  async constructBasicPageInfo(page: PageDocument, isGuestUser?: boolean): Promise<IPageInfo | IPageInfoForEntity> {
+    let isDeletable = true;
+    if (isGuestUser || isTopPage(page.path)) {
+      isDeletable = false;
+    }
+
+    if (isUsersProtectedPages(page.path)) {
+      if (page.creator == null) {
+        isDeletable = false;
+      }
+      else {
+        const isUsersHomepageDeletionEnabled = configManager.getConfig('crowi', 'security:isUsersHomepageDeletionEnabled');
+        const populatedPage = await page.populate('creator');
+        if (!(isUsersHomepageDeletionEnabled && populatedPage.creator.status === USER_STATUS.DELETED)) {
+          isDeletable = false;
+        }
+      }
+    }
 
     if (page.isEmpty) {
       return {
@@ -2405,7 +2475,7 @@ class PageService {
       likerIds: this.extractStringIds(likers),
       seenUserIds: this.extractStringIds(seenUsers),
       sumOfSeenUsers: page.seenUsers.length,
-      isDeletable: isMovable,
+      isDeletable,
       isAbleToDeleteCompletely: false,
       isRevertible: isTrashPage(page.path),
       contentAge: page.getContentAge(),