Selaa lähdekoodia

Merge pull request #9101 from weseek/imprv/122312-126187-implement-so-that-ROM-users-can-manage-comments

imprv: ROM users can manage comments
mergify[bot] 1 vuosi sitten
vanhempi
sitoutus
112ccf8264

+ 6 - 0
apps/app/public/static/locales/en_US/admin.json

@@ -15,6 +15,7 @@
     "scope_of_page_disclosure": "Scope of page disclosure",
     "set_point": "Set point",
     "Guest Users Access": "Guest users access",
+    "readonly_users_access": "ROM users' access",
     "always_hidden": "Always hidden",
     "always_displayed": "Always displayed",
     "displayed_or_hidden": "Hidden / Displayed",
@@ -37,6 +38,7 @@
     "page_delete_rights": "Delete rights",
     "page_delete": "Page Delete",
     "page_delete_completely": "Page Delete Completely",
+    "comment_manage_rights": "Comment management rights",
     "other_options": "Other options",
     "deletion_explanation": "Restricts users who can trash the selected single page.",
     "complete_deletion_explanation": "Restricts users who can completely delete  selected single page.",
@@ -85,6 +87,10 @@
       "deny": "Deny (Registered users only)",
       "readonly": "Accept (Guests can read only)"
     },
+    "rom_users_comment": {
+      "deny": "Deny (Prohibit ROM users from comment management)",
+      "accept": "Allow (ROM users can manage comments)"
+    },
     "registration_mode": {
       "open": "Open (Anyone can register)",
       "restricted": "Restricted (Requires approval by administrators)",

+ 2 - 1
apps/app/public/static/locales/en_US/translation.json

@@ -349,7 +349,8 @@
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",
     "no_user_found": "No user found",
     "reply": "Reply",
-    "delete_comment": "Delete comment?"
+    "delete_comment": "Delete comment?",
+    "comment_management_is_not_allowed": "Comment management is not allowed."
   },
   "page_api_error": {
     "notfound_or_forbidden": "Original page is not found or forbidden.",

+ 6 - 0
apps/app/public/static/locales/fr_FR/admin.json

@@ -15,6 +15,7 @@
     "scope_of_page_disclosure": "Confidentialité de la page",
     "set_point": "Valeur",
     "Guest Users Access": "Accès invité",
+    "readonly_users_access": "Accès des utilisateurs ROM",
     "always_hidden": "Toujours caché",
     "always_displayed": "Toujours affiché",
     "displayed_or_hidden": "Caché / Affiché",
@@ -37,6 +38,7 @@
     "page_delete_rights": "Droits de suppression",
     "page_delete": "Suppression de page",
     "page_delete_completely": "Suppression complète de page",
+    "comment_manage_rights": "Droits de gestion des commentaires",
     "other_options": "Paramètres supplémentaires",
     "deletion_explanation": "Restreindre les utilisateurs pouvant supprimer une page.",
     "complete_deletion_explanation": "Restreindre les utilisateurs pouvant supprimer complètement une page.",
@@ -85,6 +87,10 @@
       "deny": "Refuser (Utilisateurs inscrits seulement)",
       "readonly": "Autoriser (Lecture seule)"
     },
+    "rom_users_comment": {
+      "deny": "Refuser (Interdire la gestion des commentaires aux utilisateurs ROM)",
+      "accept": "Autoriser (Les utilisateurs ROM peuvent gérer les commentaires)"
+    },
     "registration_mode": {
       "open": "Ouvert (Tout le monde peut s'inscrire)",
       "restricted": "Restreint (Requiert l'approbation d'administrateurs)",

+ 2 - 1
apps/app/public/static/locales/fr_FR/translation.json

@@ -349,7 +349,8 @@
     "display_the_page_when_posting_this_comment": "Afficher la page en postant le commentaire",
     "no_user_found": "Aucun utilisateur trouvé",
     "reply": "Répondre",
-    "delete_comment": "Supprimer?"
+    "delete_comment": "Supprimer?",
+    "comment_management_is_not_allowed": "La gestion des commentaires n'est pas autorisée."
   },
   "page_api_error": {
     "notfound_or_forbidden": "Page originale introuvable ou accès restreint.",

+ 6 - 0
apps/app/public/static/locales/ja_JP/admin.json

@@ -24,6 +24,7 @@
     "scope_of_page_disclosure": "ページの公開範囲",
     "set_point": "設定値",
     "Guest Users Access":"ゲストユーザーのアクセス",
+    "readonly_users_access": "ROMユーザーのアクセス",
     "always_hidden": "非表示 (固定)",
     "always_displayed": "表示 (固定)",
     "displayed_or_hidden": "非表示 / 表示",
@@ -46,6 +47,7 @@
     "page_delete_rights": "ページの削除権限",
     "page_delete": "ゴミ箱に入れる",
     "page_delete_completely": "完全に削除する",
+    "comment_manage_rights": "コメントの操作権限",
     "other_options": "その他のオプション",
     "deletion_explanation": "ページをゴミ箱に入れることができるユーザーを制限します。",
     "complete_deletion_explanation": "ページを完全削除することができるユーザーを制限します。",
@@ -94,6 +96,10 @@
       "deny": "拒否 (アカウントを持つユーザーのみ利用可能)",
       "readonly": "許可 (ゲストユーザーも閲覧のみ可能)"
     },
+    "rom_users_comment": {
+      "deny": "拒否 (ROMユーザーのコメント操作を禁止)",
+      "accept": "許可 (ROMユーザーもコメント操作可能)"
+    },
     "registration_mode": {
       "open": "公開 (だれでも登録可能)",
       "restricted": "制限 (登録完了には管理者の承認が必要)",

+ 2 - 1
apps/app/public/static/locales/ja_JP/translation.json

@@ -382,7 +382,8 @@
     "display_the_page_when_posting_this_comment": "投稿時のページを表示する",
     "no_user_found": "ユーザー名が見つかりません",
     "reply": "返信",
-    "delete_comment": "コメントを削除しますか?"
+    "delete_comment": "コメントを削除しますか?",
+    "comment_management_is_not_allowed": "コメントの操作が許可されていません。"
   },
   "page_api_error": {
     "notfound_or_forbidden": "元のページが見つからないか、アクセス権がありません。",

+ 6 - 0
apps/app/public/static/locales/zh_CN/admin.json

@@ -27,6 +27,7 @@
     "always_hidden": "总是隐藏",
     "displayed_or_hidden": "隐藏 / 显示",
     "Guest Users Access": "来宾用户访问",
+    "readonly_users_access": "ROM用户的访问",
 		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
 		"register_limitation": "注册限制",
 		"register_limitation_desc": "限制新用户注册",
@@ -46,6 +47,7 @@
     "page_delete_rights": "删除权限",
     "page_delete": "删除",
     "page_delete_completely": "彻底删除",
+    "comment_manage_rights": "评论管理权限",
     "other_options": "其他选项",
     "deletion_explanation": "限制用户对选定的单一页面进行垃圾处理。",
     "complete_deletion_explanation": "限制可以完全删除所选单页的用户。",
@@ -94,6 +96,10 @@
 			"deny": "拒绝(仅限注册用户)",
 			"readonly": "接受(来宾可以只读)"
 		},
+    "rom_users_comment": {
+      "deny": "拒绝 (禁止ROM用户操作评论)",
+      "accept": "允许 (ROM用户可以管理评论)"
+    },
 		"registration_mode": {
 			"open": "打开(任何人都可以注册)",
 			"restricted": "受限(需要管理员批准)",

+ 2 - 1
apps/app/public/static/locales/zh_CN/translation.json

@@ -339,7 +339,8 @@
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",
     "no_user_found": "未找到用户名",
     "reply": "Reply",
-    "delete_comment": "Delete comment?"
+    "delete_comment": "Delete comment?",
+    "comment_management_is_not_allowed": "不允许操作评论。"
   },
   "page_api_error": {
     "notfound_or_forbidden": "未找到或禁止原始页。",

+ 34 - 1
apps/app/src/client/components/Admin/Security/SecuritySetting.jsx

@@ -333,7 +333,7 @@ class SecuritySetting extends React.Component {
     const { t, adminGeneralSecurityContainer } = this.props;
     const {
       currentRestrictGuestMode, currentPageDeletionAuthority, currentPageCompleteDeletionAuthority,
-      currentPageRecursiveDeletionAuthority, currentPageRecursiveCompleteDeletionAuthority,
+      currentPageRecursiveDeletionAuthority, currentPageRecursiveCompleteDeletionAuthority, isRomUserAllowedToComment,
     } = adminGeneralSecurityContainer.state;
 
     const isButtonDisabledForDeletion = !validateDeleteConfigs(
@@ -509,6 +509,39 @@ class SecuritySetting extends React.Component {
           </div>
         </div>
 
+        <h4 className="mb-3">{t('security_settings.comment_manage_rights')}</h4>
+        <div className="row mb-4">
+          <div className="col-md-4 text-md-end py-2">
+            <strong>{t('security_settings.readonly_users_access')}</strong>
+          </div>
+          <div className="col-md-8">
+            <div className="dropdown">
+              <button
+                className={`btn btn-outline-secondary dropdown-toggle text-end col-12
+                            col-md-auto ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
+                type="button"
+                id="dropdownMenuButton"
+                data-bs-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                <span className="float-start">
+                  {isRomUserAllowedToComment === true && t('security_settings.rom_users_comment.accept')}
+                  {isRomUserAllowedToComment === false && t('security_settings.rom_users_comment.deny')}
+                </span>
+              </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <button className="dropdown-item" type="button" onClick={() => { adminGeneralSecurityContainer.switchIsRomUserAllowedToComment(false) }}>
+                  {t('security_settings.rom_users_comment.deny')}
+                </button>
+                <button className="dropdown-item" type="button" onClick={() => { adminGeneralSecurityContainer.switchIsRomUserAllowedToComment(true) }}>
+                  {t('security_settings.rom_users_comment.accept')}
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+
         <h4>{t('security_settings.session')}</h4>
         <div className="row">
           <label className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.max_age')}</label>

+ 24 - 1
apps/app/src/client/components/NotAvailableForReadOnlyUser.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { useIsReadOnlyUser } from '~/stores-universal/context';
+import { useIsReadOnlyUser, useIsRomUserAllowedToComment } from '~/stores-universal/context';
 
 import { NotAvailable } from './NotAvailable';
 
@@ -26,3 +26,26 @@ export const NotAvailableForReadOnlyUser: React.FC<{
   );
 });
 NotAvailableForReadOnlyUser.displayName = 'NotAvailableForReadOnlyUser';
+
+export const NotAvailableIfReadOnlyUserNotAllowedToComment: React.FC<{
+  children: JSX.Element
+}> = React.memo(({ children }) => {
+  const { t } = useTranslation();
+  const { data: isReadOnlyUser } = useIsReadOnlyUser();
+
+  const { data: isRomUserAllowedToComment } = useIsRomUserAllowedToComment();
+
+  const isDisabled = !!isReadOnlyUser && !isRomUserAllowedToComment;
+  const title = t('page_comment.comment_management_is_not_allowed');
+
+  return (
+    <NotAvailable
+      isDisabled={isDisabled}
+      title={title}
+      classNamePrefix="grw-not-available-for-read-only-user"
+    >
+      {children}
+    </NotAvailable>
+  );
+});
+NotAvailableIfReadOnlyUserNotAllowedToComment.displayName = 'NotAvailableIfReadOnlyUserNotAllowedToComment';

+ 3 - 3
apps/app/src/client/components/PageComment.tsx

@@ -20,7 +20,7 @@ import type { ICommentHasId, ICommentHasIdList } from '../../interfaces/comment'
 import { useSWRxPageComment } from '../../stores/comment';
 
 import { NotAvailableForGuest } from './NotAvailableForGuest';
-import { NotAvailableForReadOnlyUser } from './NotAvailableForReadOnlyUser';
+import { NotAvailableIfReadOnlyUserNotAllowedToComment } from './NotAvailableForReadOnlyUser';
 import { Comment } from './PageComment/Comment';
 import { CommentEditor } from './PageComment/CommentEditor';
 import { DeleteCommentModal } from './PageComment/DeleteCommentModal';
@@ -183,7 +183,7 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
                 {(!isReadOnly && !showEditorIds.has(comment._id)) && (
                   <div className="d-flex flex-row-reverse">
                     <NotAvailableForGuest>
-                      <NotAvailableForReadOnlyUser>
+                      <NotAvailableIfReadOnlyUserNotAllowedToComment>
                         <button
                           type="button"
                           data-testid="comment-reply-button"
@@ -193,7 +193,7 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
                           <UserPicture user={currentUser} noLink noTooltip additionalClassName="me-2" />
                           <span className="material-symbols-outlined me-1 fs-5 pb-1">reply</span><small>{t('page_comment.reply')}...</small>
                         </button>
-                      </NotAvailableForReadOnlyUser>
+                      </NotAvailableIfReadOnlyUserNotAllowedToComment>
                     </NotAvailableForGuest>
                   </div>
                 )}

+ 16 - 11
apps/app/src/client/components/PageComment/CommentControl.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
 
+import { NotAvailableIfReadOnlyUserNotAllowedToComment } from '../NotAvailableForReadOnlyUser';
 
 type CommentControlProps = {
   onClickEditBtn: () => void,
@@ -13,17 +14,21 @@ export const CommentControl = (props: CommentControlProps): JSX.Element => {
   return (
     // The page-comment-control class is imported from Comment.module.scss
     <div className="page-comment-control">
-      <button type="button" className="btn btn-link p-2 opacity-50" onClick={onClickEditBtn}>
-        <span className="material-symbols-outlined">edit</span>
-      </button>
-      <button
-        data-testid="comment-delete-button"
-        type="button"
-        className="btn btn-link p-2 me-2 opacity-50"
-        onClick={onClickDeleteBtn}
-      >
-        <span className="material-symbols-outlined">close</span>
-      </button>
+      <NotAvailableIfReadOnlyUserNotAllowedToComment>
+        <>
+          <button type="button" className="btn btn-link p-2 opacity-50" onClick={onClickEditBtn}>
+            <span className="material-symbols-outlined">edit</span>
+          </button>
+          <button
+            data-testid="comment-delete-button"
+            type="button"
+            className="btn btn-link p-2 me-2 opacity-50"
+            onClick={onClickDeleteBtn}
+          >
+            <span className="material-symbols-outlined">close</span>
+          </button>
+        </>
+      </NotAvailableIfReadOnlyUserNotAllowedToComment>
     </div>
   );
 

+ 3 - 3
apps/app/src/client/components/PageComment/CommentEditor.tsx

@@ -32,7 +32,7 @@ import { useCommentEditorDirtyMap } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
 import { NotAvailableForGuest } from '../NotAvailableForGuest';
-import { NotAvailableForReadOnlyUser } from '../NotAvailableForReadOnlyUser';
+import { NotAvailableIfReadOnlyUserNotAllowedToComment } from '../NotAvailableForReadOnlyUser';
 
 import { CommentPreview } from './CommentPreview';
 import { SwitchingButtonGroup } from './SwitchingButtonGroup';
@@ -330,7 +330,7 @@ export const CommentEditorPre = (props: CommentEditorProps): JSX.Element => {
     return (
       <CommentEditorLayout>
         <NotAvailableForGuest>
-          <NotAvailableForReadOnlyUser>
+          <NotAvailableIfReadOnlyUserNotAllowedToComment>
             <button
               type="button"
               className="btn btn-outline-primary w-100 text-start py-3"
@@ -341,7 +341,7 @@ export const CommentEditorPre = (props: CommentEditorProps): JSX.Element => {
               <span className="material-symbols-outlined me-1 fs-5">add_comment</span>
               <small>{t('page_comment.add_a_comment')}...</small>
             </button>
-          </NotAvailableForReadOnlyUser>
+          </NotAvailableIfReadOnlyUserNotAllowedToComment>
         </NotAvailableForGuest>
       </CommentEditorLayout>
     );

+ 11 - 0
apps/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -41,6 +41,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       isShowRestrictedByGroup: false,
       isUsersHomepageDeletionEnabled: false,
       isForceDeleteUserHomepageOnUserDeletion: false,
+      isRomUserAllowedToComment: false,
       isLocalEnabled: false,
       isLdapEnabled: false,
       isSamlEnabled: false,
@@ -79,6 +80,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,
       isUsersHomepageDeletionEnabled: generalSetting.isUsersHomepageDeletionEnabled,
       isForceDeleteUserHomepageOnUserDeletion: generalSetting.isForceDeleteUserHomepageOnUserDeletion,
+      isRomUserAllowedToComment: generalSetting.isRomUserAllowedToComment,
       sessionMaxAge: generalSetting.sessionMaxAge,
       wikiMode: generalSetting.wikiMode,
       disableLinkSharing: shareLinkSetting.disableLinkSharing,
@@ -220,6 +222,14 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ isForceDeleteUserHomepageOnUserDeletion: !this.state.isForceDeleteUserHomepageOnUserDeletion });
   }
 
+  /**
+   * switch isRomUserAllowedToComment
+   */
+  switchIsRomUserAllowedToComment(bool) {
+    this.setState({ isRomUserAllowedToComment: bool });
+  }
+
+
   /**
    * Update restrictGuestMode
    * @memberOf AdminGeneralSecuritySContainer
@@ -239,6 +249,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       hideRestrictedByOwner: !this.state.isShowRestrictedByOwner,
       isUsersHomepageDeletionEnabled: this.state.isUsersHomepageDeletionEnabled,
       isForceDeleteUserHomepageOnUserDeletion: this.state.isForceDeleteUserHomepageOnUserDeletion,
+      isRomUserAllowedToComment: this.state.isRomUserAllowedToComment,
     };
 
     requestParams = await removeNullPropertyFromObject(requestParams);

+ 7 - 0
apps/app/src/pages/[[...path]].page.tsx

@@ -45,6 +45,7 @@ import {
   useIsUploadAllFileAllowed, useIsUploadEnabled,
   useElasticsearchMaxBodyLengthToIndex,
   useIsLocalAccountRegistrationEnabled,
+  useIsRomUserAllowedToComment,
 } from '~/stores-universal/context';
 import { useEditingMarkdown } from '~/stores/editor';
 import {
@@ -164,6 +165,8 @@ type Props = CommonProps & {
   elasticsearchMaxBodyLengthToIndex: number,
   isEnabledMarp: boolean,
 
+  isRomUserAllowedToComment: boolean,
+
   sidebarConfig: ISidebarConfig,
 
   isSlackConfigured: boolean,
@@ -242,6 +245,8 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
   useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
 
+  useIsRomUserAllowedToComment(props.isRomUserAllowedToComment);
+
   const { pageWithMeta } = props;
 
   const pageId = pageWithMeta?.data._id;
@@ -551,6 +556,8 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
   props.elasticsearchMaxBodyLengthToIndex = configManager.getConfig('crowi', 'app:elasticsearchMaxBodyLengthToIndex');
 
+  props.isRomUserAllowedToComment = configManager.getConfig('crowi', 'security:isRomUserAllowedToComment');
+
   props.isSlackConfigured = crowi.slackIntegrationService.isSlackConfigured;
   // props.isMailerSetup = mailService.isMailerSetup;
   props.isAclEnabled = aclService.isAclEnabled();

+ 26 - 2
apps/app/src/server/middlewares/exclude-read-only-user.ts

@@ -1,9 +1,12 @@
 import { ErrorV3 } from '@growi/core/dist/models';
-import { NextFunction, Response } from 'express';
-import { Request } from 'express-validator/src/base';
+import type { NextFunction, Response } from 'express';
+import type { Request } from 'express-validator/src/base';
 
 import loggerFactory from '~/utils/logger';
 
+import { configManager } from '../service/config-manager';
+
+
 const logger = loggerFactory('growi:middleware:exclude-read-only-user');
 
 export const excludeReadOnlyUser = (req: Request, res: Response & { apiv3Err }, next: () => NextFunction): NextFunction => {
@@ -23,3 +26,24 @@ export const excludeReadOnlyUser = (req: Request, res: Response & { apiv3Err },
 
   return next();
 };
+
+export const excludeReadOnlyUserIfCommentNotAllowed = (req: Request, res: Response & { apiv3Err }, next: () => NextFunction): NextFunction => {
+  const user = req.user;
+
+  const isRomUserAllowedToComment = configManager.getConfig('crowi', 'security:isRomUserAllowedToComment');
+
+  if (user == null) {
+    logger.warn('req.user is null');
+    return next();
+  }
+
+  if (user.readOnly && !isRomUserAllowedToComment) {
+    const message = 'This user is read only user and comment is not allowed';
+    logger.warn(message);
+
+    return res.apiv3Err(new ErrorV3(message, 'validation_failed'));
+  }
+
+  return next();
+
+};

+ 1 - 0
apps/app/src/server/models/config.ts

@@ -75,6 +75,7 @@ export const defaultCrowiConfigs: { [key: string]: any } = {
   'security:disableLinkSharing' : false,
   'security:user-homepage-deletion:isEnabled': false,
   'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion': false,
+  'security:isRomUserAllowedToComment': false,
 
   'security:passport-local:isEnabled' : true,
   'security:passport-ldap:isEnabled' : false,

+ 3 - 0
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -364,6 +364,7 @@ module.exports = (crowi) => {
         isUsersHomepageDeletionEnabled: await configManager.getConfig('crowi', 'security:user-homepage-deletion:isEnabled'),
         isForceDeleteUserHomepageOnUserDeletion:
         await configManager.getConfig('crowi', 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
+        isRomUserAllowedToComment: await configManager.getConfig('crowi', 'security:isRomUserAllowedToComment'),
         wikiMode: await configManager.getConfig('crowi', 'security:wikiMode'),
         sessionMaxAge: await configManager.getConfig('crowi', 'security:sessionMaxAge'),
       },
@@ -637,6 +638,7 @@ module.exports = (crowi) => {
       'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion': req.body.isUsersHomepageDeletionEnabled
         ? req.body.isForceDeleteUserHomepageOnUserDeletion
         : false,
+      'security:isRomUserAllowedToComment': req.body.isRomUserAllowedToComment,
     };
 
     // Validate delete config
@@ -670,6 +672,7 @@ module.exports = (crowi) => {
         isUsersHomepageDeletionEnabled: await configManager.getConfig('crowi', 'security:user-homepage-deletion:isEnabled'),
         isForceDeleteUserHomepageOnUserDeletion:
         await configManager.getConfig('crowi', 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
+        isRomUserAllowedToComment: await configManager.getConfig('crowi', 'security:isRomUserAllowedToComment'),
       };
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE };

+ 4 - 4
apps/app/src/server/routes/index.js

@@ -5,7 +5,7 @@ import { middlewareFactory as rateLimiterFactory } from '~/features/rate-limiter
 
 import { generateAddActivityMiddleware } from '../middlewares/add-activity';
 import apiV1FormValidator from '../middlewares/apiv1-form-validator';
-import { excludeReadOnlyUser } from '../middlewares/exclude-read-only-user';
+import { excludeReadOnlyUser, excludeReadOnlyUserIfCommentNotAllowed } from '../middlewares/exclude-read-only-user';
 import injectResetOrderByTokenMiddleware from '../middlewares/inject-reset-order-by-token-middleware';
 import injectUserRegistrationOrderByTokenMiddleware from '../middlewares/inject-user-registration-order-by-token-middleware';
 import * as loginFormValidator from '../middlewares/login-form-validator';
@@ -131,9 +131,9 @@ module.exports = function(crowi, app) {
   apiV1Router.get('/tags.search'         , accessTokenParser, loginRequired, tag.api.search);
   apiV1Router.post('/tags.update'        , accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser, addActivity, tag.api.update);
   apiV1Router.get('/comments.get'        , accessTokenParser , loginRequired , comment.api.get);
-  apiV1Router.post('/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , excludeReadOnlyUser, addActivity, comment.api.add);
-  apiV1Router.post('/comments.update'    , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , excludeReadOnlyUser, addActivity, comment.api.update);
-  apiV1Router.post('/comments.remove'    , accessTokenParser , loginRequiredStrictly , excludeReadOnlyUser, addActivity, comment.api.remove);
+  apiV1Router.post('/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.add);
+  apiV1Router.post('/comments.update'    , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.update);
+  apiV1Router.post('/comments.remove'    , accessTokenParser , loginRequiredStrictly , excludeReadOnlyUserIfCommentNotAllowed, addActivity, comment.api.remove);
 
   apiV1Router.post('/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly , excludeReadOnlyUser, attachmentApi.uploadProfileImage);
   apiV1Router.post('/attachments.remove'               , accessTokenParser , loginRequiredStrictly , excludeReadOnlyUser, addActivity ,attachmentApi.remove);

+ 4 - 0
apps/app/src/stores-universal/context.tsx

@@ -208,6 +208,10 @@ export const useIsLocalAccountRegistrationEnabled = (initialData?: boolean): SWR
   return useContextSWR('isLocalAccountRegistrationEnabled', initialData);
 };
 
+export const useIsRomUserAllowedToComment = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useContextSWR('isRomUserAllowedToComment', initialData);
+};
+
 /** **********************************************************
  *                     Computed contexts
  *********************************************************** */