Sfoglia il codice sorgente

Merge branch 'master' into dependabot/github_actions/anothrNick/github-tag-action-1.38.0

Luqman Grune 4 anni fa
parent
commit
3d11faf668

+ 1 - 0
packages/app/resource/locales/en_US/translation.json

@@ -169,6 +169,7 @@
   "Link sharing is disabled": "Link sharing is disabled",
   "successfully_saved_the_page": "Successfully saved the page",
   "you_can_not_create_page_with_this_name": "You can not create page with this name",
+  "not_allowed_to_see_this_page": "You cannot see this page",
   "Confirm": "Confirm",
   "personal_dropdown": {
     "home": "Home",

+ 1 - 0
packages/app/resource/locales/ja_JP/translation.json

@@ -171,6 +171,7 @@
   "Link sharing is disabled": "リンクのシェアは無効化されています",
   "successfully_saved_the_page": "ページが正常に保存されました",
   "you_can_not_create_page_with_this_name": "この名前でページを作成することはできません",
+  "not_allowed_to_see_this_page": "このページは閲覧できません",
   "Confirm": "確認",
   "personal_dropdown": {
     "home": "ホーム",

+ 1 - 0
packages/app/resource/locales/zh_CN/translation.json

@@ -177,6 +177,7 @@
   "Link sharing is disabled": "你不允许分享该链接",
   "successfully_saved_the_page": "成功地保存了该页面",
   "you_can_not_create_page_with_this_name": "您无法使用此名称创建页面",
+  "not_allowed_to_see_this_page": "你不能看到这个页面",
   "Confirm": "确定",
 	"form_validation": {
 		"error_message": "有些值不正确",

+ 11 - 5
packages/app/src/components/Page/RevisionLoader.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import { withTranslation } from 'react-i18next';
 import { Waypoint } from 'react-waypoint';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
@@ -49,7 +50,7 @@ class LegacyRevisionLoader extends React.Component {
       const res = await this.props.appContainer.apiv3Get(`/revisions/${revisionId}`, { pageId });
 
       this.setState({
-        markdown: res.data.revision.body,
+        markdown: res.data?.revision?.body,
         errors: null,
       });
 
@@ -94,12 +95,16 @@ class LegacyRevisionLoader extends React.Component {
     }
 
     // ----- after load -----
+    const isForbidden = this.state.errors != null && this.state.errors[0].code === 'forbidden-page';
     let markdown = this.state.markdown;
-    if (this.state.errors != null) {
+    if (isForbidden) {
+      markdown = `<i class="icon-exclamation p-1"></i>${this.props.t('not_allowed_to_see_this_page')}`;
+    }
+    else if (this.state.errors != null) {
       const errorMessages = this.state.errors.map((error) => {
-        return `<span class="text-muted"><em>${error.message}</em></span>`;
+        return `<i class="icon-exclamation p-1"></i><span class="text-muted"><em>${error.message}</em></span>`;
       });
-      markdown = errorMessages.join('');
+      markdown = errorMessages.join('\n');
     }
 
     return (
@@ -117,10 +122,11 @@ class LegacyRevisionLoader extends React.Component {
 /**
  * Wrapper component for using unstated
  */
-const LegacyRevisionLoaderWrapper = withUnstatedContainers(LegacyRevisionLoader, [AppContainer]);
+const LegacyRevisionLoaderWrapper = withTranslation()(withUnstatedContainers(LegacyRevisionLoader, [AppContainer]));
 
 LegacyRevisionLoader.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  t: PropTypes.func.isRequired,
 
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
   pageId: PropTypes.string.isRequired,

+ 16 - 2
packages/app/src/components/PageList/PageListItemL.tsx

@@ -3,6 +3,7 @@ import React, {
   ForwardRefRenderFunction, memo, useCallback, useImperativeHandle, useRef,
 } from 'react';
 
+import { useTranslation } from 'react-i18next';
 import { CustomInput } from 'reactstrap';
 
 import Clamp from 'react-multiline-clamp';
@@ -55,6 +56,8 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     onClickItem, onCheckboxChanged, onPageDuplicated, onPageRenamed, onPageDeleted, onPagePutBacked,
   } = props;
 
+  const { t } = useTranslation();
+
   const inputRef = useRef<HTMLInputElement>(null);
 
   // publish ISelectable methods
@@ -139,7 +142,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
   // background color of list item changes when class "active" exists under 'list-group-item'
   const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
 
-  const shouldDangerouslySetInnerHTMLForPaths = elasticSearchResult != null && elasticSearchResult.highlightedPath.length > 0;
+  const shouldDangerouslySetInnerHTMLForPaths = elasticSearchResult != null && elasticSearchResult.highlightedPath != null;
 
   let likerCount;
   if (isSelected && isIPageInfoForEntity(pageInfo)) {
@@ -157,6 +160,9 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     bookmarkCount = pageMeta?.bookmarkCount;
   }
 
+  const canRenderESSnippet = elasticSearchResult != null && elasticSearchResult.snippet != null;
+  const canRenderRevisionSnippet = revisionShortBody != null;
+
   return (
     <li
       key={pageData._id}
@@ -239,13 +245,21 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
             </div>
             <div className="page-list-snippet py-1">
               <Clamp lines={2}>
-                { elasticSearchResult != null && elasticSearchResult?.snippet.length > 0 && (
+                { elasticSearchResult != null && elasticSearchResult.snippet != null && (
                   // eslint-disable-next-line react/no-danger
                   <div dangerouslySetInnerHTML={{ __html: elasticSearchResult.snippet }}></div>
                 ) }
                 { revisionShortBody != null && (
                   <div>{revisionShortBody}</div>
                 ) }
+                {
+                  !canRenderESSnippet && !canRenderRevisionSnippet && (
+                    <>
+                      <i className="icon-exclamation p-1"></i>
+                      {t('not_allowed_to_see_this_page')}
+                    </>
+                  )
+                }
               </Clamp>
             </div>
           </div>

+ 1 - 1
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -37,7 +37,7 @@ const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props>
   const { t } = useTranslation();
 
   const pageIdsWithNoSnippet = pages
-    .filter(page => (page.meta?.elasticSearchResult?.snippet.length ?? 0) === 0)
+    .filter(page => (page.meta?.elasticSearchResult?.snippet?.length ?? 0) === 0)
     .map(page => page.data._id);
 
   const { data: isGuestUser } = useIsGuestUser();

+ 1 - 2
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -181,8 +181,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
                   <div className="page-list px-md-4">
                     <SearchResultList
                       ref={searchResultListRef}
-                      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                      pages={pages!}
+                      pages={pages}
                       selectedPageId={selectedPageWithMeta?.data._id}
                       forceHideMenuItems={forceHideMenuItems}
                       onPageSelected={page => setSelectedPageWithMeta(page)}

+ 2 - 2
packages/app/src/interfaces/search.ts

@@ -3,8 +3,8 @@ import { IPageWithMeta } from './page';
 export type IPageSearchMeta = {
   bookmarkCount?: number,
   elasticSearchResult?: {
-    snippet: string;
-    highlightedPath: string;
+    snippet?: string | null;
+    highlightedPath?: string | null;
     isHtmlInPath: boolean;
   };
 }

+ 1 - 1
packages/app/src/server/routes/search.js

@@ -156,7 +156,7 @@ module.exports = function(crowi, app) {
 
     let result;
     try {
-      result = await searchService.formatSearchResult(searchResult, delegatorName);
+      result = await searchService.formatSearchResult(searchResult, delegatorName, user, userGroups);
     }
     catch (err) {
       return res.json(ApiResponse.error(err));

+ 28 - 9
packages/app/src/server/service/search.ts

@@ -1,4 +1,5 @@
 import xss from 'xss';
+import mongoose from 'mongoose';
 
 import { SearchDelegatorName } from '~/interfaces/named-query';
 import { IPageWithMeta } from '~/interfaces/page';
@@ -351,7 +352,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
   /**
    * formatting result
    */
-  async formatSearchResult(searchResult: ISearchResult<any>, delegatorName): Promise<IFormattedSearchResult> {
+  async formatSearchResult(searchResult: ISearchResult<any>, delegatorName: SearchDelegatorName, user, userGroups): Promise<IFormattedSearchResult> {
     if (!this.checkIsFormattable(searchResult, delegatorName)) {
       const data: IPageWithMeta<IPageSearchMeta>[] = searchResult.data.map((page) => {
         return {
@@ -398,21 +399,17 @@ class SearchService implements SearchQueryParser, SearchResolver {
         pageData.lastUpdateUser = serializeUserSecurely(pageData.lastUpdateUser);
       }
 
-      // const data = searchResult.data.find((data) => {
-      //   return pageData.id === data._id;
-      // });
-
       // increment elasticSearchResult
       let elasticSearchResult;
       const highlightData = data._highlight;
       if (highlightData != null) {
-        const snippet = highlightData['body.en'] || highlightData['body.ja'] || '';
-        const pathMatch = highlightData['path.en'] || highlightData['path.ja'] || '';
+        const snippet = this.canShowSnippet(pageData, user, userGroups) ? highlightData['body.en'] || highlightData['body.ja'] : null;
+        const pathMatch = highlightData['path.en'] || highlightData['path.ja'];
         const isHtmlInPath = highlightData['path.en'] != null || highlightData['path.ja'] != null;
 
         elasticSearchResult = {
-          snippet: filterXss.process(snippet),
-          highlightedPath: filterXss.process(pathMatch),
+          snippet: typeof snippet === 'string' ? filterXss.process(snippet) : null,
+          highlightedPath: typeof pathMatch === 'string' ? filterXss.process(pathMatch) : null,
           isHtmlInPath,
         };
       }
@@ -430,6 +427,28 @@ class SearchService implements SearchQueryParser, SearchResolver {
     return result;
   }
 
+  canShowSnippet(pageData, user, userGroups): boolean {
+    const Page = mongoose.model('Page') as unknown as PageModel;
+
+    const testGrant = pageData.grant;
+    const testGrantedUser = pageData.grantedUsers?.[0];
+    const testGrantedGroup = pageData.grantedGroup;
+
+    if (testGrant === Page.GRANT_RESTRICTED) {
+      return false;
+    }
+
+    if (testGrant === Page.GRANT_OWNER) {
+      return user._id.toString() === testGrantedUser.toString();
+    }
+
+    if (testGrant === Page.GRANT_USER_GROUP) {
+      return userGroups.map(d => d.toString()).include(testGrantedGroup.toString());
+    }
+
+    return true;
+  }
+
 }
 
 export default SearchService;