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

Merge branch 'feat/growi-bot' into feat/4937-5532-re-setup-bolt-after-update-secret

itizawa 5 лет назад
Родитель
Сommit
d1f20686bd

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

@@ -257,8 +257,8 @@
       "discard": "Discard",
       "discard": "Discard",
       "generate": "Generate"
       "generate": "Generate"
     },
     },
-    "custom_bot_non_proxy_settings": "Custom bot (non-proxy) Settings",
-    "non_proxy": {
+    "custom_bot_without_proxy_settings": "Custom bot (without-proxy) Settings",
+    "without_proxy": {
       "create_bot": "Create Bot"
       "create_bot": "Create Bot"
     }
     }
   },
   },

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

@@ -255,8 +255,8 @@
       "discard": "破棄",
       "discard": "破棄",
       "generate": "発行"
       "generate": "発行"
     },
     },
-    "custom_bot_non_proxy_settings": "Custom bot (non-proxy) 設定",
-    "non_proxy": {
+    "custom_bot_without_proxy_settings": "Custom bot (without-proxy) 設定",
+    "without_proxy": {
       "create_bot": "Bot を作成する"
       "create_bot": "Bot を作成する"
     }
     }
   },
   },

+ 2 - 2
resource/locales/zh_CN/admin/admin.json

@@ -265,8 +265,8 @@
       "discard": "丢弃",
       "discard": "丢弃",
       "generate": "生成"
       "generate": "生成"
     },
     },
-    "custom_bot_non_proxy_settings": "Custom bot (non-proxy) 设置",
-    "non_proxy": {
+    "custom_bot_without_proxy_settings": "Custom bot (without-proxy) 设置",
+    "without_proxy": {
       "create_bot": "创建 Bot"
       "create_bot": "创建 Bot"
     }
     }
   },
   },

+ 9 - 9
src/client/js/components/Admin/SlackIntegration/CustomBotNonProxySettings.jsx → src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -6,7 +6,7 @@ import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 
-const CustomBotNonProxySettings = (props) => {
+const CustomBotWithoutProxySettings = (props) => {
   const { appContainer } = props;
   const { appContainer } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
@@ -14,14 +14,14 @@ const CustomBotNonProxySettings = (props) => {
   const [slackBotToken, setSlackBotToken] = useState('');
   const [slackBotToken, setSlackBotToken] = useState('');
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
-  const botType = 'non-proxy';
+  const botType = 'without-proxy';
 
 
   const fetchData = useCallback(async() => {
   const fetchData = useCallback(async() => {
     try {
     try {
       const res = await appContainer.apiv3.get('/slack-integration/');
       const res = await appContainer.apiv3.get('/slack-integration/');
       const {
       const {
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
-      } = res.data.slackBotSettingParams.customBotNonProxySettings;
+      } = res.data.slackBotSettingParams.customBotWithoutProxySettings;
       setSlackSigningSecret(slackSigningSecret);
       setSlackSigningSecret(slackSigningSecret);
       setSlackBotToken(slackBotToken);
       setSlackBotToken(slackBotToken);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
@@ -38,12 +38,12 @@ const CustomBotNonProxySettings = (props) => {
 
 
   async function updateHandler() {
   async function updateHandler() {
     try {
     try {
-      await appContainer.apiv3.put('/slack-integration/custom-bot-non-proxy', {
+      await appContainer.apiv3.put('/slack-integration/custom-bot-without-proxy', {
         slackSigningSecret,
         slackSigningSecret,
         slackBotToken,
         slackBotToken,
         botType,
         botType,
       });
       });
-      toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_non_proxy_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_without_proxy_settings') }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -59,7 +59,7 @@ const CustomBotNonProxySettings = (props) => {
             className="btn btn-primary text-nowrap mx-1"
             className="btn btn-primary text-nowrap mx-1"
             onClick={() => window.open('https://api.slack.com/apps', '_blank')}
             onClick={() => window.open('https://api.slack.com/apps', '_blank')}
           >
           >
-            {t('slack_integration.non_proxy.create_bot')}
+            {t('slack_integration.without_proxy.create_bot')}
           </button>
           </button>
         </div>
         </div>
       </div>
       </div>
@@ -92,10 +92,10 @@ const CustomBotNonProxySettings = (props) => {
   );
   );
 };
 };
 
 
-const CustomBotNonProxySettingsWrapper = withUnstatedContainers(CustomBotNonProxySettings, [AppContainer]);
+const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWithoutProxySettings, [AppContainer]);
 
 
-CustomBotNonProxySettings.propTypes = {
+CustomBotWithoutProxySettings.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 };
 };
 
 
-export default CustomBotNonProxySettingsWrapper;
+export default CustomBotWithoutProxySettingsWrapper;

+ 3 - 3
src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
 import AccessTokenSettings from './AccessTokenSettings';
 import AccessTokenSettings from './AccessTokenSettings';
-import CustomBotNonProxySettings from './CustomBotNonProxySettings';
+import CustomBotWithoutProxySettings from './CustomBotWithoutProxySettings';
 
 
 function SlackIntegration() {
 function SlackIntegration() {
 
 
@@ -18,8 +18,8 @@ function SlackIntegration() {
 
 
       <div className="row">
       <div className="row">
         <div className="col-lg-12">
         <div className="col-lg-12">
-          <h2 className="admin-setting-header">{t('slack_integration.custom_bot_non_proxy_settings')}</h2>
-          <CustomBotNonProxySettings />
+          <h2 className="admin-setting-header">{t('slack_integration.custom_bot_without_proxy_settings')}</h2>
+          <CustomBotWithoutProxySettings />
         </div>
         </div>
       </div>
       </div>
     </>
     </>

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

@@ -1,6 +1,10 @@
 
 
 const express = require('express');
 const express = require('express');
 
 
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:routes:apiv3:slack-bot');
+
 const router = express.Router();
 const router = express.Router();
 
 
 module.exports = (crowi) => {
 module.exports = (crowi) => {
@@ -8,13 +12,30 @@ module.exports = (crowi) => {
   const { boltService } = crowi;
   const { boltService } = crowi;
   const requestHandler = boltService.receiver.requestHandler.bind(boltService.receiver);
   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
     // for verification request URL on Event Subscriptions
     if (req.body.type === 'url_verification') {
     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
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     res.send();
     res.send();

+ 52 - 15
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 logger = loggerFactory('growi:routes:apiv3:notification-setting');
 const express = require('express');
 const express = require('express');
 const { body } = require('express-validator');
 const { body } = require('express-validator');
+const crypto = require('crypto');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
 const router = express.Router();
 const router = express.Router();
@@ -18,8 +19,8 @@ const router = express.Router();
  *
  *
  *  components:
  *  components:
  *    schemas:
  *    schemas:
- *      CustomBotNonProxy:
- *        description: CustomBotNonProxy
+ *      CustomBotWithoutProxy:
+ *        description: CustomBotWithoutProxy
  *        type: object
  *        type: object
  *        properties:
  *        properties:
  *          slackSigningSecret:
  *          slackSigningSecret:
@@ -40,7 +41,7 @@ module.exports = (crowi) => {
 
 
 
 
   const validator = {
   const validator = {
-    CusotmBotNonProxy: [
+    CusotmBotWithoutProxy: [
       body('slackSigningSecret').isString(),
       body('slackSigningSecret').isString(),
       body('slackBotToken').isString(),
       body('slackBotToken').isString(),
       body('botType').isString(),
       body('botType').isString(),
@@ -53,6 +54,14 @@ module.exports = (crowi) => {
     return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
     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
    * @swagger
    *
    *
@@ -75,7 +84,7 @@ module.exports = (crowi) => {
         // TODO impl this after GW-4939
         // TODO impl this after GW-4939
         // AccessToken: "tempaccessdatahogehoge",
         // AccessToken: "tempaccessdatahogehoge",
       },
       },
-      customBotNonProxySettings: {
+      customBotWithoutProxySettings: {
         // TODO impl this after GW-4939
         // TODO impl this after GW-4939
         // AccessToken: "tempaccessdatahogehoge",
         // AccessToken: "tempaccessdatahogehoge",
         slackSigningSecretEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret'),
         slackSigningSecretEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret'),
@@ -95,24 +104,24 @@ module.exports = (crowi) => {
   /**
   /**
    * @swagger
    * @swagger
    *
    *
-   *    /slack-integration/custom-bot-non-proxy/:
+   *    /slack-integration/custom-bot-without-proxy/:
    *      put:
    *      put:
-   *        tags: [CustomBotNonProxy]
-   *        operationId: putCustomBotNonProxy
-   *        summary: /slack-integration/custom-bot-non-proxy
-   *        description: Put customBotNonProxy setting.
+   *        tags: [CustomBotWithoutProxy]
+   *        operationId: putCustomBotWithoutProxy
+   *        summary: /slack-integration/custom-bot-without-proxy
+   *        description: Put customBotWithoutProxy setting.
    *        requestBody:
    *        requestBody:
    *          required: true
    *          required: true
    *          content:
    *          content:
    *            application/json:
    *            application/json:
    *              schema:
    *              schema:
-   *                $ref: '#/components/schemas/CustomBotNonProxy'
+   *                $ref: '#/components/schemas/CustomBotWithoutProxy'
    *        responses:
    *        responses:
    *           200:
    *           200:
-   *             description: Succeeded to put CustomBotNonProxy setting.
+   *             description: Succeeded to put CustomBotWithoutProxy setting.
    */
    */
-  router.put('/custom-bot-non-proxy',
-    accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.CusotmBotNonProxy, apiV3FormValidator, async(req, res) => {
+  router.put('/custom-bot-without-proxy',
+    accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.CusotmBotWithoutProxy, apiV3FormValidator, async(req, res) => {
       const { slackSigningSecret, slackBotToken, botType } = req.body;
       const { slackSigningSecret, slackBotToken, botType } = req.body;
 
 
       const requestParams = {
       const requestParams = {
@@ -129,12 +138,12 @@ module.exports = (crowi) => {
         crowi.boltService.publishUpdatedMessage();
         crowi.boltService.publishUpdatedMessage();
 
 
         // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
         // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
-        const customBotNonProxySettingParams = {
+        const customBotWithoutProxySettingParams = {
           slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
           slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
           slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
           slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
           slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:type'),
           slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:type'),
         };
         };
-        return res.apiv3({ customBotNonProxySettingParams });
+        return res.apiv3({ customBotWithoutProxySettingParams });
       }
       }
       catch (error) {
       catch (error) {
         const msg = 'Error occured in updating Custom bot setting';
         const msg = 'Error occured in updating Custom bot setting';
@@ -143,5 +152,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;
   return router;
 };
 };

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

@@ -1,4 +1,5 @@
 const logger = require('@alias/logger')('growi:service:BoltService');
 const logger = require('@alias/logger')('growi:service:BoltService');
+const mongoose = require('mongoose');
 
 
 const PAGINGLIMIT = 10;
 const PAGINGLIMIT = 10;
 
 
@@ -323,20 +324,6 @@ class BoltService {
   }
   }
 
 
   async createModal(command, client, body) {
   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 {
     try {
       await client.views.open({
       await client.views.open({
         trigger_id: body.trigger_id,
         trigger_id: body.trigger_id,
@@ -379,23 +366,20 @@ class BoltService {
 
 
   // Submit action in create Modal
   // Submit action in create Modal
   async createPageInGrowi(view, body) {
   async createPageInGrowi(view, body) {
-    const User = this.crowi.model('User');
     const Page = this.crowi.model('Page');
     const Page = this.crowi.model('Page');
     const pathUtils = require('growi-commons').pathUtils;
     const pathUtils = require('growi-commons').pathUtils;
 
 
     const contentsBody = view.state.values.contents.contents_input.value;
     const contentsBody = view.state.values.contents.contents_input.value;
 
 
     try {
     try {
-      // search "slackUser" to create page in slack
-      const slackUser = await User.findUserByUsername('slackUser');
-
       let path = view.state.values.path.path_input.value;
       let path = view.state.values.path.path_input.value;
       // sanitize path
       // sanitize path
       path = this.crowi.xss.process(path);
       path = this.crowi.xss.process(path);
       path = pathUtils.normalizePath(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) {
     catch (err) {
       this.client.chat.postMessage({
       this.client.chat.postMessage({

+ 1 - 1
src/server/service/config-loader.js

@@ -412,7 +412,7 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
   },
   },
   SLACK_BOT_TYPE: {
   SLACK_BOT_TYPE: {
     ns:      'crowi',
     ns:      'crowi',
-    key:     'slackbot:type', // eg. official || custom-non-proxy || custom-with-proxy
+    key:     'slackbot:type', // eg. official || custom-without-proxy || custom-with-proxy
     type:    TYPES.STRING,
     type:    TYPES.STRING,
     default: null,
     default: null,
   },
   },