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

Merge branch 'feat/growi-bot' into feat/5512-radio-button-and-modal

深瀬スティーヴン 5 лет назад
Родитель
Сommit
fc8146cfe2

+ 2 - 1
resource/locales/en_US/admin/admin.json

@@ -66,7 +66,7 @@
     "load_plugins": "Load_plugins",
     "enable": "Enable",
     "disable": "Disable",
-    "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <cod>{{variable}}</code> is used.",
+    "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
     "note_for_the_only_env_option": "The GCS Settings is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> ."
   },
   "markdown_setting": {
@@ -260,6 +260,7 @@
       "cancel": "Cancel",
       "change": "Change"
     },
+    "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
     "access_token_settings": {
       "discard": "Discard",
       "generate": "Generate"

+ 1 - 1
resource/locales/ja_JP/admin/admin.json

@@ -257,7 +257,7 @@
       "changes_will_be_deleted": "他のBotの設定が消去されます。",
       "cancel": "取消",
       "change": "変更する"
-    },
+    "use_env_var_if_empty": "データベース側の値が空の場合、環境変数 <code>{{variable}}</code> の値を利用します",
     "access_token_settings": {
       "discard": "破棄",
       "generate": "発行"

+ 1 - 0
resource/locales/zh_CN/admin/admin.json

@@ -268,6 +268,7 @@
       "cancel": "取消",
       "change": "改变"
     },
+    "use_env_var_if_empty": "如果数据库中的值为空,则环境变量的值 <cod>{{variable}}</code> 启用。",
     "access_token_settings": {
       "discard": "丢弃",
       "generate": "生成"

+ 57 - 22
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -63,30 +63,65 @@ const CustomBotWithoutProxySettings = (props) => {
           </button>
         </div>
       </div>
+      <table className="table settings-table">
+        <colgroup>
+          <col className="item-name" />
+          <col className="from-db" />
+          <col className="from-env-vars" />
+        </colgroup>
+        <thead>
+          <tr><th></th><th>Database</th><th>Environment variables</th></tr>
+        </thead>
+        <tbody>
+          <tr>
+            <th>Signing Secret</th>
+            <td>
+              <input
+                className="form-control"
+                type="text"
+                value={slackSigningSecret || ''}
+                onChange={e => setSlackSigningSecret(e.target.value)}
+              />
+            </td>
+            <td>
+              <input
+                className="form-control"
+                type="text"
+                value={slackSigningSecretEnv || ''}
+                readOnly
+              />
+              <p className="form-text text-muted">
+                <small dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.use_env_var_if_empty', { variable: 'SLACK_SIGNING_SECRET' }) }} />
+              </p>
+            </td>
+          </tr>
+          <tr>
+            <th>Bot User OAuth Token</th>
+            <td>
+              <input
+                className="form-control"
+                type="text"
+                value={slackBotToken || ''}
+                onChange={e => setSlackBotToken(e.target.value)}
+              />
+            </td>
+            <td>
+              <input
+                className="form-control"
+                type="text"
+                value={slackBotTokenEnv || ''}
+                readOnly
+              />
+              <p className="form-text text-muted">
+                <small dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.use_env_var_if_empty', { variable: 'SLACK_BOT_TOKEN' }) }} />
+              </p>
+            </td>
+
+          </tr>
+        </tbody>
+      </table>
 
-      <div className="form-group row">
-        <label className="text-left text-md-right col-md-3 col-form-label">Signing Secret</label>
-        <div className="col-md-6">
-          <input
-            className="form-control"
-            type="text"
-            value={slackSigningSecret || slackSigningSecretEnv || ''}
-            onChange={e => setSlackSigningSecret(e.target.value)}
-          />
-        </div>
-      </div>
 
-      <div className="form-group row mb-5">
-        <label className="text-left text-md-right col-md-3 col-form-label">Bot User OAuth Token</label>
-        <div className="col-md-6">
-          <input
-            className="form-control"
-            type="text"
-            value={slackBotToken || slackBotTokenEnv || ''}
-            onChange={e => setSlackBotToken(e.target.value)}
-          />
-        </div>
-      </div>
       <AdminUpdateButtonRow onClick={updateHandler} disabled={false} />
     </>
   );

+ 24 - 3
src/server/routes/apiv3/slack-bot.js

@@ -1,6 +1,10 @@
 
 const express = require('express');
 
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:routes:apiv3:slack-bot');
+
 const router = express.Router();
 
 module.exports = (crowi) => {
@@ -8,13 +12,30 @@ module.exports = (crowi) => {
   const { boltService } = crowi;
   const requestHandler = boltService.receiver.requestHandler.bind(boltService.receiver);
 
-  router.post('/', async(req, res) => {
+
+  // Check if the access token is correct
+  function verificationAccessToken(req, res, next) {
+    const slackBotAccessToken = req.body.slack_bot_access_token || null;
+
+    if (slackBotAccessToken == null || slackBotAccessToken !== this.crowi.configManager.getConfig('crowi', 'slackbot:access-token')) {
+      logger.error('slack_bot_access_token is invalid.');
+      return res.send('*Access token is inValid*');
+    }
+
+    return next();
+  }
+
+  function verificationRequestUrl(req, res, next) {
     // for verification request URL on Event Subscriptions
     if (req.body.type === 'url_verification') {
-      res.send(req.body);
-      return;
+      return res.send(req.body);
     }
 
+    return next();
+  }
+
+  router.post('/', verificationRequestUrl, verificationAccessToken, async(req, res) => {
+
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     res.send();

+ 37 - 0
src/server/routes/apiv3/slack-integration.js

@@ -3,6 +3,7 @@ const loggerFactory = require('@alias/logger');
 const logger = loggerFactory('growi:routes:apiv3:notification-setting');
 const express = require('express');
 const { body } = require('express-validator');
+const crypto = require('crypto');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 const router = express.Router();
@@ -53,6 +54,14 @@ module.exports = (crowi) => {
     return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
   }
 
+
+  function generateAccessToken(user) {
+    const hasher = crypto.createHash('sha512');
+    hasher.update(new Date().getTime() + user._id);
+
+    return hasher.digest('base64');
+  }
+
   /**
    * @swagger
    *
@@ -138,5 +147,33 @@ module.exports = (crowi) => {
       }
     });
 
+  /**
+   * @swagger
+   *
+   *    /slack-integration/access-token:
+   *      put:
+   *        tags: [SlackIntegration]
+   *        operationId: getCustomBotSetting
+   *        summary: /slack-integration
+   *        description: Generate accessToken
+   *        responses:
+   *          200:
+   *            description: Succeeded to update access token for slack
+   */
+  router.put('/access-token', loginRequiredStrictly, adminRequired, async(req, res) => {
+
+    try {
+      const accessToken = generateAccessToken(req.user);
+      await updateSlackBotSettings({ 'slackbot:access-token': accessToken });
+
+      return res.apiv3({ accessToken });
+    }
+    catch (error) {
+      const msg = 'Error occured in updating access token for access token';
+      logger.error('Error', error);
+      return res.apiv3Err(new ErrorV3(msg, 'update-accessToken-failed'));
+    }
+  });
+
   return router;
 };

+ 4 - 20
src/server/service/bolt.js

@@ -1,4 +1,5 @@
 const logger = require('@alias/logger')('growi:service:BoltService');
+const mongoose = require('mongoose');
 
 const PAGINGLIMIT = 10;
 
@@ -293,20 +294,6 @@ class BoltService {
   }
 
   async createModal(command, client, body) {
-    const User = this.crowi.model('User');
-    const slackUser = await User.findUserByUsername('slackUser');
-
-    // if "slackUser" is null, don't show create Modal
-    if (slackUser == null) {
-      logger.error('Failed to create a page because slackUser is not found.');
-      this.client.chat.postEphemeral({
-        channel: command.channel_id,
-        user: command.user_id,
-        blocks: [this.generateMarkdownSectionBlock('*slackUser does not exist.*')],
-      });
-      throw new Error('/growi command:create: slackUser is not found');
-    }
-
     try {
       await client.views.open({
         trigger_id: body.trigger_id,
@@ -349,23 +336,20 @@ class BoltService {
 
   // Submit action in create Modal
   async createPageInGrowi(view, body) {
-    const User = this.crowi.model('User');
     const Page = this.crowi.model('Page');
     const pathUtils = require('growi-commons').pathUtils;
 
     const contentsBody = view.state.values.contents.contents_input.value;
 
     try {
-      // search "slackUser" to create page in slack
-      const slackUser = await User.findUserByUsername('slackUser');
-
       let path = view.state.values.path.path_input.value;
       // sanitize path
       path = this.crowi.xss.process(path);
       path = pathUtils.normalizePath(path);
 
-      const user = slackUser._id;
-      await Page.create(path, contentsBody, user, {});
+      // generate a dummy id because Operation to create a page needs ObjectId
+      const dummyObjectIdOfUser = new mongoose.Types.ObjectId();
+      await Page.create(path, contentsBody, dummyObjectIdOfUser, {});
     }
     catch (err) {
       this.client.chat.postMessage({