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

+ 39 - 3
packages/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -1,5 +1,6 @@
 import { Container } from 'unstated';
 
+import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
 import { toastError } from '../util/apiNotification';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';
 
@@ -22,7 +23,10 @@ export default class AdminGeneralSecurityContainer extends Container {
       wikiMode: '',
       // set dummy value tile for using suspense
       currentRestrictGuestMode: this.dummyCurrentRestrictGuestMode,
-      currentPageCompleteDeletionAuthority: 'adminOnly',
+      currentPageDeletionAuthority: PageDeleteConfigValue.AdminOnly,
+      currentPageCompleteDeletionAuthority: PageDeleteConfigValue.AdminOnly,
+      currentPageRecursiveDeletionAuthority: PageDeleteConfigValue.Inherit,
+      currentPageRecursiveCompleteDeletionAuthority: PageDeleteConfigValue.Inherit,
       isShowRestrictedByOwner: false,
       isShowRestrictedByGroup: false,
       appSiteUrl: appContainer.config.crowi.url || '',
@@ -42,6 +46,11 @@ export default class AdminGeneralSecurityContainer extends Container {
       shareLinksActivePage: 1,
     };
 
+    this.changePageDeletionAuthority = this.changePageDeletionAuthority.bind(this);
+    this.changePageCompleteDeletionAuthority = this.changePageCompleteDeletionAuthority.bind(this);
+    this.changePageRecursiveDeletionAuthority = this.changePageRecursiveDeletionAuthority.bind(this);
+    this.changePageRecursiveCompleteDeletionAuthority = this.changePageRecursiveCompleteDeletionAuthority.bind(this);
+
   }
 
   async retrieveSecurityData() {
@@ -50,7 +59,10 @@ export default class AdminGeneralSecurityContainer extends Container {
     const { generalSetting, shareLinkSetting, generalAuth } = response.data.securityParams;
     this.setState({
       currentRestrictGuestMode: generalSetting.restrictGuestMode,
+      currentPageDeletionAuthority: generalSetting.pageDeletionAuthority,
       currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
+      currentPageRecursiveDeletionAuthority: generalSetting.pageRecursiveDeletionAuthority,
+      currentPageRecursiveCompleteDeletionAuthority: generalSetting.pageRecursiveCompleteDeletionAuthority,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,
       sessionMaxAge: generalSetting.sessionMaxAge,
@@ -104,11 +116,32 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ currentRestrictGuestMode: restrictGuestModeLabel });
   }
 
+  /**
+   * Change pageDeletionAuthority
+   */
+  changePageDeletionAuthority(val) {
+    this.setState({ currentPageDeletionAuthority: val });
+  }
+
   /**
    * Change pageCompleteDeletionAuthority
    */
-  changePageCompleteDeletionAuthority(pageCompleteDeletionAuthorityLabel) {
-    this.setState({ currentPageCompleteDeletionAuthority: pageCompleteDeletionAuthorityLabel });
+  changePageCompleteDeletionAuthority(val) {
+    this.setState({ currentPageCompleteDeletionAuthority: val });
+  }
+
+  /**
+   * Change pageRecursiveDeletionAuthority
+   */
+  changePageRecursiveDeletionAuthority(val) {
+    this.setState({ currentPageRecursiveDeletionAuthority: val });
+  }
+
+  /**
+   * Change pageRecursiveCompleteDeletionAuthority
+   */
+  changePageRecursiveCompleteDeletionAuthority(val) {
+    this.setState({ currentPageRecursiveCompleteDeletionAuthority: val });
   }
 
   /**
@@ -135,7 +168,10 @@ export default class AdminGeneralSecurityContainer extends Container {
     let requestParams = {
       sessionMaxAge: this.state.sessionMaxAge,
       restrictGuestMode: this.state.currentRestrictGuestMode,
+      pageDeletionAuthority: this.state.currentPageDeletionAuthority,
       pageCompleteDeletionAuthority: this.state.currentPageCompleteDeletionAuthority,
+      pageRecursiveDeletionAuthority: this.state.currentPageRecursiveDeletionAuthority,
+      pageRecursiveCompleteDeletionAuthority: this.state.currentPageRecursiveCompleteDeletionAuthority,
       hideRestrictedByGroup: !this.state.isShowRestrictedByGroup,
       hideRestrictedByOwner: !this.state.isShowRestrictedByOwner,
     };

+ 93 - 48
packages/app/src/components/Admin/Security/SecuritySetting.jsx

@@ -5,16 +5,25 @@ import { withTranslation } from 'react-i18next';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-
+import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
 import AppContainer from '~/client/services/AppContainer';
 import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
 
+// used as the prefix of translation
+const DeletionType = Object.freeze({
+  Deletion: 'deletion',
+  CompleteDeletion: 'complete_deletion',
+  RecursiveDeletion: 'recursive_deletion',
+  RecursiveCompleteDeletion: 'recursive_complete_deletion',
+});
+
 class SecuritySetting extends React.Component {
 
   constructor(props) {
     super(props);
 
     this.putSecuritySetting = this.putSecuritySetting.bind(this);
+    this.renderPageDeletePermissionDropdown = this.renderPageDeletePermissionDropdown.bind(this);
   }
 
   async putSecuritySetting() {
@@ -28,9 +37,80 @@ class SecuritySetting extends React.Component {
     }
   }
 
+  renderPageDeletePermissionDropdown(currentState, setState, deletionType, t) {
+    return (
+      <div className="row mb-4">
+        <div className="col-md-3 text-md-right mb-2">
+          <strong>{t(`security_setting.${deletionType}`)}</strong>
+        </div>
+        <div className="col-md-6">
+          <div className="dropdown">
+            <button
+              className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
+              type="button"
+              id="dropdownMenuButton"
+              data-toggle="dropdown"
+              aria-haspopup="true"
+              aria-expanded="true"
+            >
+              <span className="float-left">
+                {currentState === PageDeleteConfigValue.Inherit && t('security_setting.inherit')}
+                {(currentState === PageDeleteConfigValue.Anyone || currentState == null)
+                    && t('security_setting.anyone')}
+                {currentState === PageDeleteConfigValue.AdminOnly && t('security_setting.admin_only')}
+                {currentState === PageDeleteConfigValue.AdminAndAuthor && t('security_setting.admin_and_author')}
+              </span>
+            </button>
+            <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+              {
+                (deletionType === DeletionType.RecursiveDeletion || deletionType === DeletionType.RecursiveCompleteDeletion)
+                && (
+                  <button
+                    className="dropdown-item"
+                    type="button"
+                    onClick={() => { setState(PageDeleteConfigValue.Inherit) }}
+                  >
+                    {t('security_setting.inherit')}
+                  </button>
+                )
+              }
+              <button
+                className="dropdown-item"
+                type="button"
+                onClick={() => { setState(PageDeleteConfigValue.Anyone) }}
+              >
+                {t('security_setting.anyone')}
+              </button>
+              <button
+                className="dropdown-item"
+                type="button"
+                onClick={() => { setState(PageDeleteConfigValue.AdminOnly) }}
+              >
+                {t('security_setting.admin_only')}
+              </button>
+              <button
+                className="dropdown-item"
+                type="button"
+                onClick={() => { setState(PageDeleteConfigValue.AdminAndAuthor) }}
+              >
+                {t('security_setting.admin_and_author')}
+              </button>
+            </div>
+            <p className="form-text text-muted small">
+              {t(`security_setting.${deletionType}_explain`)}
+            </p>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
   render() {
     const { t, adminGeneralSecurityContainer } = this.props;
-    const { currentRestrictGuestMode, currentPageCompleteDeletionAuthority } = adminGeneralSecurityContainer.state;
+    const {
+      currentRestrictGuestMode, currentPageDeletionAuthority, currentPageCompleteDeletionAuthority,
+      currentPageRecursiveDeletionAuthority, currentPageRecursiveCompleteDeletionAuthority,
+    } = adminGeneralSecurityContainer.state;
 
     return (
       <React.Fragment>
@@ -142,52 +222,17 @@ class SecuritySetting extends React.Component {
             )}
           </div>
         </div>
-        <div className="row mb-4">
-          <div className="col-md-3 text-md-right mb-2">
-            <strong>{t('security_setting.complete_deletion')}</strong>
-          </div>
-          <div className="col-md-6">
-            <div className="dropdown">
-              <button
-                className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
-                type="button"
-                id="dropdownMenuButton"
-                data-toggle="dropdown"
-                aria-haspopup="true"
-                aria-expanded="true"
-              >
-                <span className="float-left">
-                  {(currentPageCompleteDeletionAuthority === 'anyOne' || currentPageCompleteDeletionAuthority == null)
-                      && t('security_setting.anyone')}
-                  {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
-                  {currentPageCompleteDeletionAuthority === 'adminAndAuthor' && t('security_setting.admin_and_author')}
-                </span>
-              </button>
-              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                <button className="dropdown-item" type="button" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('anyOne') }}>
-                  {t('security_setting.anyone')}
-                </button>
-                <button
-                  className="dropdown-item"
-                  type="button"
-                  onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminOnly') }}
-                >
-                  {t('security_setting.admin_only')}
-                </button>
-                <button
-                  className="dropdown-item"
-                  type="button"
-                  onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminAndAuthor') }}
-                >
-                  {t('security_setting.admin_and_author')}
-                </button>
-              </div>
-              <p className="form-text text-muted small">
-                {t('security_setting.complete_deletion_explain')}
-              </p>
-            </div>
-          </div>
-        </div>
+
+        {/* Render PageDeletePermissionDropdown */}
+        {
+          [
+            [currentPageDeletionAuthority, adminGeneralSecurityContainer.changePageDeletionAuthority, DeletionType.Deletion],
+            [currentPageCompleteDeletionAuthority, adminGeneralSecurityContainer.changePageCompleteDeletionAuthority, DeletionType.CompleteDeletion],
+            [currentPageRecursiveDeletionAuthority, adminGeneralSecurityContainer.changePageRecursiveDeletionAuthority, DeletionType.RecursiveDeletion],
+            // eslint-disable-next-line max-len
+            [currentPageRecursiveCompleteDeletionAuthority, adminGeneralSecurityContainer.changePageRecursiveCompleteDeletionAuthority, DeletionType.RecursiveCompleteDeletion],
+          ].map(arr => this.renderPageDeletePermissionDropdown(arr[0], arr[1], arr[2], t))
+        }
 
         <h4>{t('security_setting.session')}</h4>
         <div className="form-group row">

+ 7 - 0
packages/app/src/interfaces/page-delete-config.ts

@@ -0,0 +1,7 @@
+export const PageDeleteConfigValue = {
+  Anyone: 'anyone',
+  AdminAndAuthor: 'adminAndAuthor',
+  AdminOnly: 'adminOnly',
+  Inherit: 'inherit',
+} as const;
+export type PageDeleteConfigValue = typeof PageDeleteConfigValue[keyof typeof PageDeleteConfigValue];

+ 2 - 2
packages/app/src/server/models/config.ts

@@ -60,8 +60,8 @@ export const defaultCrowiConfigs: { [key: string]: any } = {
   'security:list-policy:hideRestrictedByOwner' : false,
   'security:list-policy:hideRestrictedByGroup' : false,
   // DEPRECATED: 'security:pageCompleteDeletionAuthority' : undefined,
-  'security:pageSingleDeletionAuthority' : undefined,
-  'security:pageSingleCompleteDeletionAuthority' : undefined,
+  'security:pageDeletionAuthority' : undefined,
+  'security:pageCompleteDeletionAuthority' : undefined,
   'security:pageRecursiveDeletionAuthority' : undefined,
   'security:pageRecursiveCompleteDeletionAuthority' : undefined,
   'security:disableLinkSharing' : false,

+ 1 - 1
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -139,7 +139,7 @@ export default (crowi: Crowi): Router => {
           // create IPageInfoForListing
           : {
             ...basicPageInfo,
-            isAbleToDeleteCompletely: pageService.canDeleteCompletely((page.creator as IUserHasId)?._id, req.user),
+            isAbleToDeleteCompletely: pageService.canDeleteCompletely((page.creator as IUserHasId)?._id, req.user, false), // TODOT: consider
             bookmarkCount: bookmarkCountMap != null ? bookmarkCountMap[page._id] : undefined,
             revisionShortBody: shortBodiesMap != null ? shortBodiesMap[page._id] : undefined,
           } as IPageInfoForListing;

+ 7 - 7
packages/app/src/server/routes/apiv3/security-setting.js

@@ -1,6 +1,7 @@
 import loggerFactory from '~/utils/logger';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';
 
+import { PageDeleteConfigValue } from '~/interfaces/page-delete-config';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 const logger = loggerFactory('growi:routes:apiv3:security-setting');
@@ -18,9 +19,7 @@ const validator = {
     body('restrictGuestMode').if(value => value != null).isString().isIn([
       'Deny', 'Readonly',
     ]),
-    body('pageCompleteDeletionAuthority').if(value => value != null).isString().isIn([
-      'anyOne', 'adminOnly', 'adminAndAuthor',
-    ]),
+    body('pageCompleteDeletionAuthority').if(value => value != null).isString().isIn(Object.values(PageDeleteConfigValue)),
     body('hideRestrictedByOwner').if(value => value != null).isBoolean(),
     body('hideRestrictedByGroup').if(value => value != null).isBoolean(),
   ],
@@ -368,8 +367,8 @@ module.exports = (crowi) => {
     const securityParams = {
       generalSetting: {
         restrictGuestMode: await crowi.configManager.getConfig('crowi', 'security:restrictGuestMode'),
-        pageSingleDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageSingleDeletionAuthority'),
-        pageSingleCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageSingleCompleteDeletionAuthority'),
+        pageDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageDeletionAuthority'),
+        pageCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority'),
         pageRecursiveDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageRecursiveDeletionAuthority'),
         pageRecursiveCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority'),
         hideRestrictedByOwner: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),
@@ -589,6 +588,7 @@ module.exports = (crowi) => {
     const updateData = {
       'security:sessionMaxAge': parseInt(req.body.sessionMaxAge),
       'security:restrictGuestMode': req.body.restrictGuestMode,
+      'security:pageDeletionAuthority': req.body.pageDeletionAuthority,
       'security:pageCompleteDeletionAuthority': req.body.pageCompleteDeletionAuthority,
       'security:pageRecursiveDeletionAuthority': req.body.pageRecursiveDeletionAuthority,
       'security:pageRecursiveCompleteDeletionAuthority': req.body.pageRecursiveCompleteDeletionAuthority,
@@ -605,8 +605,8 @@ module.exports = (crowi) => {
       const securitySettingParams = {
         sessionMaxAge: await crowi.configManager.getConfig('crowi', 'security:sessionMaxAge'),
         restrictGuestMode: await crowi.configManager.getConfig('crowi', 'security:restrictGuestMode'),
-        pageSingleDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageSingleDeletionAuthority'),
-        pageSingleCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageSingleCompleteDeletionAuthority'),
+        pageDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageDeletionAuthority'),
+        pageCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority'),
         pageRecursiveDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageRecursiveDeletionAuthority'),
         pageRecursiveCompleteDeletionAuthority: await crowi.configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority'),
         hideRestrictedByOwner: await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),