Browse Source

Merge pull request #11182 from growilabs/fix/9315-allow-restricted-child-under-any-parent

fix: Permit GRANT_RESTRICTED child under any parent grant
mergify[bot] 3 days ago
parent
commit
d41ecc3632

+ 6 - 0
apps/app/src/server/service/page-grant.ts

@@ -199,6 +199,12 @@ class PageGrantService implements IPageGrantService {
 
 
     const Page = mongoose.model<IPage, PageModel>('Page');
     const Page = mongoose.model<IPage, PageModel>('Page');
 
 
+    // GRANT_RESTRICTED pages are link-only and orthogonal to the page tree hierarchy.
+    // They are intentionally permitted under any parent grant.
+    if (target.grant === Page.GRANT_RESTRICTED) {
+      return true;
+    }
+
     /*
     /*
      * ancestor side
      * ancestor side
      */
      */

+ 96 - 0
apps/app/src/server/service/page/page-grant.integ.ts

@@ -722,6 +722,102 @@ describe('PageGrantService', () => {
     });
     });
   });
   });
 
 
+  /*
+   * GRANT_RESTRICTED ("anyone with the link") is a link-only sharing mode
+   * and is intentionally orthogonal to the page tree hierarchy.
+   * A RESTRICTED child must be permitted under any parent grant.
+   * See: https://github.com/growilabs/growi/issues/9315
+   */
+  describe('Test isGrantNormalized method for GRANT_RESTRICTED target', () => {
+    it('Should return true when Ancestor: owned by User1, Target: anyone with the link', async () => {
+      const targetPath = `${pageE2User1Path}/NEW`;
+      const grant = Page.GRANT_RESTRICTED;
+      const grantedUserIds = undefined;
+      const grantedGroupIds: {
+        item: mongoose.Types.ObjectId;
+        type: GroupType;
+      }[] = [];
+      const shouldCheckDescendants = false;
+
+      const result = await pageGrantService.isGrantNormalized(
+        user1,
+        targetPath,
+        grant,
+        grantedUserIds,
+        grantedGroupIds,
+        shouldCheckDescendants,
+      );
+
+      expect(result).toBe(true);
+    });
+
+    it('Should return true when Ancestor: owned by GroupParent, Target: anyone with the link', async () => {
+      const targetPath = `${pageE3GroupParentPath}/NEW`;
+      const grant = Page.GRANT_RESTRICTED;
+      const grantedUserIds = undefined;
+      const grantedGroupIds: {
+        item: mongoose.Types.ObjectId;
+        type: GroupType;
+      }[] = [];
+      const shouldCheckDescendants = false;
+
+      const result = await pageGrantService.isGrantNormalized(
+        user1,
+        targetPath,
+        grant,
+        grantedUserIds,
+        grantedGroupIds,
+        shouldCheckDescendants,
+      );
+
+      expect(result).toBe(true);
+    });
+
+    it('Should return true when Ancestor: public, Target: anyone with the link', async () => {
+      const targetPath = `${pageE1PublicPath}/NEW`;
+      const grant = Page.GRANT_RESTRICTED;
+      const grantedUserIds = undefined;
+      const grantedGroupIds: {
+        item: mongoose.Types.ObjectId;
+        type: GroupType;
+      }[] = [];
+      const shouldCheckDescendants = false;
+
+      const result = await pageGrantService.isGrantNormalized(
+        user1,
+        targetPath,
+        grant,
+        grantedUserIds,
+        grantedGroupIds,
+        shouldCheckDescendants,
+      );
+
+      expect(result).toBe(true);
+    });
+
+    it('Should still return false when Ancestor: owned by User1, Target: public (regression guard for non-RESTRICTED targets under OWNER)', async () => {
+      const targetPath = `${pageE2User1Path}/NEW`;
+      const grant = Page.GRANT_PUBLIC;
+      const grantedUserIds = undefined;
+      const grantedGroupIds: {
+        item: mongoose.Types.ObjectId;
+        type: GroupType;
+      }[] = [];
+      const shouldCheckDescendants = false;
+
+      const result = await pageGrantService.isGrantNormalized(
+        user1,
+        targetPath,
+        grant,
+        grantedUserIds,
+        grantedGroupIds,
+        shouldCheckDescendants,
+      );
+
+      expect(result).toBe(false);
+    });
+  });
+
   describe('Test isGrantNormalized method with shouldCheckDescendants true', () => {
   describe('Test isGrantNormalized method with shouldCheckDescendants true', () => {
     it('Should return true when Target: public, Descendant: public', async () => {
     it('Should return true when Target: public, Descendant: public', async () => {
       const targetPath = emptyPagePath1;
       const targetPath = emptyPagePath1;