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

Implemented granted user validation mthod

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

+ 15 - 0
packages/app/src/server/models/obsolete-page.js

@@ -156,6 +156,21 @@ export class PageQueryBuilder {
 
   }
 
+  addConditionToListOnlyAncestors(path) {
+    const pathNormalized = pathUtils.normalizePath(path);
+    const ancestorsPaths = extractToAncestorsPaths(pathNormalized);
+
+    this.query = this.query
+      .and({
+        path: {
+          $in: ancestorsPaths,
+        },
+      });
+
+    return this;
+
+  }
+
   /**
    * generate the query to find pages that start with `path`
    *

+ 10 - 0
packages/app/src/server/models/page.ts

@@ -357,6 +357,16 @@ schema.statics.findAncestorsChildrenByPathAndViewer = async function(path: strin
   return pathToChildren;
 };
 
+schema.statics.findAncestors = async function(targetPath: string, includeEmpty = true): Promise<PageDocument[]> {
+  const builder = new PageQueryBuilder(this.find(), includeEmpty);
+  const ancestors = await builder
+    .addConditionToListOnlyAncestors(targetPath)
+    .query
+    .exec();
+
+  return ancestors;
+};
+
 
 /*
  * Merge obsolete page model methods and define new methods which depend on crowi instance

+ 14 - 0
packages/app/src/server/models/user-group.ts

@@ -84,6 +84,13 @@ schema.statics.createGroup = async function(name, description, parentId) {
   return this.create({ name, description, parent });
 };
 
+/**
+ * Find all ancestor groups starting from the UserGroup of the initial "group".
+ * Set "ancestors" as "[]" if the initial group is unnecessary as result.
+ * @param groups UserGroupDocument
+ * @param ancestors UserGroupDocument[]
+ * @returns UserGroupDocument[]
+ */
 schema.statics.findGroupsWithAncestorsRecursively = async function(group, ancestors = [group]) {
   if (group == null) {
     return ancestors;
@@ -99,6 +106,13 @@ schema.statics.findGroupsWithAncestorsRecursively = async function(group, ancest
   return this.findGroupsWithAncestorsRecursively(parent, ancestors);
 };
 
+/**
+ * Find all descendant groups starting from the UserGroups in the initial groups in "groups".
+ * Set "descendants" as "[]" if the initial groups are unnecessary as result.
+ * @param groups UserGroupDocument[] including at least one UserGroup
+ * @param descendants UserGroupDocument[]
+ * @returns UserGroupDocument[]
+ */
 schema.statics.findGroupsWithDescendantsRecursively = async function(groups, descendants = groups) {
   const nextGroups = await this.find({ parent: { $in: groups.map(g => g._id) } });
 

+ 125 - 15
packages/app/src/server/service/page-grant.ts

@@ -1,39 +1,149 @@
 import mongoose from 'mongoose';
+import { pagePathUtils } from '@growi/core';
 
 import UserGroup from '~/server/models/user-group';
-import { PageModel, PageDocument } from '~/server/models/page';
+import { PageModel } from '~/server/models/page';
+import { PageQueryBuilder } from '../models/obsolete-page';
+import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
+
+const { isTopPage } = pagePathUtils;
+
+type ObjectId = mongoose.Types.ObjectId;
 
 class PageGrantService {
 
   crowi!: any;
 
-  Page: PageModel;
-
   constructor(crowi: any) {
     this.crowi = crowi;
   }
 
-  async validateByTestAncestor(target: PageDocument, testAncestor: PageDocument): Promise<boolean> {
-    return false;
+  private validateGrantValues(grant, grantedUserId: ObjectId, grantedGroupId: ObjectId) {
+    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) {
+      throw Error('grantedUserId is not specified');
+    }
+    if (grant === Page.GRANT_SPECIFIED && grantedUserId == null) {
+      throw Error('grantedUserId is not specified');
+    }
   }
 
-  async validateByDescendant(target: PageDocument): Promise<boolean> {
-    return false;
+  validateByGrantUsers(targetUsers: ObjectId[], ancestorsUsers: ObjectId[], descendantsUsers?: ObjectId[]): boolean {
+    const usersShouldNotExist1 = excludeTestIdsFromTargetIds(targetUsers, ancestorsUsers);
+    if (usersShouldNotExist1.length > 0) {
+      return false;
+    }
+
+    if (descendantsUsers == null) {
+      return true;
+    }
+
+    const usersShouldNotExist2 = excludeTestIdsFromTargetIds(descendantsUsers, targetUsers);
+    if (usersShouldNotExist2.length > 0) {
+      return false;
+    }
+
+    return true;
   }
 
-  async pageCreateValidation(pathToCreate) {
-    const Page = mongoose.model('Page');
+  async generateTargetsGrantedUsers(grant, grantedUserId: ObjectId, grantedGroupId: ObjectId): Promise<ObjectId[]> {
+    // validate values
+    this.validateGrantValues(grant, grantedUserId, grantedGroupId);
 
-    // try to find target
-    const emptyTarget = await Page.findOne({ path: pathToCreate });
+    const UserGroupRelation = mongoose.model('UserGroupRelation') as any; // TODO: Typescriptize model
 
-    if (emptyTarget == null) { // checking the parent is enough
+    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 });
+    return targetRelations.map(r => r.relatedUser) as ObjectId[];
+  }
+
+  async generateAncestorsGrantedUsers(pathToCreate: 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;
 
+    /*
+     * make granted users list of ancestor's
+     */
+    const builderForAncestors = new PageQueryBuilder(Page.find({}, { _id: 0, grantedGroup: 1 }), false);
+    const ancestors = await builderForAncestors
+      .addConditionToListOnlyAncestors(pathToCreate)
+      .addConditionToSortPagesByDescPath()
+      .query
+      .exec();
+    const testAncestor = ancestors[0];
+    if (testAncestor == null) {
+      throw Error('testAncestor must exist');
     }
+    // validate values
+    this.validateGrantValues(testAncestor.grant, testAncestor.grantedUsers, testAncestor.grantedGroup);
+
+    if (testAncestor.grant === Page.GRANT_PUBLIC) {
+      ancestorUsers = [];
+    }
+    else if (testAncestor.grant === Page.GRANT_USER_GROUP) {
+      // make a set of all users
+      const ancestorsGrantedRelations = await UserGroupRelation.find({ relatedGroup: testAncestor.grantedGroup }, { _id: 0, relatedUser: 1 });
+      ancestorUsers = Array.from(new Set(ancestorsGrantedRelations.map(r => r.relatedUser))) as ObjectId[];
+    }
+    else if (testAncestor.grant === Page.GRANT_SPECIFIED || testAncestor.grant === Page.GRANT_OWNER) {
+      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[]> {
+    const Page = mongoose.model('Page') as PageModel;
+    const UserGroupRelation = mongoose.model('UserGroupRelation') as any; // TODO: Typescriptize model
+
+    /*
+     * 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 descendants = await builderForDescendants
+      .addConditionToListOnlyDescendants(pathToCreate)
+      .query
+      .exec();
+    // 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)));
+  }
+
+  async pageCreateValidation(pathToCreate: string, grant, grantedUserId: ObjectId, grantedGroupId: ObjectId) {
+    if (isTopPage(pathToCreate)) {
+      return true;
+    }
+
+    const targetUsers = await this.generateTargetsGrantedUsers(grant, grantedUserId, grantedGroupId);
+    const ancestorUsers = await this.generateAncestorsGrantedUsers(pathToCreate);
+
+    // find existing empty page at target path
+    // it will be unnecessary to check the descendant if emptyTarget is null
+    const Page = mongoose.model('Page') as PageModel;
+    const emptyTarget = await Page.findOne({ path: pathToCreate });
+    if (emptyTarget == null) { // checking the parent is enough
+      return this.validateByGrantUsers(targetUsers, ancestorUsers);
+    }
+
+    const descendantUsers = await this.generateDescendantsGrantedUsers(pathToCreate);
 
-    // get grant, grantedUser, grantedGroup of the target
-    // find the nearest parent excluding empty pages
-    // find all descendants & collect all
+    return this.validateByGrantUsers(targetUsers, ancestorUsers, descendantUsers);
   }
 
 }

+ 1 - 1
packages/app/src/server/service/user-group.ts

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
 import loggerFactory from '~/utils/logger';
-import UserGroup from '~/server/models/user-group';
+import UserGroup, { UserGroupDocument } from '~/server/models/user-group';
 import { compareObjectId, isIncludesObjectId } from '~/server/util/compare-objectId';
 
 const logger = loggerFactory('growi:service:UserGroupService'); // eslint-disable-line no-unused-vars