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

Merge pull request #4102 from weseek/feat/6450-growi-side-validation

Feat/6450 growi side validation
Yuki Takei 4 лет назад
Родитель
Сommit
5602ab7b32

+ 3 - 3
packages/app/src/components/Admin/SlackIntegration/ManageCommandsProcess.jsx

@@ -1,10 +1,10 @@
 import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
-import loggerFactory from '@alias/logger';
-
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
-import { toastSuccess, toastError } from '../../../util/apiNotification';
+import loggerFactory from '~/utils/logger';
+
+import { toastSuccess, toastError } from '../../../client/util/apiNotification';
 
 const logger = loggerFactory('growi:SlackIntegration:ManageCommandsProcess');
 

+ 52 - 3
packages/app/src/server/routes/apiv3/slack-integration.js

@@ -4,7 +4,7 @@ const express = require('express');
 const mongoose = require('mongoose');
 const urljoin = require('url-join');
 
-const { verifySlackRequest, generateWebClient } = require('@growi/slack');
+const { verifySlackRequest, generateWebClient, getSupportedGrowiActionsRegExps } = require('@growi/slack');
 
 const logger = loggerFactory('growi:routes:apiv3:slack-integration');
 const router = express.Router();
@@ -44,6 +44,55 @@ module.exports = (crowi) => {
     next();
   }
 
+  async function checkCommandPermission(req, res, next) {
+    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+
+    const relation = await SlackAppIntegration.findOne({ tokenPtoG });
+    const { supportedCommandsForBroadcastUse, supportedCommandsForSingleUse } = relation;
+    const supportedCommands = supportedCommandsForBroadcastUse.concat(supportedCommandsForSingleUse);
+    const supportedGrowiActionsRegExps = getSupportedGrowiActionsRegExps(supportedCommands);
+
+    // get command name from req.body
+    let command = '';
+    let actionId = '';
+    let callbackId = '';
+    let payload;
+    if (req.body.payload) {
+      payload = JSON.parse(req.body.payload);
+    }
+
+    if (req.body.text == null && !payload) { // when /relation-test
+      return next();
+    }
+
+    if (!payload) { // when request is to /commands
+      command = req.body.text.split(' ')[0];
+    }
+    else if (payload.actions) { // when request is to /interactions && block_actions
+      actionId = payload.actions[0].action_id;
+    }
+    else { // when request is to /interactions && view_submission
+      callbackId = payload.view.callback_id;
+    }
+
+    let isActionSupported = false;
+    supportedGrowiActionsRegExps.forEach((regexp) => {
+      if (regexp.test(actionId) || regexp.test(callbackId)) {
+        isActionSupported = true;
+      }
+    });
+
+    // validate
+    if (command && !supportedCommands.includes(command)) {
+      return res.status(403).send(`It is not allowed to run '${command}' command to this GROWI.`);
+    }
+    if ((actionId || callbackId) && !isActionSupported) {
+      return res.status(403).send(`It is not allowed to run '${command}' command to this GROWI.`);
+    }
+
+    next();
+  }
+
   const addSigningSecretToReq = (req, res, next) => {
     req.slackSigningSecret = configManager.getConfig('crowi', 'slackbot:signingSecret');
     return next();
@@ -117,7 +166,7 @@ module.exports = (crowi) => {
     return handleCommands(req, res);
   });
 
-  router.post('/proxied/commands', verifyAccessTokenFromProxy, async(req, res) => {
+  router.post('/proxied/commands', verifyAccessTokenFromProxy, checkCommandPermission, async(req, res) => {
     const { body } = req;
 
     // eslint-disable-next-line max-len
@@ -182,7 +231,7 @@ module.exports = (crowi) => {
     return handleInteractions(req, res);
   });
 
-  router.post('/proxied/interactions', verifyAccessTokenFromProxy, async(req, res) => {
+  router.post('/proxied/interactions', verifyAccessTokenFromProxy, checkCommandPermission, async(req, res) => {
     return handleInteractions(req, res);
   });
 

+ 1 - 1
packages/app/src/server/service/slack-command-handler/search.js

@@ -343,7 +343,7 @@ module.exports = (crowi) => {
           markdownSectionBlock('*Input keywords.*\n Hint\n `/growi search [keyword]`'),
         ],
       });
-      return;
+      return { pages: [] };
     }
 
     const keywords = this.getKeywords(args);

+ 1 - 0
packages/slack/src/index.ts

@@ -24,6 +24,7 @@ export * from './middlewares/verify-growi-to-slack-request';
 export * from './middlewares/verify-slack-request';
 export * from './utils/block-kit-builder';
 export * from './utils/check-communicable';
+export * from './utils/get-supported-growi-actions-regexps';
 export * from './utils/post-ephemeral-errors';
 export * from './utils/reshape-contents-body';
 export * from './utils/slash-command-parser';

+ 3 - 0
packages/slack/src/utils/get-supported-growi-actions-regexps.ts

@@ -0,0 +1,3 @@
+export const getSupportedGrowiActionsRegExps = (supportedGrowiCommands: string[]): RegExp[] => {
+  return supportedGrowiCommands.map(command => new RegExp(`^${command}:\\w+`));
+};

+ 0 - 6
packages/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -27,12 +27,6 @@ import { SectionBlockPayloadDelegator } from '~/services/growi-uri-injector/Sect
 
 const logger = loggerFactory('slackbot-proxy:controllers:growi-to-slack');
 
-<<<<<<< HEAD
-=======
-// temporarily save for selection to growi
-const temporarySinglePostCommands = ['create', 'togetter'];
-
->>>>>>> master
 @Controller('/g2s')
 export class GrowiToSlackCtrl {
 

+ 21 - 1
packages/slackbot-proxy/src/controllers/slack.ts

@@ -7,7 +7,7 @@ import axios from 'axios';
 import { WebAPICallResult } from '@slack/web-api';
 
 import {
-  markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest,
+  markdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest, generateWebClient,
 } from '@growi/slack';
 
 import { Relation } from '~/entities/relation';
@@ -173,7 +173,10 @@ export class SlackCtrl {
       }
     }));
 
+    let isCommandPermitted = false;
+
     if (relationsForSingleUse.length > 0) {
+      isCommandPermitted = true;
       body.growiUrisForSingleUse = relationsForSingleUse.map(v => v.growiUri);
       return this.selectGrowiService.process(growiCommand, authorizeResult, body);
     }
@@ -190,8 +193,25 @@ export class SlackCtrl {
      * forward to GROWI server
      */
     if (relationsForBroadcastUse.length > 0) {
+      isCommandPermitted = true;
       this.sendCommand(growiCommand, relationsForBroadcastUse, body);
     }
+
+    if (!isCommandPermitted) {
+      const botToken = relations[0].installation?.data.bot?.token;
+
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const client = generateWebClient(botToken!);
+
+      return client.chat.postEphemeral({
+        text: 'Error occured.',
+        channel: body.channel_id,
+        user: body.user_id,
+        blocks: [
+          markdownSectionBlock(`It is not allowed to run *'${growiCommand.growiCommandType}'* command to this GROWI.`),
+        ],
+      });
+    }
   }
 
   @Post('/interactions')