Quellcode durchsuchen

feat: add questionnaire scope to access token parser for enhanced route security

reiji-h vor 1 Jahr
Ursprung
Commit
9bc1f803fc

+ 115 - 110
apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts

@@ -3,6 +3,7 @@ import type { Request } from 'express';
 import { Router } from 'express';
 import { body, validationResult } from 'express-validator';
 
+import { SCOPE } from '~/interfaces/scope';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
@@ -61,7 +62,7 @@ module.exports = (crowi: Crowi): Router => {
     return 404;
   };
 
-  router.get('/orders', accessTokenParser(), loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
+  router.get('/orders', accessTokenParser([SCOPE.READ.BASE.QUESTIONNAIRE]), loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const growiInfo = await growiInfoService.getGrowiInfo(true);
     const userInfo = crowi.questionnaireService.getUserInfo(req.user ?? null, getSiteUrlHashed(growiInfo.appSiteUrl));
 
@@ -76,138 +77,142 @@ module.exports = (crowi: Crowi): Router => {
     }
   });
 
-  router.get('/is-enabled', accessTokenParser(), loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
+  router.get('/is-enabled', accessTokenParser([SCOPE.READ.BASE.QUESTIONNAIRE]), loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const isEnabled = configManager.getConfig('questionnaire:isQuestionnaireEnabled');
     return res.apiv3({ isEnabled });
   });
 
-  router.post('/proactive/answer', accessTokenParser(), loginRequired, validators.proactiveAnswer, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    const sendQuestionnaireAnswer = async() => {
-      const questionnaireServerOrigin = configManager.getConfig('app:questionnaireServerOrigin');
-      const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
-      const growiInfo = await growiInfoService.getGrowiInfo(true);
-      const userInfo = crowi.questionnaireService.getUserInfo(req.user ?? null, getSiteUrlHashed(growiInfo.appSiteUrl));
-
-      const proactiveQuestionnaireAnswer: IProactiveQuestionnaireAnswer = {
-        satisfaction: req.body.satisfaction,
-        lengthOfExperience: req.body.lengthOfExperience,
-        position: req.body.position,
-        occupation: req.body.occupation,
-        commentText: req.body.commentText,
-        growiInfo,
-        userInfo,
-        answeredAt: new Date(),
+  router.post('/proactive/answer', accessTokenParser([SCOPE.WRITE.BASE.QUESTIONNAIRE]), loginRequired,
+    validators.proactiveAnswer, async(req: AuthorizedRequest, res: ApiV3Response) => {
+      const sendQuestionnaireAnswer = async() => {
+        const questionnaireServerOrigin = configManager.getConfig('app:questionnaireServerOrigin');
+        const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
+        const growiInfo = await growiInfoService.getGrowiInfo(true);
+        const userInfo = crowi.questionnaireService.getUserInfo(req.user ?? null, getSiteUrlHashed(growiInfo.appSiteUrl));
+
+        const proactiveQuestionnaireAnswer: IProactiveQuestionnaireAnswer = {
+          satisfaction: req.body.satisfaction,
+          lengthOfExperience: req.body.lengthOfExperience,
+          position: req.body.position,
+          occupation: req.body.occupation,
+          commentText: req.body.commentText,
+          growiInfo,
+          userInfo,
+          answeredAt: new Date(),
+        };
+
+        const proactiveQuestionnaireAnswerLegacy = convertToLegacyFormat(proactiveQuestionnaireAnswer, isAppSiteUrlHashed);
+
+        try {
+          await axios.post(`${questionnaireServerOrigin}/questionnaire-answer/proactive`, proactiveQuestionnaireAnswerLegacy);
+        }
+        catch (err) {
+          if (err.request != null) {
+          // when failed to send, save to resend in cronjob
+            await ProactiveQuestionnaireAnswer.create(proactiveQuestionnaireAnswer);
+          }
+          else {
+            throw err;
+          }
+        }
       };
 
-      const proactiveQuestionnaireAnswerLegacy = convertToLegacyFormat(proactiveQuestionnaireAnswer, isAppSiteUrlHashed);
+      const errors = validationResult(req);
+      if (!errors.isEmpty()) {
+        return res.status(400).json({ errors: errors.array() });
+      }
 
       try {
-        await axios.post(`${questionnaireServerOrigin}/questionnaire-answer/proactive`, proactiveQuestionnaireAnswerLegacy);
+        await sendQuestionnaireAnswer();
+        return res.apiv3({});
       }
       catch (err) {
-        if (err.request != null) {
-          // when failed to send, save to resend in cronjob
-          await ProactiveQuestionnaireAnswer.create(proactiveQuestionnaireAnswer);
+        logger.error(err);
+        return res.apiv3Err(err, 500);
+      }
+    });
+
+  router.put('/answer', accessTokenParser([SCOPE.WRITE.BASE.QUESTIONNAIRE]), loginRequired,
+    validators.answer, async(req: AuthorizedRequest, res: ApiV3Response) => {
+      const sendQuestionnaireAnswer = async(user: IUserHasId, answers: IAnswer[]) => {
+        const questionnaireServerOrigin = crowi.configManager.getConfig('app:questionnaireServerOrigin');
+        const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
+        const growiInfo = await growiInfoService.getGrowiInfo(true);
+        const userInfo = crowi.questionnaireService.getUserInfo(user, getSiteUrlHashed(growiInfo.appSiteUrl));
+
+        const questionnaireAnswer: IQuestionnaireAnswer = {
+          growiInfo,
+          userInfo,
+          answers,
+          answeredAt: new Date(),
+          questionnaireOrder: req.body.questionnaireOrderId,
+        };
+
+        const questionnaireAnswerLegacy = convertToLegacyFormat(questionnaireAnswer, isAppSiteUrlHashed);
+
+        try {
+          await axios.post(`${questionnaireServerOrigin}/questionnaire-answer`, questionnaireAnswerLegacy);
         }
-        else {
-          throw err;
+        catch (err) {
+          if (err.request != null) {
+          // when failed to send, save to resend in cronjob
+            await QuestionnaireAnswer.create(questionnaireAnswer);
+          }
+          else {
+            throw err;
+          }
         }
-      }
-    };
-
-    const errors = validationResult(req);
-    if (!errors.isEmpty()) {
-      return res.status(400).json({ errors: errors.array() });
-    }
-
-    try {
-      await sendQuestionnaireAnswer();
-      return res.apiv3({});
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err(err, 500);
-    }
-  });
-
-  router.put('/answer', accessTokenParser(), loginRequired, validators.answer, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    const sendQuestionnaireAnswer = async(user: IUserHasId, answers: IAnswer[]) => {
-      const questionnaireServerOrigin = crowi.configManager.getConfig('app:questionnaireServerOrigin');
-      const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
-      const growiInfo = await growiInfoService.getGrowiInfo(true);
-      const userInfo = crowi.questionnaireService.getUserInfo(user, getSiteUrlHashed(growiInfo.appSiteUrl));
-
-      const questionnaireAnswer: IQuestionnaireAnswer = {
-        growiInfo,
-        userInfo,
-        answers,
-        answeredAt: new Date(),
-        questionnaireOrder: req.body.questionnaireOrderId,
       };
 
-      const questionnaireAnswerLegacy = convertToLegacyFormat(questionnaireAnswer, isAppSiteUrlHashed);
+      const errors = validationResult(req);
+      if (!errors.isEmpty()) {
+        return res.status(400).json({ errors: errors.array() });
+      }
 
       try {
-        await axios.post(`${questionnaireServerOrigin}/questionnaire-answer`, questionnaireAnswerLegacy);
+        await sendQuestionnaireAnswer(req.user ?? null, req.body.answers);
+        const status = await changeAnswerStatus(req.user, req.body.questionnaireOrderId, StatusType.answered);
+        return res.apiv3({}, status);
       }
       catch (err) {
-        if (err.request != null) {
-          // when failed to send, save to resend in cronjob
-          await QuestionnaireAnswer.create(questionnaireAnswer);
-        }
-        else {
-          throw err;
-        }
+        logger.error(err);
+        return res.apiv3Err(err, 500);
       }
-    };
+    });
 
-    const errors = validationResult(req);
-    if (!errors.isEmpty()) {
-      return res.status(400).json({ errors: errors.array() });
-    }
-
-    try {
-      await sendQuestionnaireAnswer(req.user ?? null, req.body.answers);
-      const status = await changeAnswerStatus(req.user, req.body.questionnaireOrderId, StatusType.answered);
-      return res.apiv3({}, status);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err(err, 500);
-    }
-  });
-
-  router.put('/skip', accessTokenParser(), loginRequired, validators.skipDeny, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    const errors = validationResult(req);
-    if (!errors.isEmpty()) {
-      return res.status(400).json({ errors: errors.array() });
-    }
+  router.put('/skip', accessTokenParser([SCOPE.WRITE.BASE.QUESTIONNAIRE]), loginRequired,
+    validators.skipDeny, async(req: AuthorizedRequest, res: ApiV3Response) => {
+      const errors = validationResult(req);
+      if (!errors.isEmpty()) {
+        return res.status(400).json({ errors: errors.array() });
+      }
 
-    try {
-      const status = await changeAnswerStatus(req.user, req.body.questionnaireOrderId, StatusType.skipped);
-      return res.apiv3({}, status);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err(err, 500);
-    }
-  });
+      try {
+        const status = await changeAnswerStatus(req.user, req.body.questionnaireOrderId, StatusType.skipped);
+        return res.apiv3({}, status);
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err(err, 500);
+      }
+    });
 
-  router.put('/deny', accessTokenParser(), loginRequired, validators.skipDeny, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    const errors = validationResult(req);
-    if (!errors.isEmpty()) {
-      return res.status(400).json({ errors: errors.array() });
-    }
+  router.put('/deny', accessTokenParser([SCOPE.WRITE.BASE.QUESTIONNAIRE]), loginRequired,
+    validators.skipDeny, async(req: AuthorizedRequest, res: ApiV3Response) => {
+      const errors = validationResult(req);
+      if (!errors.isEmpty()) {
+        return res.status(400).json({ errors: errors.array() });
+      }
 
-    try {
-      const status = await changeAnswerStatus(req.user, req.body.questionnaireOrderId, StatusType.denied);
-      return res.apiv3({}, status);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err(err, 500);
-    }
-  });
+      try {
+        const status = await changeAnswerStatus(req.user, req.body.questionnaireOrderId, StatusType.denied);
+        return res.apiv3({}, status);
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err(err, 500);
+      }
+    });
 
   return router;
 

+ 1 - 0
apps/app/src/interfaces/scope.ts

@@ -38,6 +38,7 @@ export const ORIGINAL_SCOPE_USER = {
     page: {},
     share_link: {},
     bookmark: {},
+    questionnaire: {},
   },
 } as const;
 

+ 329 - 288
apps/app/src/server/routes/apiv3/user-group.js

@@ -7,6 +7,8 @@ import {
 } from 'express-validator';
 
 import { SupportedAction } from '~/interfaces/activity';
+import { SCOPE } from '~/interfaces/scope';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers/user-group-relation-serializer';
 import UserGroup from '~/server/models/user-group';
 import UserGroupRelation from '~/server/models/user-group-relation';
@@ -103,7 +105,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: a result of `UserGroup.find`
    */
-  router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired, async(req, res) => {
     const { query } = req;
 
     try {
@@ -155,37 +157,43 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: userGroup objects
    */
-  router.get('/ancestors', loginRequiredStrictly, adminRequired, validator.ancestorGroup, async(req, res) => {
-    const { groupId } = req.query;
-
-    try {
-      const userGroup = await UserGroup.findById(groupId);
-      const ancestorUserGroups = await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
-      return res.apiv3({ ancestorUserGroups });
-    }
-    catch (err) {
-      const msg = 'Error occurred while searching user groups';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
-    }
-  });
-
-  router.get('/children', loginRequiredStrictly, adminRequired, validator.listChildren, async(req, res) => {
-    try {
-      const { parentIds, includeGrandChildren = false } = req.query;
-
-      const userGroupsResult = await UserGroup.findChildrenByParentIds(parentIds, includeGrandChildren);
-      return res.apiv3({
-        childUserGroups: userGroupsResult.childUserGroups,
-        grandChildUserGroups: userGroupsResult.grandChildUserGroups,
-      });
-    }
-    catch (err) {
-      const msg = 'Error occurred in fetching child user group list';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'child-user-group-list-fetch-failed'));
-    }
-  });
+  router.get('/ancestors',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.ancestorGroup,
+    async(req, res) => {
+      const { groupId } = req.query;
+
+      try {
+        const userGroup = await UserGroup.findById(groupId);
+        const ancestorUserGroups = await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
+        return res.apiv3({ ancestorUserGroups });
+      }
+      catch (err) {
+        const msg = 'Error occurred while searching user groups';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
+      }
+    });
+
+  router.get('/children',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.listChildren,
+    async(req, res) => {
+      try {
+        const { parentIds, includeGrandChildren = false } = req.query;
+
+        const userGroupsResult = await UserGroup.findChildrenByParentIds(parentIds, includeGrandChildren);
+        return res.apiv3({
+          childUserGroups: userGroupsResult.childUserGroups,
+          grandChildUserGroups: userGroupsResult.grandChildUserGroups,
+        });
+      }
+      catch (err) {
+        const msg = 'Error occurred in fetching child user group list';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'child-user-group-list-fetch-failed'));
+      }
+    });
 
 
   /**
@@ -218,25 +226,28 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: A result of `UserGroup.createGroupByName`
    */
-  router.post('/', loginRequiredStrictly, adminRequired, addActivity, validator.create, apiV3FormValidator, async(req, res) => {
-    const { name, description = '', parentId } = req.body;
-
-    try {
-      const userGroupName = generalXssFilter.process(name);
-      const userGroupDescription = generalXssFilter.process(description);
-      const userGroup = await UserGroup.createGroup(userGroupName, userGroupDescription, parentId);
-
-      const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_CREATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-
-      return res.apiv3({ userGroup }, 201);
-    }
-    catch (err) {
-      const msg = 'Error occurred in creating a user group';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-create-failed'));
-    }
-  });
+  router.post('/',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    addActivity, validator.create, apiV3FormValidator,
+    async(req, res) => {
+      const { name, description = '', parentId } = req.body;
+
+      try {
+        const userGroupName = generalXssFilter.process(name);
+        const userGroupDescription = generalXssFilter.process(description);
+        const userGroup = await UserGroup.createGroup(userGroupName, userGroupDescription, parentId);
+
+        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_CREATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3({ userGroup }, 201);
+      }
+      catch (err) {
+        const msg = 'Error occurred in creating a user group';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-create-failed'));
+      }
+    });
 
   /**
    * @swagger
@@ -268,24 +279,27 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: userGroup objects
    */
-  router.get('/selectable-parent-groups', loginRequiredStrictly, adminRequired, validator.selectableGroups, async(req, res) => {
-    const { groupId } = req.query;
-
-    try {
-      const userGroup = await UserGroup.findById(groupId);
-
-      const descendantGroups = await UserGroup.findGroupsWithDescendantsRecursively([userGroup], []);
-      const descendantGroupIds = descendantGroups.map(userGroups => userGroups._id.toString());
-
-      const selectableParentGroups = await UserGroup.find({ _id: { $nin: [groupId, ...descendantGroupIds] } });
-      return res.apiv3({ selectableParentGroups });
-    }
-    catch (err) {
-      const msg = 'Error occurred while searching user groups';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
-    }
-  });
+  router.get('/selectable-parent-groups',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.selectableGroups,
+    async(req, res) => {
+      const { groupId } = req.query;
+
+      try {
+        const userGroup = await UserGroup.findById(groupId);
+
+        const descendantGroups = await UserGroup.findGroupsWithDescendantsRecursively([userGroup], []);
+        const descendantGroupIds = descendantGroups.map(userGroups => userGroups._id.toString());
+
+        const selectableParentGroups = await UserGroup.find({ _id: { $nin: [groupId, ...descendantGroupIds] } });
+        return res.apiv3({ selectableParentGroups });
+      }
+      catch (err) {
+        const msg = 'Error occurred while searching user groups';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
+      }
+    });
 
   /**
    * @swagger
@@ -317,27 +331,30 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: userGroup objects
    */
-  router.get('/selectable-child-groups', loginRequiredStrictly, adminRequired, validator.selectableGroups, async(req, res) => {
-    const { groupId } = req.query;
-
-    try {
-      const userGroup = await UserGroup.findById(groupId);
-
-      const [ancestorGroups, descendantGroups] = await Promise.all([
-        UserGroup.findGroupsWithAncestorsRecursively(userGroup, []),
-        UserGroup.findGroupsWithDescendantsRecursively([userGroup], []),
-      ]);
-
-      const excludeUserGroupIds = [userGroup, ...ancestorGroups, ...descendantGroups].map(userGroups => userGroups._id.toString());
-      const selectableChildGroups = await UserGroup.find({ _id: { $nin: excludeUserGroupIds } });
-      return res.apiv3({ selectableChildGroups });
-    }
-    catch (err) {
-      const msg = 'Error occurred while searching user groups';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
-    }
-  });
+  router.get('/selectable-child-groups',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.selectableGroups,
+    async(req, res) => {
+      const { groupId } = req.query;
+
+      try {
+        const userGroup = await UserGroup.findById(groupId);
+
+        const [ancestorGroups, descendantGroups] = await Promise.all([
+          UserGroup.findGroupsWithAncestorsRecursively(userGroup, []),
+          UserGroup.findGroupsWithDescendantsRecursively([userGroup], []),
+        ]);
+
+        const excludeUserGroupIds = [userGroup, ...ancestorGroups, ...descendantGroups].map(userGroups => userGroups._id.toString());
+        const selectableChildGroups = await UserGroup.find({ _id: { $nin: excludeUserGroupIds } });
+        return res.apiv3({ selectableChildGroups });
+      }
+      catch (err) {
+        const msg = 'Error occurred while searching user groups';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
+      }
+    });
 
   /**
    * @swagger
@@ -367,19 +384,22 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: userGroup object
    */
-  router.get('/:id', loginRequiredStrictly, adminRequired, validator.selectableGroups, async(req, res) => {
-    const { id: groupId } = req.params;
-
-    try {
-      const userGroup = await UserGroup.findById(groupId);
-      return res.apiv3({ userGroup });
-    }
-    catch (err) {
-      const msg = 'Error occurred while getting user group';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-groups-get-failed'));
-    }
-  });
+  router.get('/:id',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.selectableGroups,
+    async(req, res) => {
+      const { id: groupId } = req.params;
+
+      try {
+        const userGroup = await UserGroup.findById(groupId);
+        return res.apiv3({ userGroup });
+      }
+      catch (err) {
+        const msg = 'Error occurred while getting user group';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-groups-get-failed'));
+      }
+    });
 
   /**
    * @swagger
@@ -419,31 +439,34 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: A result of `UserGroup.removeCompletelyById`
    */
-  router.delete('/:id', loginRequiredStrictly, adminRequired, validator.delete, apiV3FormValidator, addActivity, async(req, res) => {
-    const { id: deleteGroupId } = req.params;
-    const { actionName, transferToUserGroupId, transferToUserGroupType } = req.query;
-
-    const transferToUserGroup = typeof transferToUserGroupId === 'string'
+  router.delete('/:id',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.delete, apiV3FormValidator, addActivity,
+    async(req, res) => {
+      const { id: deleteGroupId } = req.params;
+      const { actionName, transferToUserGroupId, transferToUserGroupType } = req.query;
+
+      const transferToUserGroup = typeof transferToUserGroupId === 'string'
         && (transferToUserGroupType === GroupType.userGroup || transferToUserGroupType === GroupType.externalUserGroup)
-      ? {
-        item: transferToUserGroupId,
-        type: transferToUserGroupType,
-      } : undefined;
-
-    try {
-      const userGroups = await crowi.userGroupService.removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferToUserGroup);
-
-      const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-
-      return res.apiv3({ userGroups });
-    }
-    catch (err) {
-      const msg = 'Error occurred while deleting user groups';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-groups-delete-failed'));
-    }
-  });
+        ? {
+          item: transferToUserGroupId,
+          type: transferToUserGroupType,
+        } : undefined;
+
+      try {
+        const userGroups = await crowi.userGroupService.removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferToUserGroup);
+
+        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3({ userGroups });
+      }
+      catch (err) {
+        const msg = 'Error occurred while deleting user groups';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-groups-delete-failed'));
+      }
+    });
 
   /**
    * @swagger
@@ -473,26 +496,29 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: A result of `UserGroup.updateName`
    */
-  router.put('/:id', loginRequiredStrictly, adminRequired, validator.update, apiV3FormValidator, addActivity, async(req, res) => {
-    const { id } = req.params;
-    const {
-      name, description, parentId, forceUpdateParents = false,
-    } = req.body;
-
-    try {
-      const userGroup = await crowi.userGroupService.updateGroup(id, name, description, parentId, forceUpdateParents);
-
-      const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_UPDATE };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-
-      return res.apiv3({ userGroup });
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating a user group name';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-update-failed'));
-    }
-  });
+  router.put('/:id',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.update, apiV3FormValidator, addActivity,
+    async(req, res) => {
+      const { id } = req.params;
+      const {
+        name, description, parentId, forceUpdateParents = false,
+      } = req.body;
+
+      try {
+        const userGroup = await crowi.userGroupService.updateGroup(id, name, description, parentId, forceUpdateParents);
+
+        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_UPDATE };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3({ userGroup });
+      }
+      catch (err) {
+        const msg = 'Error occurred in updating a user group name';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-update-failed'));
+      }
+    });
 
 
   /**
@@ -525,26 +551,28 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: user objects
    */
-  router.get('/:id/users', loginRequiredStrictly, adminRequired, async(req, res) => {
-    const { id } = req.params;
-
-    try {
-      const userGroup = await UserGroup.findById(id);
-      const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
-
-      const serializeUsers = userGroupRelations.map((userGroupRelation) => {
-        return serializeUserSecurely(userGroupRelation.relatedUser);
-      });
-      const users = serializeUsers.filter(user => user != null);
-
-      return res.apiv3({ users });
-    }
-    catch (err) {
-      const msg = `Error occurred in fetching users for group: ${id}`;
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-user-list-fetch-failed'));
-    }
-  });
+  router.get('/:id/users',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    async(req, res) => {
+      const { id } = req.params;
+
+      try {
+        const userGroup = await UserGroup.findById(id);
+        const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
+
+        const serializeUsers = userGroupRelations.map((userGroupRelation) => {
+          return serializeUserSecurely(userGroupRelation.relatedUser);
+        });
+        const users = serializeUsers.filter(user => user != null);
+
+        return res.apiv3({ users });
+      }
+      catch (err) {
+        const msg = `Error occurred in fetching users for group: ${id}`;
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-user-list-fetch-failed'));
+      }
+    });
 
   /**
    * @swagger
@@ -576,35 +604,37 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: user objects
    */
-  router.get('/:id/unrelated-users', loginRequiredStrictly, adminRequired, async(req, res) => {
-    const { id } = req.params;
-    const {
-      searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
-    } = req.query;
-
-    const queryOptions = {
-      searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
-    };
-
-    try {
-      const userGroup = await UserGroup.findById(id);
-      const users = await UserGroupRelation.findUserByNotRelatedGroup(userGroup, queryOptions);
-
-      // return email only this api
-      const serializedUsers = users.map((user) => {
-        const { email } = user;
-        const serializedUser = serializeUserSecurely(user);
-        serializedUser.email = email;
-        return serializedUser;
-      });
-      return res.apiv3({ users: serializedUsers });
-    }
-    catch (err) {
-      const msg = `Error occurred in fetching unrelated users for group: ${id}`;
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-unrelated-user-list-fetch-failed'));
-    }
-  });
+  router.get('/:id/unrelated-users',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    async(req, res) => {
+      const { id } = req.params;
+      const {
+        searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
+      } = req.query;
+
+      const queryOptions = {
+        searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
+      };
+
+      try {
+        const userGroup = await UserGroup.findById(id);
+        const users = await UserGroupRelation.findUserByNotRelatedGroup(userGroup, queryOptions);
+
+        // return email only this api
+        const serializedUsers = users.map((user) => {
+          const { email } = user;
+          const serializedUser = serializeUserSecurely(user);
+          serializedUser.email = email;
+          return serializedUser;
+        });
+        return res.apiv3({ users: serializedUsers });
+      }
+      catch (err) {
+        const msg = `Error occurred in fetching unrelated users for group: ${id}`;
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-unrelated-user-list-fetch-failed'));
+      }
+    });
 
 
   /**
@@ -642,36 +672,39 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: the associative entity between user and userGroup
    */
-  router.post('/:id/users/:username', loginRequiredStrictly, adminRequired, validator.users.post, apiV3FormValidator, addActivity, async(req, res) => {
-    const { id, username } = req.params;
-
-    try {
-      const [userGroup, user] = await Promise.all([
-        UserGroup.findById(id),
-        User.findUserByUsername(username),
-      ]);
-
-      const userGroups = await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
-      const userGroupIds = userGroups.map(g => g._id);
+  router.post('/:id/users/:username',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.users.post, apiV3FormValidator, addActivity,
+    async(req, res) => {
+      const { id, username } = req.params;
+
+      try {
+        const [userGroup, user] = await Promise.all([
+          UserGroup.findById(id),
+          User.findUserByUsername(username),
+        ]);
+
+        const userGroups = await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
+        const userGroupIds = userGroups.map(g => g._id);
+
+        // remove existing relations from list to create
+        const existingRelations = await UserGroupRelation.find({ relatedGroup: { $in: userGroupIds }, relatedUser: user._id });
+        const existingGroupIds = existingRelations.map(r => r.relatedGroup);
+        const groupIdsToCreateRelation = excludeTestIdsFromTargetIds(userGroupIds, existingGroupIds);
+
+        const insertedRelations = await UserGroupRelation.createRelations(groupIdsToCreateRelation, user);
+        const serializedUser = serializeUserSecurely(user);
 
-      // remove existing relations from list to create
-      const existingRelations = await UserGroupRelation.find({ relatedGroup: { $in: userGroupIds }, relatedUser: user._id });
-      const existingGroupIds = existingRelations.map(r => r.relatedGroup);
-      const groupIdsToCreateRelation = excludeTestIdsFromTargetIds(userGroupIds, existingGroupIds);
-
-      const insertedRelations = await UserGroupRelation.createRelations(groupIdsToCreateRelation, user);
-      const serializedUser = serializeUserSecurely(user);
-
-      const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_ADD_USER };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
-      return res.apiv3({ user: serializedUser, createdRelationCount: insertedRelations.length });
-    }
-    catch (err) {
-      const msg = `Error occurred in adding the user "${username}" to group "${id}"`;
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-add-user-failed'));
-    }
-  });
+        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_ADD_USER };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+        return res.apiv3({ user: serializedUser, createdRelationCount: insertedRelations.length });
+      }
+      catch (err) {
+        const msg = `Error occurred in adding the user "${username}" to group "${id}"`;
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-add-user-failed'));
+      }
+    });
 
 
   /**
@@ -709,21 +742,24 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: the associative entity between user and userGroup
    */
-  router.delete('/:id/users/:username', loginRequiredStrictly, adminRequired, validator.users.delete, apiV3FormValidator, async(req, res) => {
-    const { id: userGroupId, username } = req.params;
-
-    try {
-      const removedUserRes = await crowi.userGroupService.removeUserByUsername(userGroupId, username);
-      const serializedUser = serializeUserSecurely(removedUserRes.user);
-
-      return res.apiv3({ user: serializedUser, deletedGroupsCount: removedUserRes.deletedGroupsCount });
-    }
-    catch (err) {
-      const msg = 'Error occurred while removing the user from groups.';
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-remove-user-failed'));
-    }
-  });
+  router.delete('/:id/users/:username',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.users.delete, apiV3FormValidator,
+    async(req, res) => {
+      const { id: userGroupId, username } = req.params;
+
+      try {
+        const removedUserRes = await crowi.userGroupService.removeUserByUsername(userGroupId, username);
+        const serializedUser = serializeUserSecurely(removedUserRes.user);
+
+        return res.apiv3({ user: serializedUser, deletedGroupsCount: removedUserRes.deletedGroupsCount });
+      }
+      catch (err) {
+        const msg = 'Error occurred while removing the user from groups.';
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-remove-user-failed'));
+      }
+    });
 
 
   /**
@@ -756,21 +792,23 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: userGroupRelation objects
    */
-  router.get('/:id/user-group-relations', loginRequiredStrictly, adminRequired, async(req, res) => {
-    const { id } = req.params;
-
-    try {
-      const userGroup = await UserGroup.findById(id);
-      const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
-      const serialized = userGroupRelations.map(relation => serializeUserGroupRelationSecurely(relation));
-      return res.apiv3({ userGroupRelations: serialized });
-    }
-    catch (err) {
-      const msg = `Error occurred in fetching user group relations for group: ${id}`;
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-user-group-relation-list-fetch-failed'));
-    }
-  });
+  router.get('/:id/user-group-relations',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    async(req, res) => {
+      const { id } = req.params;
+
+      try {
+        const userGroup = await UserGroup.findById(id);
+        const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
+        const serialized = userGroupRelations.map(relation => serializeUserGroupRelationSecurely(relation));
+        return res.apiv3({ userGroupRelations: serialized });
+      }
+      catch (err) {
+        const msg = `Error occurred in fetching user group relations for group: ${id}`;
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-user-group-relation-list-fetch-failed'));
+      }
+    });
 
 
   /**
@@ -803,40 +841,43 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: page objects
    */
-  router.get('/:id/pages', loginRequiredStrictly, adminRequired, validator.pages.get, apiV3FormValidator, async(req, res) => {
-    const { id } = req.params;
-    const { limit, offset } = req.query;
-
-    try {
-      const { docs, totalDocs } = await Page.paginate({
-        grant: Page.GRANT_USER_GROUP,
-        grantedGroups: {
-          $elemMatch: {
-            item: id,
+  router.get('/:id/pages',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+    validator.pages.get, apiV3FormValidator,
+    async(req, res) => {
+      const { id } = req.params;
+      const { limit, offset } = req.query;
+
+      try {
+        const { docs, totalDocs } = await Page.paginate({
+          grant: Page.GRANT_USER_GROUP,
+          grantedGroups: {
+            $elemMatch: {
+              item: id,
+            },
           },
-        },
-      }, {
-        offset,
-        limit,
-        populate: 'lastUpdateUser',
-      });
-
-      const current = offset / limit + 1;
-
-      const pages = docs.map((doc) => {
-        doc.lastUpdateUser = serializeUserSecurely(doc.lastUpdateUser);
-        return doc;
-      });
-
-      // TODO: create a common moudule for paginated response
-      return res.apiv3({ total: totalDocs, current, pages });
-    }
-    catch (err) {
-      const msg = `Error occurred in fetching pages for group: ${id}`;
-      logger.error(msg, err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-page-list-fetch-failed'));
-    }
-  });
+        }, {
+          offset,
+          limit,
+          populate: 'lastUpdateUser',
+        });
+
+        const current = offset / limit + 1;
+
+        const pages = docs.map((doc) => {
+          doc.lastUpdateUser = serializeUserSecurely(doc.lastUpdateUser);
+          return doc;
+        });
+
+        // TODO: create a common moudule for paginated response
+        return res.apiv3({ total: totalDocs, current, pages });
+      }
+      catch (err) {
+        const msg = `Error occurred in fetching pages for group: ${id}`;
+        logger.error(msg, err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-page-list-fetch-failed'));
+      }
+    });
 
   return router;
 };