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

Merge pull request #1075 from weseek/delete-completely-for-master-mearge

Delete completely for master mearge
Yuki Takei 6 лет назад
Родитель
Сommit
6fe83927d5

+ 15 - 12
resource/locales/en-US/translation.json

@@ -195,7 +195,7 @@
   "Re-enter new password": "Re-enter new password",
   "Password is not set": "Password is not set",
 
-  "Security Settings": "Security Settings",
+  "security_settings": "Security Settings",
 
   "API Settings": "API Settings",
   "API Token Settings": "API Token Settings",
@@ -270,7 +270,8 @@
   "page_api_error": {
     "notfound_or_forbidden": "Original page is not found or forbidden.",
     "already_exists": "New page is already exists.",
-    "outdated": "Page is updated someone and now outdated. "
+    "outdated": "Page is updated someone and now outdated.",
+    "user_not_admin": "Only admin user can delete completely"
   },
 
   "modal_rename": {
@@ -291,15 +292,13 @@
   "Delete Completely": "Delete Completely",
 
   "modal_delete": {
-    "label": {
-      "Delete Page": "Delete Page",
-      "Delete recursively": "Delete recursively",
-      "Delete completely": "Delete completely"
-    },
-    "help": {
-      "recursively": "Delete children of under <code>%s</code> recursively",
-      "completely": "Delete completely instead of putting it into trash"
-    }
+    "delete_page": "Delete Page",
+    "deleting_page": "Deleting Page",
+    "delete_recursively": "Delete child pages under %s recursively.",
+    "delete_completely": "Delete Completely",
+    "delete_completely_restriction": "You have no admin to delete completely.",
+    "recursively": "Delete children of under <code>%s</code> recursively.",
+    "completely": "Delete completely instead of putting it into trash."
   },
 
   "modal_duplicate": {
@@ -441,7 +440,6 @@
 
   "security_setting": {
 		"Basic authentication": "Basic authentication",
-		"Security settings": "Security settings",
 		"Guest users access": "Guest users access",
 		"Register limitation": "Register limitation",
 		"The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
@@ -459,6 +457,11 @@
     "page_listing_1_desc": "Show pages that are restricted by 'Just Me' option when listing/searching",
     "page_listing_2": "Page listing/searching<br>restricted by User Group",
     "page_listing_2_desc": "Show pages that are restricted by User Group when listing/searching",
+    "complete_deletion": "Restrict Complete Deletion of Pages",
+    "complete_deletion_explain": "Restricts users who can completely delete pages.",
+    "admin_only": "Admin Only",
+    "admin_and_author": "Admin and Author",
+    "anyone": "Anyone",
 
 		"Authentication mechanism settings": "Authentication mechanism settings",
     "note": "Note",

+ 15 - 12
resource/locales/ja/translation.json

@@ -195,7 +195,7 @@
   "Re-enter new password": "(確認用)",
   "Password is not set": "パスワードが設定されていません",
 
-  "Security Settings": "セキュリティ設定",
+  "security_settings": "セキュリティ設定",
 
   "API Settings": "API設定",
   "API Token Settings": "API Token設定",
@@ -270,7 +270,8 @@
   "page_api_error": {
     "notfound_or_forbidden": "元のページが見つからないか、アクセス権がありません。",
     "already_exists": "新しいページが既に存在しています。",
-    "outdated": "ページが他のユーザーによって更新されました。"
+    "outdated": "ページが他のユーザーによって更新されました。",
+    "user_not_admin": "権限のあるユーザーのみが完全削除できます"
   },
 
   "modal_rename": {
@@ -291,15 +292,13 @@
   "Delete Completely": "完全削除",
 
   "modal_delete": {
-    "label": {
-      "Delete Page": "ページを削除する",
-      "Delete recursively": "全ての子ページも削除",
-      "Delete completely": "完全削除"
-    },
-    "help": {
-      "recursively": "<code>%s</code> 配下のページも削除します",
-      "completely": "ゴミ箱を経由せず、完全に削除します"
-    }
+    "delete_page": "ページを削除する",
+    "deleting_page": "ページパス",
+    "delete_recursively": "全ての子ページも削除",
+    "delete_completely": "完全削除",
+    "delete_completely_restriction": "完全削除をするための権限がありません。",
+    "recursively": "<code>%s</code> 配下のページも削除します",
+    "completely": "ゴミ箱を経由せず、完全に削除します"
   },
 
   "modal_duplicate": {
@@ -441,7 +440,6 @@
 
   "security_setting": {
     "Basic authentication": "Basic認証",
-    "Security settings": "セキュリティ設定",
     "Guest users access": "ゲストユーザーのアクセス",
     "Register limitation": "登録の制限",
     "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
@@ -459,6 +457,11 @@
     "page_listing_1_desc": "ページのリスト表示や検索結果において、'自分のみ'に閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
     "page_listing_2": "ページのリスト表示と検索<br>特定グループに閲覧制限しているページ",
     "page_listing_2_desc": "ページのリスト表示や検索結果において、特定グループにのみ閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
+    "complete_deletion": "完全削除制限機能",
+    "complete_deletion_explain": "完全削除できるユーザーを制限します。",
+    "admin_only": "管理者のみ可能",
+    "admin_and_author": "管理者とページ作者が可能",
+    "anyone": "誰でも可能",
 
     "Authentication mechanism settings":"認証機構設定",
     "note": "メモ",

+ 1 - 0
src/server/form/admin/customfeatures.js

@@ -4,6 +4,7 @@ const field = form.field;
 
 module.exports = form(
   field('settingForm[customize:isEnabledTimeline]').trim().toBooleanStrict(),
+  field('settingForm[customize:isEnabledDeleteCompletely]').trim().toBooleanStrict(),
   field('settingForm[customize:isSavedStatesOfTabChanges]').trim().toBooleanStrict(),
   field('settingForm[customize:isEnabledAttachTitleHeader]').trim().toBooleanStrict(),
   field('settingForm[customize:showRecentCreatedNumber]').trim().toInt(),

+ 1 - 0
src/server/form/admin/securityGeneral.js

@@ -12,4 +12,5 @@ module.exports = form(
   field('settingForm[security:registrationWhiteList]').custom(normalizeCRLF).custom(stringToArray),
   field('settingForm[security:list-policy:hideRestrictedByOwner]').trim().toBooleanStrict(),
   field('settingForm[security:list-policy:hideRestrictedByGroup]').trim().toBooleanStrict(),
+  field('settingForm[security:pageCompleteDeletionAuthority]'),
 );

+ 1 - 0
src/server/models/config.js

@@ -64,6 +64,7 @@ module.exports = function(crowi) {
 
       'security:list-policy:hideRestrictedByOwner' : false,
       'security:list-policy:hideRestrictedByGroup' : false,
+      'security:pageCompleteDeletionAuthority' : null,
 
       'security:isEnabledPassport' : false,
       'security:passport-ldap:isEnabled' : false,

+ 12 - 0
src/server/models/user.js

@@ -199,6 +199,18 @@ module.exports = function(crowi) {
     });
   };
 
+  userSchema.methods.canDeleteCompletely = function(creatorId) {
+    const pageCompleteDeletionAuthority = crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority');
+    if (pageCompleteDeletionAuthority == null || this.admin) {
+      return true;
+    }
+    if (pageCompleteDeletionAuthority === 'adminAndAuthor') {
+      return (this._id.equals(creatorId));
+    }
+
+    return false;
+  };
+
   userSchema.methods.updateApiToken = function(callback) {
     const self = this;
 

+ 3 - 0
src/server/routes/page.js

@@ -951,6 +951,9 @@ module.exports = function(crowi, app) {
 
     try {
       if (isCompletely) {
+        if (!req.user.canDeleteCompletely(page.creator)) {
+          return res.json(ApiResponse.error('You can not delete completely', 'user_not_admin'));
+        }
         if (isRecursively) {
           page = await Page.completelyDeletePageRecursively(page, req.user, options);
         }

+ 20 - 3
src/server/views/admin/security.html

@@ -1,11 +1,11 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Security settings')) }} · {% endblock %}
+{% block html_title %}{{ customTitle(t('security_settings')) }} · {% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">
   <header id="page-header">
-    <h1 id="admin-title" class="title">{{ t('Security settings') }}</h1>
+    <h1 id="admin-title" class="title">{{ t('security_settings') }}</h1>
   </header>
 </div>
 {% endblock %}
@@ -38,7 +38,7 @@
 
       <form action="/_api/admin/security/general" method="post" class="form-horizontal" id="generalSetting" role="form">
         <fieldset>
-        <legend class="alert-anchor">{{ t('security_setting.Security settings') }}</legend>
+        <legend class="alert-anchor">{{ t('security_settings') }}</legend>
 
           <div class="form-group">
             <label for="settingForm[security:registrationMode]" class="col-xs-3 control-label">{{ t('Basic authentication') }}</label>
@@ -136,6 +136,23 @@
             </div>
           </div>
 
+          <div class="form-group">
+            {% set configName = 'settingForm[security:pageCompleteDeletionAuthority]' %}
+            {% set configValue = getConfig('crowi','security:pageCompleteDeletionAuthority') %}
+            <label for="{{configName}}" class="col-xs-3 control-label">{{ t('security_setting.complete_deletion') }}</label>
+            <div class="col-xs-6">
+              <select class="form-control selectpicker" name="settingForm[security:pageCompleteDeletionAuthority]" value="{{ configValue }}">
+                <option value="adminOnly" {% if configValue =="adiminOnly" %}selected{% endif %}>{{ t('security_setting.admin_only') }}</option>
+                <option value="adminAndAuthor" {% if configValue == "adminAndAuthor" %}selected{% endif %}>{{ t('security_setting.admin_and_author') }}</option>
+                <option value=null {% if configValue == null  %}selected{% endif %}>{{ t('security_setting.anyone') }}</option>
+              </select>
+
+              <p class="help-block small">
+                {{ t('security_setting.complete_deletion_explain') }}
+              </p>
+            </div>
+          </div>
+
           <div class="form-group">
             <div class="col-xs-offset-3 col-xs-6">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">

+ 1 - 1
src/server/views/admin/widget/menu.html

@@ -4,7 +4,7 @@
 <ul class="nav nav-pills nav-stacked">
   <li class="{% if current == 'index'%}active{% endif %}"><a href="/admin"><i class="icon-fw icon-home"></i> {{ t('Management Wiki Home') }}</a></li>
   <li class="{% if current == 'app'%}active{% endif %}"><a href="/admin/app"><i class="icon-fw icon-settings"></i> {{ t('App Settings') }}</a></li>
-  <li class="{% if current == 'security'%}active{% endif %}"><a href="/admin/security"><i class="icon-fw icon-shield"></i> {{ t('Security Settings') }}</a></li>
+  <li class="{% if current == 'security'%}active{% endif %}"><a href="/admin/security"><i class="icon-fw icon-shield"></i> {{ t('security_settings') }}</a></li>
   <li class="{% if current == 'markdown'%}active{% endif %}"><a href="/admin/markdown"><i class="icon-fw icon-note"></i> {{ t('Markdown Settings') }}</a></li>
   <li class="{% if current == 'customize'%}active{% endif %}"><a href="/admin/customize"><i class="icon-fw icon-wrench"></i> {{ t('Customize') }}</a></li>
   <li class="{% if current == 'importer'%}active{% endif %}"><a href="/admin/importer"><i class="icon-fw icon-cloud-download"></i> {{ t('Import Data') }}</a></li>

+ 13 - 10
src/server/views/modal/delete.html

@@ -8,15 +8,15 @@
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
           <div class="modal-title">
             {% if page.isDeleted() %}
-            <i class="icon-fw icon-fire"></i> {{ t('modal_delete.label.Delete completely') }}
+            <i class="icon-fw icon-fire"></i> {{ t('modal_delete.delete_completely') }}
             {% else %}
-            <i class="icon-fw icon-trash"></i> {{ t('modal_delete.label.Delete Page') }}
+            <i class="icon-fw icon-trash"></i> {{ t('modal_delete.delete_page') }}
             {% endif %}
           </div>
         </div>
         <div class="modal-body">
           <div class="form-group">
-            <label for="">Deleting page:</label><br>
+            <label for="">{{ t('modal_delete.deleting_page') }}:</label><br>
             <code>{{ page.path }}</code>
           </div>
 
@@ -25,17 +25,20 @@
           {% if page.grant != 2 %}
           <div class="checkbox checkbox-warning">
             <input name="recursively" id="cbDeleteRecursively" value="1" type="checkbox" checked>
-            <label for="cbDeleteRecursively">{{ t('modal_delete.label.Delete recursively') }}</label>
-            <p class="help-block"> {{ t('modal_delete.help.recursively', page.path) }}
+            <label for="cbDeleteRecursively">{{ t('modal_delete.delete_recursively') }}</label>
+            <p class="help-block"> {{ t('modal_delete.recursively', page.path) }}
             </p>
           </div>
           {% endif %}
           {% if not page.isDeleted() %}
           <div class="checkbox checkbox-danger">
-            <input name="completely" id="cbDeleteCompletely" value="1"  type="checkbox">
-              <label for="cbDeleteCompletely" class="text-danger">{{ t('modal_delete.label.Delete completely') }}</label>
-              <p class="help-block"> {{ t('modal_delete.help.completely') }}
-              </p>
+          <input name="completely" id="cbDeleteCompletely" {% if !user.canDeleteCompletely(page.creator._id) %} disabled="disabled" {% endif %} value="1"  type="checkbox">
+            <label for="cbDeleteCompletely" class="text-danger">{{ t('modal_delete.delete_completely') }}</label>
+            {% if !user.canDeleteCompletely(page.creator._id) %}
+              <p class="bg-danger text-white p-2 mt-2"> <i class="icon-ban" ></i>{{ t('modal_delete.delete_completely_restriction') }}</p>
+            {% else %}
+            <p class="help-block"> {{ t('modal_delete.completely') }}</p>
+            {% endif %}
           </div>
           {% endif %}
         </div>
@@ -51,7 +54,7 @@
                 <input type="hidden" name="completely" value="true">
                 <button type="submit" class="m-l-10 btn btn-sm btn-danger delete-button">
                   <i class="icon-fire" aria-hidden="true"></i>
-                  {{ t('Delete Completely') }}
+                  {{ t('delete_completely') }}
                 </button>
               {% else %}
                 <button type="submit" class="m-l-10 btn btn-sm btn-default delete-button">

+ 2 - 2
src/server/views/widget/alert_breaking_changes.html

@@ -3,13 +3,13 @@
 {% if getConfig('crowi', 'security:isEnabledPassport') !== true %}
 <div class="myadmin-alert alert alert-warning mb-0">
   <i class="icon-exclamation"></i>
-  {{ t("breaking_changes.v346_passport_is_not_enabled", '<a href="/admin/security">' + t('Security settings') + '<i class="icon-login"></i></a>') }}
+  {{ t("breaking_changes.v346_passport_is_not_enabled", '<a href="/admin/security">' + t('security_settings') + '<i class="icon-login"></i></a>') }}
 </div>
 {% endif %}
 
 {% if getConfig('crowi', 'security:basicName') || getConfig('crowi', 'security:basicSecret') %}
 <div class="myadmin-alert alert alert-warning mb-0">
   <i class="icon-exclamation"></i>
-  {{ t("breaking_changes.v346_using_basic_auth", '<a href="/admin/security">' + t('Security settings') + '<i class="icon-login"></i></a>') }}
+  {{ t("breaking_changes.v346_using_basic_auth", '<a href="/admin/security">' + t('security_settings') + '<i class="icon-login"></i></a>') }}
 </div>
 {% endif %}

+ 3 - 0
src/server/views/widget/modal/page-api-error-messages.html

@@ -2,6 +2,9 @@
   <span class="text-danger msg msg-notfound_or_forbidden">
     <strong><i class="icon-fw icon-ban"></i>{{ t('page_api_error.notfound_or_forbidden') }}</strong>
   </span>
+  <span class="text-danger msg msg-user_not_admin">
+    <strong><i class="icon-fw icon-ban"></i>{{ t('page_api_error.user_not_admin') }}</strong>
+  </span>
   <span class="text-danger msg msg-already_exists">
     <strong><i class="icon-fw icon-ban"></i>{{ t('page_api_error.already_exists') }}</strong>
     <small id="linkToNewPage"></small>

+ 2 - 2
src/server/views/widget/page_alerts.html

@@ -25,10 +25,10 @@
       {% if page.isDeleted() and user %}
       <ul class="list-inline">
         <li>
-          <a href="#" class="btn btn-default btn-rounded btn-sm" data-target="#putBackPage" data-toggle="modal"><i class="icon-action-undo" aria-hidden="true"></i> {{ t('Put Back') }}</a>
+          <button href="#" class="btn btn-default btn-rounded btn-sm" data-target="#putBackPage" data-toggle="modal"><i class="icon-action-undo" aria-hidden="true"></i> {{ t('Put Back') }}</button>
         </li>
         <li>
-          <a href="#" class="btn btn-danger btn-rounded btn-sm" data-target="#deletePage" data-toggle="modal"><i class="icon-fire" aria-hidden="true"></i> {{ t('Delete Completely') }}</a>
+            <button href="#" class="btn btn-danger btn-rounded btn-sm" {% if !user.canDeleteCompletely(page.creator._id) %} disabled="disabled" {% endif %} data-target="#deletePage" data-toggle="modal"><i class="icon-fire" aria-hidden="true"></i> {{ t('Delete Completely') }}</button>
         </li>
       </ul>{# /.pull-right #}
       {% endif %}