Taichi Masuyama 4 лет назад
Родитель
Сommit
72a910754f

+ 14 - 3
packages/app/src/server/models/obsolete-page.js

@@ -994,7 +994,7 @@ export const getPageSchema = (crowi) => {
       const Page = this;
       const Revision = crowi.model('Revision');
       const {
-        format = 'markdown', redirectTo, grantUserGroupId,
+        format = 'markdown', redirectTo, grantedUserIds, grantUserGroupId,
       } = options;
 
       // sanitize path
@@ -1017,6 +1017,17 @@ export const getPageSchema = (crowi) => {
       /*
        * UserGroup & Owner validation
        */
+      let isGrantNormalized = false;
+      try {
+        isGrantNormalized = await crowi.pageGrantService.pageValidationForCreate(path, grant, grantedUserIds, grantUserGroupId);
+      }
+      catch (err) {
+        logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
+        throw err;
+      }
+      if (!isGrantNormalized) {
+        throw Error('The selected grant or grantedGroup is not assignable to this page.');
+      }
 
 
       /*
@@ -1033,7 +1044,7 @@ export const getPageSchema = (crowi) => {
       }
 
       let parentId = null;
-      if (parent == null && !isTopPage(path)) {
+      if (!isTopPage(path)) {
         parentId = await Page.getParentIdAndFillAncestors(path, parent);
       }
 
@@ -1042,7 +1053,7 @@ export const getPageSchema = (crowi) => {
       page.lastUpdateUser = user;
       page.redirectTo = redirectTo;
       page.status = STATUS_PUBLISHED;
-      page.parent = parentId;
+      page.parent = options.grant === GRANT_RESTRICTED ? null : parentId;
 
       await validateAppliedScope(user, grant, grantUserGroupId);
       page.applyScope(user, grant, grantUserGroupId);

+ 1 - 1
packages/app/src/server/models/page.ts

@@ -175,7 +175,7 @@ schema.statics.findOneParentByParentPath = async function(parentPath: string): P
  *   - second  update ancestor pages' parent
  *   - finally return the target's parent page id
  */
-schema.statics.getParentIdAndFillAncestors = async function(path: string, parent: PageDocument): Promise<Schema.Types.ObjectId> {
+schema.statics.getParentIdAndFillAncestors = async function(path: string, parent: PageDocument | null): Promise<Schema.Types.ObjectId> {
   const parentPath = nodePath.dirname(path);
 
   if (parent != null) {

+ 44 - 26
packages/app/src/server/service/page-grant.ts

@@ -4,7 +4,7 @@ import { pagePathUtils } from '@growi/core';
 import UserGroup from '~/server/models/user-group';
 import { PageModel } from '~/server/models/page';
 import { PageQueryBuilder } from '../models/obsolete-page';
-import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
+import { excludeTestIdsFromTargetIds, removeDuplicates } from '~/server/util/compare-objectId';
 
 const { isTopPage } = pagePathUtils;
 
@@ -18,16 +18,16 @@ class PageGrantService {
     this.crowi = crowi;
   }
 
-  private validateGrantValues(grant, grantedUserId: ObjectId, grantedGroupId: ObjectId) {
+  private validateGrantValues(grant, grantedUserIds, grantedGroupId) {
     const Page = mongoose.model('Page') as PageModel;
 
     if (grant === Page.GRANT_USER_GROUP && grantedGroupId == null) {
       throw Error('grantedGroupId is not specified');
     }
-    if (grant === Page.GRANT_OWNER && grantedUserId == null) {
+    if (grant === Page.GRANT_OWNER && grantedUserIds == null) {
       throw Error('grantedUserId is not specified');
     }
-    if (grant === Page.GRANT_SPECIFIED && grantedUserId == null) {
+    if (grant === Page.GRANT_SPECIFIED && grantedUserIds == null) {
       throw Error('grantedUserId is not specified');
     }
   }
@@ -50,33 +50,46 @@ class PageGrantService {
     return true;
   }
 
-  async generateTargetsGrantedUsers(grant, grantedUserId: ObjectId, grantedGroupId: ObjectId): Promise<ObjectId[]> {
+  private async generateTargetsGrantedUsersForCreate(user, grant, grantedUserIds: ObjectId[], grantedGroupId: ObjectId): Promise<ObjectId[]> {
     // validate values
-    this.validateGrantValues(grant, grantedUserId, grantedGroupId);
+    this.validateGrantValues(grant, grantedUserIds, grantedGroupId);
 
+    let targetGrantedUsers: ObjectId[] = [];
+
+    const Page = mongoose.model('Page') as PageModel;
     const UserGroupRelation = mongoose.model('UserGroupRelation') as any; // TODO: Typescriptize model
 
-    const targetGroup = await UserGroup.findOne({ _id: grantedGroupId });
-    if (targetGroup == null) {
-      throw Error('The group of grantedGroupId does not exist.');
+    if (grant === Page.GRANT_USER_GROUP) {
+      const targetGroup = await UserGroup.findOne({ _id: grantedGroupId });
+      if (targetGroup == null) {
+        throw Error('The group of grantedGroupId does not exist.');
+      }
+
+      const targetRelations = await UserGroupRelation.find({ relatedGroup: grantedGroupId }, { _id: 0, relatedUser: 1 });
+      targetGrantedUsers = targetRelations.map(r => r.relatedUser) as ObjectId[];
+    }
+    if (grant === Page.GRANT_OWNER) {
+      targetGrantedUsers = [user._id];
+    }
+    if (grant === Page.GRANT_SPECIFIED) {
+      targetGrantedUsers = grantedUserIds;
     }
 
-    const targetRelations = await UserGroupRelation.find({ relatedGroup: grantedGroupId }, { _id: 0, relatedUser: 1 });
-    return targetRelations.map(r => r.relatedUser) as ObjectId[];
+    return targetGrantedUsers;
   }
 
-  async generateAncestorsGrantedUsers(pathToCreate: string): Promise<ObjectId[]> {
+  private async generateAncestorsGrantedUsers(targetPath: string): Promise<ObjectId[]> {
     const Page = mongoose.model('Page') as PageModel;
     const UserGroupRelation = mongoose.model('UserGroupRelation') as any; // TODO: Typescriptize model
 
-    let ancestorUsers: ObjectId[] | null = null;
+    let ancestorUsers: ObjectId[] = [];
 
     /*
      * make granted users list of ancestor's
      */
     const builderForAncestors = new PageQueryBuilder(Page.find({}, { _id: 0, grantedGroup: 1 }), false);
     const ancestors = await builderForAncestors
-      .addConditionToListOnlyAncestors(pathToCreate)
+      .addConditionToListOnlyAncestors(targetPath)
       .addConditionToSortPagesByDescPath()
       .query
       .exec();
@@ -99,14 +112,10 @@ class PageGrantService {
       ancestorUsers = testAncestor.grantedUsers;
     }
 
-    if (ancestorUsers == null || Array.isArray(ancestorUsers)) {
-      throw Error('ancestorUsers is unexpectedly non-array type value.');
-    }
-
     return ancestorUsers;
   }
 
-  async generateDescendantsGrantedUsers(pathToCreate: string): Promise<ObjectId[]> {
+  private async generateDescendantsGrantedUsers(targetPath: string): Promise<ObjectId[]> {
     const Page = mongoose.model('Page') as PageModel;
     const UserGroupRelation = mongoose.model('UserGroupRelation') as any; // TODO: Typescriptize model
 
@@ -114,23 +123,32 @@ class PageGrantService {
      * make granted users list of descendant's
      */
     // find all descendants excluding empty pages
-    const builderForDescendants = new PageQueryBuilder(Page.find({}, { _id: 0, grantedGroup: 1 }), false);
+    const builderForDescendants = new PageQueryBuilder(Page.find({}, { _id: 0, grantedUsers: 1, grantedGroup: 1 }), false);
     const descendants = await builderForDescendants
-      .addConditionToListOnlyDescendants(pathToCreate)
+      .addConditionToListOnlyDescendants(targetPath)
       .query
       .exec();
+    // users of GRANT_OWNER
+    let grantedUsersOfGrantOwner = [];
+    descendants.forEach((d) => {
+      if (d.grantedUsers == null) {
+        return;
+      }
+      grantedUsersOfGrantOwner = grantedUsersOfGrantOwner.concat(d.grantedUsers);
+    });
     // make a set of all users
-    const descendantsGrantedGroups = Array.from(new Set(descendants.map(d => d.grantedGroup)));
-    const descendantsGrantedRelations = await UserGroupRelation.find({ relatedGroup: { $in: descendantsGrantedGroups } }, { _id: 0, relatedUser: 1 });
-    return Array.from(new Set(descendantsGrantedRelations.map(r => r.relatedUser)));
+    const grantedGroups = removeDuplicates(descendants.map(d => d.grantedGroup));
+    const grantedRelations = await UserGroupRelation.find({ relatedGroup: { $in: grantedGroups } }, { _id: 0, relatedUser: 1 });
+    const grantedUsersOfGrantUserGroup = grantedRelations.map(r => r.relatedUser);
+    return removeDuplicates([...grantedUsersOfGrantOwner, ...grantedUsersOfGrantUserGroup]);
   }
 
-  async pageCreateValidation(pathToCreate: string, grant, grantedUserId: ObjectId, grantedGroupId: ObjectId) {
+  async pageValidationForCreate(pathToCreate: string, user, grant, grantedUserIds: ObjectId[], grantedGroupId: ObjectId): Promise<boolean> {
     if (isTopPage(pathToCreate)) {
       return true;
     }
 
-    const targetUsers = await this.generateTargetsGrantedUsers(grant, grantedUserId, grantedGroupId);
+    const targetUsers = await this.generateTargetsGrantedUsersForCreate(user, grant, grantedUserIds, grantedGroupId);
     const ancestorUsers = await this.generateAncestorsGrantedUsers(pathToCreate);
 
     // find existing empty page at target path

+ 1 - 1
packages/app/src/server/service/page.js

@@ -366,7 +366,7 @@ class PageService {
     const options = { page };
     options.grant = page.grant;
     options.grantUserGroupId = page.grantedGroup;
-    options.grantedUsers = page.grantedUsers;
+    options.grantedUserIds = page.grantedUsers;
 
     newPagePath = this.crowi.xss.process(newPagePath); // eslint-disable-line no-param-reassign
 

+ 9 - 0
packages/app/src/server/util/compare-objectId.ts

@@ -31,3 +31,12 @@ export const excludeTestIdsFromTargetIds = (targetIds: IObjectId[], testIds: IOb
   // cast to ObjectId
   return excluded.map(e => new ObjectId(e));
 };
+
+export const removeDuplicates = (objectIds: IObjectId[]): IObjectId[] => {
+  // cast to string
+  const strs = objectIds.map(id => id.toString());
+  const uniqueArr = Array.from(new Set(strs));
+
+  // cast to ObjectId
+  return uniqueArr.map(str => new ObjectId(str));
+};