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

+ 0 - 5
apps/app/src/server/routes/page.js

@@ -463,11 +463,6 @@ module.exports = function(crowi, app) {
     const isSyncRevisionToHackmd = !!req.body.isSyncRevisionToHackmd; // cast to boolean
     const isSyncRevisionToHackmd = !!req.body.isSyncRevisionToHackmd; // cast to boolean
     const pageTags = req.body.pageTags || undefined;
     const pageTags = req.body.pageTags || undefined;
 
 
-    // TODO: remove in https://redmine.weseek.co.jp/issues/136140
-    if (grantUserGroupIds != null && grantUserGroupIds.length > 1) {
-      return res.apiv3Err('Cannot grant multiple groups to page at the moment');
-    }
-
     if (pageId === null || pageBody === null || revisionId === null) {
     if (pageId === null || pageBody === null || revisionId === null) {
       return res.json(ApiResponse.error('page_id, body and revision_id are required.'));
       return res.json(ApiResponse.error('page_id, body and revision_id are required.'));
     }
     }

+ 14 - 11
apps/app/src/server/service/page-grant.ts

@@ -1,19 +1,18 @@
 import {
 import {
   type IGrantedGroup,
   type IGrantedGroup,
-  PageGrant, type PageGrantCanBeOnTree, GroupType,
+  PageGrant, GroupType,
 } from '@growi/core';
 } from '@growi/core';
 import {
 import {
   pagePathUtils, pathUtils, pageUtils,
   pagePathUtils, pathUtils, pageUtils,
 } from '@growi/core/dist/utils';
 } from '@growi/core/dist/utils';
-import { et } from 'date-fns/locale';
 import escapeStringRegexp from 'escape-string-regexp';
 import escapeStringRegexp from 'escape-string-regexp';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import ExternalUserGroup, { ExternalUserGroupDocument } from '~/features/external-user-group/server/models/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 ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import { IRecordApplicableGrant } from '~/interfaces/page-grant';
 import { IRecordApplicableGrant } from '~/interfaces/page-grant';
 import { PageDocument, PageModel } from '~/server/models/page';
 import { PageDocument, PageModel } from '~/server/models/page';
-import UserGroup, { UserGroupDocument } from '~/server/models/user-group';
+import UserGroup from '~/server/models/user-group';
 import { includesObjectIds, excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 import { includesObjectIds, excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 
 
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
@@ -26,7 +25,7 @@ const { isTopPage } = pagePathUtils;
 const LIMIT_FOR_MULTIPLE_PAGE_OP = 20;
 const LIMIT_FOR_MULTIPLE_PAGE_OP = 20;
 
 
 type ComparableTarget = {
 type ComparableTarget = {
-  grant: number,
+  grant?: number,
   grantedUserIds?: ObjectIdLike[],
   grantedUserIds?: ObjectIdLike[],
   grantedGroupIds?: IGrantedGroup[],
   grantedGroupIds?: IGrantedGroup[],
   applicableUserIds?: ObjectIdLike[],
   applicableUserIds?: ObjectIdLike[],
@@ -103,7 +102,10 @@ class PageGrantService {
    * About the rule of validation, see: https://dev.growi.org/61b2cdabaa330ce7d8152844
    * About the rule of validation, see: https://dev.growi.org/61b2cdabaa330ce7d8152844
    * @returns boolean
    * @returns boolean
    */
    */
-  private processValidation(target: ComparableTarget, ancestor: ComparableAncestor, descendants?: ComparableDescendants): boolean {
+  private validateGrant(target: ComparableTarget, ancestor: ComparableAncestor, descendants?: ComparableDescendants): boolean {
+    /*
+     * the page itself
+     */
     this.validateComparableTarget(target);
     this.validateComparableTarget(target);
 
 
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
@@ -214,7 +216,7 @@ class PageGrantService {
    * @returns Promise<ComparableAncestor>
    * @returns Promise<ComparableAncestor>
    */
    */
   private async generateComparableTarget(
   private async generateComparableTarget(
-      grant, grantedUserIds: ObjectIdLike[] | undefined, grantedGroupIds: IGrantedGroup[] | undefined, includeApplicable: boolean,
+      grant: PageGrant | undefined, grantedUserIds: ObjectIdLike[] | undefined, grantedGroupIds: IGrantedGroup[] | undefined, includeApplicable: boolean,
   ): Promise<ComparableTarget> {
   ): Promise<ComparableTarget> {
     if (includeApplicable) {
     if (includeApplicable) {
       const Page = mongoose.model('Page') as unknown as PageModel;
       const Page = mongoose.model('Page') as unknown as PageModel;
@@ -419,7 +421,7 @@ class PageGrantService {
    */
    */
   async isGrantNormalized(
   async isGrantNormalized(
       // eslint-disable-next-line max-len
       // eslint-disable-next-line max-len
-      user, targetPath: string, grant, grantedUserIds?: ObjectIdLike[], grantedGroupIds?: IGrantedGroup[], shouldCheckDescendants = false, includeNotMigratedPages = false,
+      user, targetPath: string, grant?: PageGrant, grantedUserIds?: ObjectIdLike[], grantedGroupIds?: IGrantedGroup[], shouldCheckDescendants = false, includeNotMigratedPages = false,
   ): Promise<boolean> {
   ): Promise<boolean> {
     if (isTopPage(targetPath)) {
     if (isTopPage(targetPath)) {
       return true;
       return true;
@@ -429,13 +431,13 @@ class PageGrantService {
 
 
     if (!shouldCheckDescendants) { // checking the parent is enough
     if (!shouldCheckDescendants) { // checking the parent is enough
       const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupIds, false);
       const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupIds, false);
-      return this.processValidation(comparableTarget, comparableAncestor);
+      return this.validateGrant(comparableTarget, comparableAncestor);
     }
     }
 
 
     const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupIds, true);
     const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupIds, true);
     const comparableDescendants = await this.generateComparableDescendants(targetPath, user, includeNotMigratedPages);
     const comparableDescendants = await this.generateComparableDescendants(targetPath, user, includeNotMigratedPages);
 
 
-    return this.processValidation(comparableTarget, comparableAncestor, comparableDescendants);
+    return this.validateGrant(comparableTarget, comparableAncestor, comparableDescendants);
   }
   }
 
 
   /**
   /**
@@ -622,7 +624,7 @@ class PageGrantService {
   }
   }
 
 
   async generateUpdateGrantInfoToOverwriteDescendants(
   async generateUpdateGrantInfoToOverwriteDescendants(
-      operator, updateGrant: PageGrantCanBeOnTree, grantGroupIds?: IGrantedGroup[],
+      operator, updateGrant?: PageGrant, grantGroupIds?: IGrantedGroup[],
   ): Promise<UpdateGrantInfo> {
   ): Promise<UpdateGrantInfo> {
     let updateGrantInfo: UpdateGrantInfo | null = null;
     let updateGrantInfo: UpdateGrantInfo | null = null;
 
 
@@ -666,6 +668,7 @@ class PageGrantService {
     }
     }
 
 
     if (updateGrantInfo == null) {
     if (updateGrantInfo == null) {
+      // Neither pages with grant `GRANT_RESTRICTED` nor `GRANT_SPECIFIED` can be on a page tree.
       throw Error('The parameter `updateGrant` must be 1, 4, or 5');
       throw Error('The parameter `updateGrant` must be 1, 4, or 5');
     }
     }
 
 

+ 21 - 15
apps/app/src/server/service/page.ts

@@ -45,6 +45,7 @@ import { V5ConversionError } from '../models/vo/v5-conversion-error';
 import { divideByType } from '../util/granted-group';
 import { divideByType } from '../util/granted-group';
 
 
 import { configManager } from './config-manager';
 import { configManager } from './config-manager';
+import PageGrantService from './page-grant';
 
 
 const debug = require('debug')('growi:services:page');
 const debug = require('debug')('growi:services:page');
 
 
@@ -150,11 +151,14 @@ class PageService {
 
 
   activityEvent: any;
   activityEvent: any;
 
 
+  pageGrantService: PageGrantService;
+
   constructor(crowi) {
   constructor(crowi) {
     this.crowi = crowi;
     this.crowi = crowi;
     this.pageEvent = crowi.event('page');
     this.pageEvent = crowi.event('page');
     this.tagEvent = crowi.event('tag');
     this.tagEvent = crowi.event('tag');
     this.activityEvent = crowi.event('activity');
     this.activityEvent = crowi.event('activity');
+    this.pageGrantService = crowi.pageGrantService;
 
 
     // init
     // init
     this.initPageEvent();
     this.initPageEvent();
@@ -540,7 +544,7 @@ class PageService {
     if (grant !== Page.GRANT_RESTRICTED) {
     if (grant !== Page.GRANT_RESTRICTED) {
       let isGrantNormalized = false;
       let isGrantNormalized = false;
       try {
       try {
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupIds, false);
+        isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupIds, false);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${newPagePath}" when renaming`, err);
         logger.error(`Failed to validate grant of page at "${newPagePath}" when renaming`, err);
@@ -1047,7 +1051,7 @@ class PageService {
     if (grant !== Page.GRANT_RESTRICTED) {
     if (grant !== Page.GRANT_RESTRICTED) {
       let isGrantNormalized = false;
       let isGrantNormalized = false;
       try {
       try {
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupIds, false);
+        isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupIds, false);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${newPagePath}" when duplicating`, err);
         logger.error(`Failed to validate grant of page at "${newPagePath}" when duplicating`, err);
@@ -2656,7 +2660,7 @@ class PageService {
     try {
     try {
       const shouldCheckDescendants = true;
       const shouldCheckDescendants = true;
 
 
-      isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
+      isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(`Failed to validate grant of page at "${path}"`, err);
       logger.error(`Failed to validate grant of page at "${path}"`, err);
@@ -2773,7 +2777,7 @@ class PageService {
       try {
       try {
         const shouldCheckDescendants = true;
         const shouldCheckDescendants = true;
 
 
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
+        isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${path}"`, err);
         logger.error(`Failed to validate grant of page at "${path}"`, err);
@@ -2819,7 +2823,7 @@ class PageService {
     let normalizablePages;
     let normalizablePages;
     let nonNormalizablePages;
     let nonNormalizablePages;
     try {
     try {
-      [normalizablePages, nonNormalizablePages] = await this.crowi.pageGrantService.separateNormalizableAndNotNormalizablePages(user, pagesToNormalize);
+      [normalizablePages, nonNormalizablePages] = await this.pageGrantService.separateNormalizableAndNotNormalizablePages(user, pagesToNormalize);
     }
     }
     catch (err) {
     catch (err) {
       socket.emit(SocketEventName.PageMigrationError);
       socket.emit(SocketEventName.PageMigrationError);
@@ -3642,7 +3646,7 @@ class PageService {
   private async canProcessCreate(
   private async canProcessCreate(
       path: string,
       path: string,
       grantData: {
       grantData: {
-        grant: number,
+        grant?: PageGrant,
         grantedUserIds?: ObjectIdLike[],
         grantedUserIds?: ObjectIdLike[],
         grantUserGroupIds?: IGrantedGroup[],
         grantUserGroupIds?: IGrantedGroup[],
       },
       },
@@ -3679,7 +3683,7 @@ class PageService {
         const isEmptyPageAlreadyExist = await Page.count({ path, isEmpty: true }) > 0;
         const isEmptyPageAlreadyExist = await Page.count({ path, isEmpty: true }) > 0;
         const shouldCheckDescendants = isEmptyPageAlreadyExist && !options?.overwriteScopesOfDescendants;
         const shouldCheckDescendants = isEmptyPageAlreadyExist && !options?.overwriteScopesOfDescendants;
 
 
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantUserGroupIds, shouldCheckDescendants);
+        isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantUserGroupIds, shouldCheckDescendants);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
         logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
@@ -3690,8 +3694,8 @@ class PageService {
       }
       }
 
 
       if (options?.overwriteScopesOfDescendants) {
       if (options?.overwriteScopesOfDescendants) {
-        const updateGrantInfo = await this.crowi.pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupIds);
-        const canOverwriteDescendants = await this.crowi.pageGrantService.canOverwriteDescendants(path, user, updateGrantInfo);
+        const updateGrantInfo = await this.pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupIds);
+        const canOverwriteDescendants = await this.pageGrantService.canOverwriteDescendants(path, user, updateGrantInfo);
 
 
         if (!canOverwriteDescendants) {
         if (!canOverwriteDescendants) {
           throw Error('Cannot overwrite scopes of descendants.');
           throw Error('Cannot overwrite scopes of descendants.');
@@ -3721,14 +3725,14 @@ class PageService {
     const {
     const {
       format = 'markdown', grantUserGroupIds,
       format = 'markdown', grantUserGroupIds,
     } = options;
     } = options;
-    const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
+    const grant = isTopPage(path) ? PageGrant.GRANT_PUBLIC : options.grant;
     const grantData = {
     const grantData = {
       grant,
       grant,
-      grantedUserIds: grant === Page.GRANT_OWNER ? [user._id] : undefined,
+      grantedUserIds: grant === PageGrant.GRANT_OWNER ? [user._id] : undefined,
       grantUserGroupIds,
       grantUserGroupIds,
     };
     };
 
 
-    const isGrantRestricted = grant === Page.GRANT_RESTRICTED;
+    const isGrantRestricted = grant === PageGrant.GRANT_RESTRICTED;
 
 
     // Validate
     // Validate
     const shouldValidateGrant = !isGrantRestricted;
     const shouldValidateGrant = !isGrantRestricted;
@@ -3877,7 +3881,7 @@ class PageService {
   private async canProcessForceCreateBySystem(
   private async canProcessForceCreateBySystem(
       path: string,
       path: string,
       grantData: {
       grantData: {
-        grant: number,
+        grant: PageGrant,
         grantedUserIds?: ObjectIdLike[],
         grantedUserIds?: ObjectIdLike[],
         grantUserGroupId?: ObjectIdLike,
         grantUserGroupId?: ObjectIdLike,
       },
       },
@@ -4028,7 +4032,7 @@ class PageService {
   }
   }
 
 
   async updatePage(
   async updatePage(
-      pageData,
+      pageData: PageDocument,
       body: string | null,
       body: string | null,
       previousBody: string | null,
       previousBody: string | null,
       user,
       user,
@@ -4165,7 +4169,7 @@ class PageService {
   }
   }
 
 
 
 
-  async updatePageV4(pageData, body, previousBody, user, options: IOptionsForUpdate = {}): Promise<PageDocument> {
+  async updatePageV4(pageData: PageDocument, body, previousBody, user, options: IOptionsForUpdate = {}): Promise<PageDocument> {
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Revision = mongoose.model('Revision') as any; // TODO: TypeScriptize model
     const Revision = mongoose.model('Revision') as any; // TODO: TypeScriptize model
 
 
@@ -4173,6 +4177,8 @@ class PageService {
     const grantUserGroupIds = options.grantUserGroupIds || pageData.grantUserGroupIds; // use the previous data if absence
     const grantUserGroupIds = options.grantUserGroupIds || pageData.grantUserGroupIds; // use the previous data if absence
     const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
     const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
 
 
+    // TODO 136137: validate multiple group grant before save using pageData and options
+
     await this.validateAppliedScope(user, grant, grantUserGroupIds);
     await this.validateAppliedScope(user, grant, grantUserGroupIds);
     pageData.applyScope(user, grant, grantUserGroupIds);
     pageData.applyScope(user, grant, grantUserGroupIds);
 
 

+ 0 - 5
packages/core/src/interfaces/page.ts

@@ -67,11 +67,6 @@ export const PageGrant = {
 type UnionPageGrantKeys = keyof typeof PageGrant;
 type UnionPageGrantKeys = keyof typeof PageGrant;
 export type PageGrant = typeof PageGrant[UnionPageGrantKeys];
 export type PageGrant = typeof PageGrant[UnionPageGrantKeys];
 
 
-/**
- * Neither pages with grant `GRANT_RESTRICTED` nor `GRANT_SPECIFIED` can be on a page tree.
- */
-export type PageGrantCanBeOnTree = typeof PageGrant[Exclude<UnionPageGrantKeys, 'GRANT_RESTRICTED' | 'GRANT_SPECIFIED'>];
-
 export const PageStatus = {
 export const PageStatus = {
   STATUS_PUBLISHED: 'published',
   STATUS_PUBLISHED: 'published',
   STATUS_DELETED: 'deleted',
   STATUS_DELETED: 'deleted',