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

Merge pull request #7974 from weseek/fix/128138

fix: Pages can be created under a non-existent user page
Shun Miyazawa 2 лет назад
Родитель
Сommit
0841ed8131

+ 9 - 6
apps/app/src/server/models/user.js

@@ -1,4 +1,6 @@
 /* eslint-disable no-use-before-define */
 /* eslint-disable no-use-before-define */
+import { pagePathUtils } from '@growi/core/dist/utils';
+
 import { i18n } from '^/config/next-i18next.config';
 import { i18n } from '^/config/next-i18next.config';
 
 
 import { generateGravatarSrc } from '~/utils/gravatar';
 import { generateGravatarSrc } from '~/utils/gravatar';
@@ -700,14 +702,15 @@ module.exports = function(crowi) {
     });
     });
   };
   };
 
 
-  userSchema.statics.getUsernameByPath = function(path) {
-    let username = null;
-    const match = path.match(/^\/user\/([^/]+)\/?/);
-    if (match) {
-      username = match[1];
+  userSchema.statics.isExistUserByUserPagePath = async function(path) {
+    const username = pagePathUtils.getUsernameByPath(path);
+
+    if (username == null) {
+      return false;
     }
     }
 
 
-    return username;
+    const user = await this.exists({ username });
+    return user != null;
   };
   };
 
 
   userSchema.statics.updateIsInvitationEmailSended = async function(id) {
   userSchema.statics.updateIsInvitationEmailSended = async function(id) {

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

@@ -1,7 +1,7 @@
 
 
 import { PageGrant } from '@growi/core';
 import { PageGrant } from '@growi/core';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
-import { isCreatablePage, isTrashPage } from '@growi/core/dist/utils/page-path-utils';
+import { isCreatablePage, isTrashPage, isUserPage } from '@growi/core/dist/utils/page-path-utils';
 import { normalizePath, addHeadingSlash, attachTitleHeader } from '@growi/core/dist/utils/path-utils';
 import { normalizePath, addHeadingSlash, attachTitleHeader } from '@growi/core/dist/utils/path-utils';
 
 
 import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
 import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
@@ -303,6 +303,13 @@ module.exports = (crowi) => {
     // check whether path starts slash
     // check whether path starts slash
     path = addHeadingSlash(path);
     path = addHeadingSlash(path);
 
 
+    if (isUserPage(path)) {
+      const isExistUser = await User.isExistUserByUserPagePath(path);
+      if (!isExistUser) {
+        return res.apiv3Err("Unable to create a page under a non-existent user's user page");
+      }
+    }
+
     const options = { overwriteScopesOfDescendants };
     const options = { overwriteScopesOfDescendants };
     if (grant != null) {
     if (grant != null) {
       options.grant = grant;
       options.grant = grant;
@@ -526,6 +533,13 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3(`Could not use the path '${newPagePath}'`, 'invalid_path'), 409);
       return res.apiv3Err(new ErrorV3(`Could not use the path '${newPagePath}'`, 'invalid_path'), 409);
     }
     }
 
 
+    if (isUserPage(newPagePath)) {
+      const isExistUser = await User.isExistUserByUserPagePath(newPagePath);
+      if (!isExistUser) {
+        return res.apiv3Err("Unable to rename a page under a non-existent user's user page");
+      }
+    }
+
     // check whether path starts slash
     // check whether path starts slash
     newPagePath = addHeadingSlash(newPagePath);
     newPagePath = addHeadingSlash(newPagePath);
 
 
@@ -756,6 +770,13 @@ module.exports = (crowi) => {
         return res.apiv3Err(new ErrorV3('This page path is invalid', 'invalid_path'), 400);
         return res.apiv3Err(new ErrorV3('This page path is invalid', 'invalid_path'), 400);
       }
       }
 
 
+      if (isUserPage(newPagePath)) {
+        const isExistUser = await User.isExistUserByUserPagePath(newPagePath);
+        if (!isExistUser) {
+          return res.apiv3Err("Unable to duplicate a page under a non-existent user's user page");
+        }
+      }
+
       // check page existence
       // check page existence
       const isExist = (await Page.exists({ path: newPagePath, isEmpty: false }));
       const isExist = (await Page.exists({ path: newPagePath, isEmpty: false }));
       if (isExist) {
       if (isExist) {

+ 11 - 13
apps/app/test/integration/models/user.test.js

@@ -73,7 +73,6 @@ describe('User', () => {
         expect(user).toBeInstanceOf(User);
         expect(user).toBeInstanceOf(User);
         expect(user.name).toBe('Example for User Test');
         expect(user.name).toBe('Example for User Test');
       });
       });
-
     });
     });
 
 
   });
   });
@@ -119,20 +118,19 @@ describe('User', () => {
   });
   });
 
 
   describe('User Utilities', () => {
   describe('User Utilities', () => {
-    describe('Get username from path', () => {
-      test('found', () => {
-        let username = null;
-        username = User.getUsernameByPath('/user/sotarok');
-        expect(username).toEqual('sotarok');
-
-        username = User.getUsernameByPath('/user/some.user.name12/'); // with slash
-        expect(username).toEqual('some.user.name12');
+    describe('Get user exists from user page path', () => {
+      test('found', async() => {
+        const userPagePath = '/user/usertest';
+        const isExist = await User.isExistUserByUserPagePath(userPagePath);
+
+        expect(isExist).toBe(true);
       });
       });
 
 
-      test('not found', () => {
-        let username = null;
-        username = User.getUsernameByPath('/the/page/is/not/related/to/user/page');
-        expect(username).toBeNull();
+      test('not found', async() => {
+        const userPagePath = '/user/usertest-hoge';
+        const isExist = await User.isExistUserByUserPagePath(userPagePath);
+
+        expect(isExist).toBe(false);
       });
       });
     });
     });
   });
   });

+ 20 - 1
packages/core/src/utils/page-path-utils/index.spec.ts

@@ -1,5 +1,5 @@
 import {
 import {
-  isMovablePage, convertToNewAffiliationPath, isCreatablePage, omitDuplicateAreaPathFromPaths,
+  isMovablePage, convertToNewAffiliationPath, isCreatablePage, omitDuplicateAreaPathFromPaths, getUsernameByPath,
 } from './index';
 } from './index';
 
 
 describe.concurrent('isMovablePage test', () => {
 describe.concurrent('isMovablePage test', () => {
@@ -117,4 +117,23 @@ describe.concurrent('isCreatablePage test', () => {
       expect(omitDuplicateAreaPathFromPaths(paths)).toStrictEqual(expectedPaths);
       expect(omitDuplicateAreaPathFromPaths(paths)).toStrictEqual(expectedPaths);
     });
     });
   });
   });
+
+
+  describe.concurrent('Test getUsernameByPath', () => {
+    test.concurrent('found', () => {
+      const username = getUsernameByPath('/user/sotarok');
+      expect(username).toBe('sotarok');
+    });
+
+    test.concurrent('found with slash', () => {
+      const username = getUsernameByPath('/user/some.user.name12/');
+      expect(username).toBe('some.user.name12');
+    });
+
+    test.concurrent('not found', () => {
+      const username = getUsernameByPath('/the/page/is/not/related/to/user/page');
+      expect(username).toBeNull();
+    });
+  });
+
 });
 });

+ 17 - 0
packages/core/src/utils/page-path-utils/index.ts

@@ -287,5 +287,22 @@ export const generateChildrenRegExp = (path: string): RegExp => {
   return new RegExp(`^${path}(\\/[^/]+)\\/?$`);
   return new RegExp(`^${path}(\\/[^/]+)\\/?$`);
 };
 };
 
 
+/**
+ * Get username from user page path
+ * @param path string
+ * @returns string | null
+ */
+export const getUsernameByPath = (path: string): string | null => {
+  let username: string | null = null;
+  // https://regex101.com/r/qj4SfD/1
+  const match = path.match(/^\/user\/([^/]+)\/?/);
+  if (match) {
+    username = match[1];
+  }
+
+  return username;
+};
+
+
 export * from './is-top-page';
 export * from './is-top-page';
 export * from './collect-ancestor-paths';
 export * from './collect-ancestor-paths';