|
|
@@ -3,9 +3,7 @@ import { SCOPE } from '@growi/core/dist/interfaces';
|
|
|
import { ErrorV3 } from '@growi/core/dist/models';
|
|
|
import type { Request } from 'express';
|
|
|
import { Router } from 'express';
|
|
|
-import {
|
|
|
- body, param, query, validationResult,
|
|
|
-} from 'express-validator';
|
|
|
+import { body, param, query, validationResult } from 'express-validator';
|
|
|
|
|
|
import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
|
|
|
import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
|
|
|
@@ -26,7 +24,7 @@ const logger = loggerFactory('growi:routes:apiv3:external-user-group');
|
|
|
const router = Router();
|
|
|
|
|
|
interface AuthorizedRequest extends Request {
|
|
|
- user?: any
|
|
|
+ user?: any;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -45,55 +43,69 @@ interface AuthorizedRequest extends Request {
|
|
|
* type: number
|
|
|
*/
|
|
|
module.exports = (crowi: Crowi): Router => {
|
|
|
- const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
|
|
|
+ const loginRequiredStrictly = require('~/server/middlewares/login-required')(
|
|
|
+ crowi,
|
|
|
+ );
|
|
|
const adminRequired = require('~/server/middlewares/admin-required')(crowi);
|
|
|
const addActivity = generateAddActivityMiddleware();
|
|
|
|
|
|
const activityEvent = crowi.event('activity');
|
|
|
|
|
|
const isExecutingSync = () => {
|
|
|
- return crowi.ldapUserGroupSyncService?.syncStatus?.isExecutingSync || crowi.keycloakUserGroupSyncService?.syncStatus?.isExecutingSync || false;
|
|
|
+ return (
|
|
|
+ crowi.ldapUserGroupSyncService?.syncStatus?.isExecutingSync ||
|
|
|
+ crowi.keycloakUserGroupSyncService?.syncStatus?.isExecutingSync ||
|
|
|
+ false
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
const validators = {
|
|
|
ldapSyncSettings: [
|
|
|
body('ldapGroupSearchBase').optional({ nullable: true }).isString(),
|
|
|
- body('ldapGroupMembershipAttribute').exists({ checkFalsy: true }).isString(),
|
|
|
- body('ldapGroupMembershipAttributeType').exists({ checkFalsy: true }).isString(),
|
|
|
- body('ldapGroupChildGroupAttribute').exists({ checkFalsy: true }).isString(),
|
|
|
+ body('ldapGroupMembershipAttribute')
|
|
|
+ .exists({ checkFalsy: true })
|
|
|
+ .isString(),
|
|
|
+ body('ldapGroupMembershipAttributeType')
|
|
|
+ .exists({ checkFalsy: true })
|
|
|
+ .isString(),
|
|
|
+ body('ldapGroupChildGroupAttribute')
|
|
|
+ .exists({ checkFalsy: true })
|
|
|
+ .isString(),
|
|
|
body('autoGenerateUserOnLdapGroupSync').exists().isBoolean(),
|
|
|
body('preserveDeletedLdapGroups').exists().isBoolean(),
|
|
|
body('ldapGroupNameAttribute').optional({ nullable: true }).isString(),
|
|
|
- body('ldapGroupDescriptionAttribute').optional({ nullable: true }).isString(),
|
|
|
+ body('ldapGroupDescriptionAttribute')
|
|
|
+ .optional({ nullable: true })
|
|
|
+ .isString(),
|
|
|
],
|
|
|
keycloakSyncSettings: [
|
|
|
body('keycloakHost').exists({ checkFalsy: true }).isString(),
|
|
|
body('keycloakGroupRealm').exists({ checkFalsy: true }).isString(),
|
|
|
- body('keycloakGroupSyncClientRealm').exists({ checkFalsy: true }).isString(),
|
|
|
+ body('keycloakGroupSyncClientRealm')
|
|
|
+ .exists({ checkFalsy: true })
|
|
|
+ .isString(),
|
|
|
body('keycloakGroupSyncClientID').exists({ checkFalsy: true }).isString(),
|
|
|
- body('keycloakGroupSyncClientSecret').exists({ checkFalsy: true }).isString(),
|
|
|
+ body('keycloakGroupSyncClientSecret')
|
|
|
+ .exists({ checkFalsy: true })
|
|
|
+ .isString(),
|
|
|
body('autoGenerateUserOnKeycloakGroupSync').exists().isBoolean(),
|
|
|
body('preserveDeletedKeycloakGroups').exists().isBoolean(),
|
|
|
- body('keycloakGroupDescriptionAttribute').optional({ nullable: true }).isString(),
|
|
|
+ body('keycloakGroupDescriptionAttribute')
|
|
|
+ .optional({ nullable: true })
|
|
|
+ .isString(),
|
|
|
],
|
|
|
listChildren: [
|
|
|
query('parentIds').optional().isArray(),
|
|
|
query('includeGrandChildren').optional().isBoolean(),
|
|
|
],
|
|
|
- ancestorGroup: [
|
|
|
- query('groupId').isString(),
|
|
|
- ],
|
|
|
- update: [
|
|
|
- body('description').optional().isString(),
|
|
|
- ],
|
|
|
+ ancestorGroup: [query('groupId').isString()],
|
|
|
+ update: [body('description').optional().isString()],
|
|
|
delete: [
|
|
|
param('id').trim().exists({ checkFalsy: true }),
|
|
|
query('actionName').trim().exists({ checkFalsy: true }),
|
|
|
query('transferToUserGroupId').trim(),
|
|
|
],
|
|
|
- detail: [
|
|
|
- param('id').isString(),
|
|
|
- ],
|
|
|
+ detail: [param('id').isString()],
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -143,28 +155,43 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* pagingLimit:
|
|
|
* type: integer
|
|
|
*/
|
|
|
- router.get('/', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
+ router.get(
|
|
|
+ '/',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const { query } = req;
|
|
|
|
|
|
try {
|
|
|
- const page = query.page != null ? parseInt(query.page as string) : undefined;
|
|
|
- const limit = query.limit != null ? parseInt(query.limit as string) : undefined;
|
|
|
- const offset = query.offset != null ? parseInt(query.offset as string) : undefined;
|
|
|
- const pagination = query.pagination != null ? query.pagination !== 'false' : undefined;
|
|
|
+ const page =
|
|
|
+ query.page != null ? parseInt(query.page as string) : undefined;
|
|
|
+ const limit =
|
|
|
+ query.limit != null ? parseInt(query.limit as string) : undefined;
|
|
|
+ const offset =
|
|
|
+ query.offset != null ? parseInt(query.offset as string) : undefined;
|
|
|
+ const pagination =
|
|
|
+ query.pagination != null ? query.pagination !== 'false' : undefined;
|
|
|
|
|
|
const result = await ExternalUserGroup.findWithPagination({
|
|
|
- page, limit, offset, pagination,
|
|
|
+ page,
|
|
|
+ limit,
|
|
|
+ offset,
|
|
|
+ pagination,
|
|
|
});
|
|
|
- const { docs: userGroups, totalDocs: totalUserGroups, limit: pagingLimit } = result;
|
|
|
+ const {
|
|
|
+ docs: userGroups,
|
|
|
+ totalDocs: totalUserGroups,
|
|
|
+ limit: pagingLimit,
|
|
|
+ } = result;
|
|
|
return res.apiv3({ userGroups, totalUserGroups, pagingLimit });
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = 'Error occurred in fetching external user group list';
|
|
|
logger.error('Error', err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -195,22 +222,30 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* items:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.get('/ancestors', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- validators.ancestorGroup, apiV3FormValidator,
|
|
|
- async(req, res: ApiV3Response) => {
|
|
|
+ router.get(
|
|
|
+ '/ancestors',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ validators.ancestorGroup,
|
|
|
+ apiV3FormValidator,
|
|
|
+ async (req, res: ApiV3Response) => {
|
|
|
const { groupId } = req.query;
|
|
|
|
|
|
try {
|
|
|
- const userGroup = await ExternalUserGroup.findOne({ _id: { $eq: groupId } });
|
|
|
- const ancestorUserGroups = await ExternalUserGroup.findGroupsWithAncestorsRecursively(userGroup);
|
|
|
+ const userGroup = await ExternalUserGroup.findOne({
|
|
|
+ _id: { $eq: groupId },
|
|
|
+ });
|
|
|
+ const ancestorUserGroups =
|
|
|
+ await ExternalUserGroup.findGroupsWithAncestorsRecursively(userGroup);
|
|
|
return res.apiv3({ ancestorUserGroups });
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = 'Error occurred while searching user groups';
|
|
|
logger.error(msg, err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -251,23 +286,32 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* items:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.get('/children', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired, validators.listChildren,
|
|
|
- async(req, res) => {
|
|
|
+ router.get(
|
|
|
+ '/children',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ validators.listChildren,
|
|
|
+ async (req, res) => {
|
|
|
try {
|
|
|
const { parentIds, includeGrandChildren = false } = req.query;
|
|
|
|
|
|
- const externalUserGroupsResult = await ExternalUserGroup.findChildrenByParentIds(parentIds, includeGrandChildren);
|
|
|
+ const externalUserGroupsResult =
|
|
|
+ await ExternalUserGroup.findChildrenByParentIds(
|
|
|
+ parentIds,
|
|
|
+ includeGrandChildren,
|
|
|
+ );
|
|
|
return res.apiv3({
|
|
|
childUserGroups: externalUserGroupsResult.childUserGroups,
|
|
|
grandChildUserGroups: externalUserGroupsResult.grandChildUserGroups,
|
|
|
});
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = 'Error occurred in fetching child user group list';
|
|
|
logger.error(msg, err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -296,20 +340,25 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* userGroup:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.get('/:id', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired, validators.detail,
|
|
|
- async(req, res: ApiV3Response) => {
|
|
|
+ router.get(
|
|
|
+ '/:id',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ validators.detail,
|
|
|
+ async (req, res: ApiV3Response) => {
|
|
|
const { id } = req.params;
|
|
|
|
|
|
try {
|
|
|
const userGroup = await ExternalUserGroup.findById(id);
|
|
|
return res.apiv3({ userGroup });
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = 'Error occurred while getting external user group';
|
|
|
logger.error(msg, err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -356,35 +405,54 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* items:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.delete('/:id', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- validators.delete, apiV3FormValidator, addActivity,
|
|
|
- async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
+ router.delete(
|
|
|
+ '/:id',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ validators.delete,
|
|
|
+ apiV3FormValidator,
|
|
|
+ addActivity,
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const { id: deleteGroupId } = req.params;
|
|
|
const { transferToUserGroupId, transferToUserGroupType } = req.query;
|
|
|
const actionName = req.query.actionName as PageActionOnGroupDelete;
|
|
|
|
|
|
- const transferToUserGroup = typeof transferToUserGroupId === 'string'
|
|
|
- && (transferToUserGroupType === GroupType.userGroup || transferToUserGroupType === GroupType.externalUserGroup)
|
|
|
- ? {
|
|
|
- item: transferToUserGroupId,
|
|
|
- type: transferToUserGroupType,
|
|
|
- } : undefined;
|
|
|
+ const transferToUserGroup =
|
|
|
+ typeof transferToUserGroupId === 'string' &&
|
|
|
+ (transferToUserGroupType === GroupType.userGroup ||
|
|
|
+ transferToUserGroupType === GroupType.externalUserGroup)
|
|
|
+ ? {
|
|
|
+ item: transferToUserGroupId,
|
|
|
+ type: transferToUserGroupType,
|
|
|
+ }
|
|
|
+ : undefined;
|
|
|
|
|
|
try {
|
|
|
- const userGroups = await (crowi.userGroupService as UserGroupService)
|
|
|
- .removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferToUserGroup, ExternalUserGroup, ExternalUserGroupRelation);
|
|
|
+ const userGroups = await (
|
|
|
+ crowi.userGroupService as UserGroupService
|
|
|
+ ).removeCompletelyByRootGroupId(
|
|
|
+ deleteGroupId,
|
|
|
+ actionName,
|
|
|
+ req.user,
|
|
|
+ transferToUserGroup,
|
|
|
+ ExternalUserGroup,
|
|
|
+ ExternalUserGroupRelation,
|
|
|
+ );
|
|
|
|
|
|
- const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE };
|
|
|
+ const parameters = {
|
|
|
+ action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE,
|
|
|
+ };
|
|
|
activityEvent.emit('update', res.locals.activity._id, parameters);
|
|
|
|
|
|
return res.apiv3({ userGroups });
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = 'Error occurred while deleting user groups';
|
|
|
logger.error(msg, err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -422,28 +490,37 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* userGroup:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.put('/:id', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- validators.update, apiV3FormValidator, addActivity,
|
|
|
- async(req, res: ApiV3Response) => {
|
|
|
+ router.put(
|
|
|
+ '/:id',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ validators.update,
|
|
|
+ apiV3FormValidator,
|
|
|
+ addActivity,
|
|
|
+ async (req, res: ApiV3Response) => {
|
|
|
const { id } = req.params;
|
|
|
- const {
|
|
|
- description,
|
|
|
- } = req.body;
|
|
|
+ const { description } = req.body;
|
|
|
|
|
|
try {
|
|
|
- const userGroup = await ExternalUserGroup.findOneAndUpdate({ _id: id }, { $set: { description } });
|
|
|
+ const userGroup = await ExternalUserGroup.findOneAndUpdate(
|
|
|
+ { _id: id },
|
|
|
+ { $set: { description } },
|
|
|
+ );
|
|
|
|
|
|
- const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_UPDATE };
|
|
|
+ const parameters = {
|
|
|
+ action: SupportedAction.ACTION_ADMIN_USER_GROUP_UPDATE,
|
|
|
+ };
|
|
|
activityEvent.emit('update', res.locals.activity._id, parameters);
|
|
|
|
|
|
return res.apiv3({ userGroup });
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = 'Error occurred in updating an external user group';
|
|
|
logger.error(msg, err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -474,23 +551,33 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* items:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.get('/:id/external-user-group-relations', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- async(req: Request<{id: string}, Response, undefined>, res: ApiV3Response) => {
|
|
|
+ router.get(
|
|
|
+ '/:id/external-user-group-relations',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ async (
|
|
|
+ req: Request<{ id: string }, Response, undefined>,
|
|
|
+ res: ApiV3Response,
|
|
|
+ ) => {
|
|
|
const { id } = req.params;
|
|
|
|
|
|
try {
|
|
|
const externalUserGroup = await ExternalUserGroup.findById(id);
|
|
|
- const userGroupRelations = await ExternalUserGroupRelation.find({ relatedGroup: externalUserGroup })
|
|
|
- .populate('relatedUser');
|
|
|
- const serialized = userGroupRelations.map(relation => serializeUserGroupRelationSecurely(relation));
|
|
|
+ const userGroupRelations = await ExternalUserGroupRelation.find({
|
|
|
+ relatedGroup: externalUserGroup,
|
|
|
+ }).populate('relatedUser');
|
|
|
+ const serialized = userGroupRelations.map((relation) =>
|
|
|
+ serializeUserGroupRelationSecurely(relation),
|
|
|
+ );
|
|
|
return res.apiv3({ userGroupRelations: serialized });
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
const msg = `Error occurred in fetching user group relations for external user group: ${id}`;
|
|
|
logger.error(msg, err);
|
|
|
return res.apiv3Err(new ErrorV3(msg));
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -526,21 +613,42 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* ldapGroupDescriptionAttribute:
|
|
|
* type: string
|
|
|
*/
|
|
|
- router.get('/ldap/sync-settings', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
+ router.get(
|
|
|
+ '/ldap/sync-settings',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const settings = {
|
|
|
- ldapGroupSearchBase: configManager.getConfig('external-user-group:ldap:groupSearchBase'),
|
|
|
- ldapGroupMembershipAttribute: configManager.getConfig('external-user-group:ldap:groupMembershipAttribute'),
|
|
|
- ldapGroupMembershipAttributeType: configManager.getConfig('external-user-group:ldap:groupMembershipAttributeType'),
|
|
|
- ldapGroupChildGroupAttribute: configManager.getConfig('external-user-group:ldap:groupChildGroupAttribute'),
|
|
|
- autoGenerateUserOnLdapGroupSync: configManager.getConfig('external-user-group:ldap:autoGenerateUserOnGroupSync'),
|
|
|
- preserveDeletedLdapGroups: configManager.getConfig('external-user-group:ldap:preserveDeletedGroups'),
|
|
|
- ldapGroupNameAttribute: configManager.getConfig('external-user-group:ldap:groupNameAttribute'),
|
|
|
- ldapGroupDescriptionAttribute: configManager.getConfig('external-user-group:ldap:groupDescriptionAttribute'),
|
|
|
+ ldapGroupSearchBase: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:groupSearchBase',
|
|
|
+ ),
|
|
|
+ ldapGroupMembershipAttribute: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:groupMembershipAttribute',
|
|
|
+ ),
|
|
|
+ ldapGroupMembershipAttributeType: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:groupMembershipAttributeType',
|
|
|
+ ),
|
|
|
+ ldapGroupChildGroupAttribute: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:groupChildGroupAttribute',
|
|
|
+ ),
|
|
|
+ autoGenerateUserOnLdapGroupSync: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:autoGenerateUserOnGroupSync',
|
|
|
+ ),
|
|
|
+ preserveDeletedLdapGroups: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:preserveDeletedGroups',
|
|
|
+ ),
|
|
|
+ ldapGroupNameAttribute: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:groupNameAttribute',
|
|
|
+ ),
|
|
|
+ ldapGroupDescriptionAttribute: configManager.getConfig(
|
|
|
+ 'external-user-group:ldap:groupDescriptionAttribute',
|
|
|
+ ),
|
|
|
};
|
|
|
|
|
|
return res.apiv3(settings);
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -576,21 +684,42 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* keycloakGroupDescriptionAttribute:
|
|
|
* type: string
|
|
|
*/
|
|
|
- router.get('/keycloak/sync-settings', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
+ router.get(
|
|
|
+ '/keycloak/sync-settings',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const settings = {
|
|
|
- keycloakHost: configManager.getConfig('external-user-group:keycloak:host'),
|
|
|
- keycloakGroupRealm: configManager.getConfig('external-user-group:keycloak:groupRealm'),
|
|
|
- keycloakGroupSyncClientRealm: configManager.getConfig('external-user-group:keycloak:groupSyncClientRealm'),
|
|
|
- keycloakGroupSyncClientID: configManager.getConfig('external-user-group:keycloak:groupSyncClientID'),
|
|
|
- keycloakGroupSyncClientSecret: configManager.getConfig('external-user-group:keycloak:groupSyncClientSecret'),
|
|
|
- autoGenerateUserOnKeycloakGroupSync: configManager.getConfig('external-user-group:keycloak:autoGenerateUserOnGroupSync'),
|
|
|
- preserveDeletedKeycloakGroups: configManager.getConfig('external-user-group:keycloak:preserveDeletedGroups'),
|
|
|
- keycloakGroupDescriptionAttribute: configManager.getConfig('external-user-group:keycloak:groupDescriptionAttribute'),
|
|
|
+ keycloakHost: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:host',
|
|
|
+ ),
|
|
|
+ keycloakGroupRealm: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:groupRealm',
|
|
|
+ ),
|
|
|
+ keycloakGroupSyncClientRealm: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:groupSyncClientRealm',
|
|
|
+ ),
|
|
|
+ keycloakGroupSyncClientID: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:groupSyncClientID',
|
|
|
+ ),
|
|
|
+ keycloakGroupSyncClientSecret: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:groupSyncClientSecret',
|
|
|
+ ),
|
|
|
+ autoGenerateUserOnKeycloakGroupSync: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:autoGenerateUserOnGroupSync',
|
|
|
+ ),
|
|
|
+ preserveDeletedKeycloakGroups: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:preserveDeletedGroups',
|
|
|
+ ),
|
|
|
+ keycloakGroupDescriptionAttribute: configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:groupDescriptionAttribute',
|
|
|
+ ),
|
|
|
};
|
|
|
|
|
|
return res.apiv3(settings);
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -632,43 +761,66 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* schema:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.put('/ldap/sync-settings', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
+ router.put(
|
|
|
+ '/ldap/sync-settings',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
validators.ldapSyncSettings,
|
|
|
- async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const errors = validationResult(req);
|
|
|
if (!errors.isEmpty()) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('Invalid sync settings', 'external_user_group.invalid_sync_settings'), 400,
|
|
|
+ new ErrorV3(
|
|
|
+ 'Invalid sync settings',
|
|
|
+ 'external_user_group.invalid_sync_settings',
|
|
|
+ ),
|
|
|
+ 400,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const params = {
|
|
|
- 'external-user-group:ldap:groupSearchBase': req.body.ldapGroupSearchBase,
|
|
|
- 'external-user-group:ldap:groupMembershipAttribute': req.body.ldapGroupMembershipAttribute,
|
|
|
- 'external-user-group:ldap:groupMembershipAttributeType': req.body.ldapGroupMembershipAttributeType,
|
|
|
- 'external-user-group:ldap:groupChildGroupAttribute': req.body.ldapGroupChildGroupAttribute,
|
|
|
- 'external-user-group:ldap:autoGenerateUserOnGroupSync': req.body.autoGenerateUserOnLdapGroupSync,
|
|
|
- 'external-user-group:ldap:preserveDeletedGroups': req.body.preserveDeletedLdapGroups,
|
|
|
- 'external-user-group:ldap:groupNameAttribute': req.body.ldapGroupNameAttribute,
|
|
|
- 'external-user-group:ldap:groupDescriptionAttribute': req.body.ldapGroupDescriptionAttribute,
|
|
|
+ 'external-user-group:ldap:groupSearchBase':
|
|
|
+ req.body.ldapGroupSearchBase,
|
|
|
+ 'external-user-group:ldap:groupMembershipAttribute':
|
|
|
+ req.body.ldapGroupMembershipAttribute,
|
|
|
+ 'external-user-group:ldap:groupMembershipAttributeType':
|
|
|
+ req.body.ldapGroupMembershipAttributeType,
|
|
|
+ 'external-user-group:ldap:groupChildGroupAttribute':
|
|
|
+ req.body.ldapGroupChildGroupAttribute,
|
|
|
+ 'external-user-group:ldap:autoGenerateUserOnGroupSync':
|
|
|
+ req.body.autoGenerateUserOnLdapGroupSync,
|
|
|
+ 'external-user-group:ldap:preserveDeletedGroups':
|
|
|
+ req.body.preserveDeletedLdapGroups,
|
|
|
+ 'external-user-group:ldap:groupNameAttribute':
|
|
|
+ req.body.ldapGroupNameAttribute,
|
|
|
+ 'external-user-group:ldap:groupDescriptionAttribute':
|
|
|
+ req.body.ldapGroupDescriptionAttribute,
|
|
|
};
|
|
|
|
|
|
- if (params['external-user-group:ldap:groupNameAttribute'] == null || params['external-user-group:ldap:groupNameAttribute'] === '') {
|
|
|
- // default is cn
|
|
|
+ if (
|
|
|
+ params['external-user-group:ldap:groupNameAttribute'] == null ||
|
|
|
+ params['external-user-group:ldap:groupNameAttribute'] === ''
|
|
|
+ ) {
|
|
|
+ // default is cn
|
|
|
params['external-user-group:ldap:groupNameAttribute'] = 'cn';
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
await configManager.updateConfigs(params, { skipPubsub: true });
|
|
|
return res.apiv3({}, 204);
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('Sync settings update failed', 'external_user_group.update_sync_settings_failed'), 500,
|
|
|
+ new ErrorV3(
|
|
|
+ 'Sync settings update failed',
|
|
|
+ 'external_user_group.update_sync_settings_failed',
|
|
|
+ ),
|
|
|
+ 500,
|
|
|
);
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -710,38 +862,56 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* schema:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.put('/keycloak/sync-settings', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
+ router.put(
|
|
|
+ '/keycloak/sync-settings',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
validators.keycloakSyncSettings,
|
|
|
- async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const errors = validationResult(req);
|
|
|
if (!errors.isEmpty()) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('Invalid sync settings', 'external_user_group.invalid_sync_settings'), 400,
|
|
|
+ new ErrorV3(
|
|
|
+ 'Invalid sync settings',
|
|
|
+ 'external_user_group.invalid_sync_settings',
|
|
|
+ ),
|
|
|
+ 400,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const params = {
|
|
|
'external-user-group:keycloak:host': req.body.keycloakHost,
|
|
|
'external-user-group:keycloak:groupRealm': req.body.keycloakGroupRealm,
|
|
|
- 'external-user-group:keycloak:groupSyncClientRealm': req.body.keycloakGroupSyncClientRealm,
|
|
|
- 'external-user-group:keycloak:groupSyncClientID': req.body.keycloakGroupSyncClientID,
|
|
|
- 'external-user-group:keycloak:groupSyncClientSecret': req.body.keycloakGroupSyncClientSecret,
|
|
|
- 'external-user-group:keycloak:autoGenerateUserOnGroupSync': req.body.autoGenerateUserOnKeycloakGroupSync,
|
|
|
- 'external-user-group:keycloak:preserveDeletedGroups': req.body.preserveDeletedKeycloakGroups,
|
|
|
- 'external-user-group:keycloak:groupDescriptionAttribute': req.body.keycloakGroupDescriptionAttribute,
|
|
|
+ 'external-user-group:keycloak:groupSyncClientRealm':
|
|
|
+ req.body.keycloakGroupSyncClientRealm,
|
|
|
+ 'external-user-group:keycloak:groupSyncClientID':
|
|
|
+ req.body.keycloakGroupSyncClientID,
|
|
|
+ 'external-user-group:keycloak:groupSyncClientSecret':
|
|
|
+ req.body.keycloakGroupSyncClientSecret,
|
|
|
+ 'external-user-group:keycloak:autoGenerateUserOnGroupSync':
|
|
|
+ req.body.autoGenerateUserOnKeycloakGroupSync,
|
|
|
+ 'external-user-group:keycloak:preserveDeletedGroups':
|
|
|
+ req.body.preserveDeletedKeycloakGroups,
|
|
|
+ 'external-user-group:keycloak:groupDescriptionAttribute':
|
|
|
+ req.body.keycloakGroupDescriptionAttribute,
|
|
|
};
|
|
|
|
|
|
try {
|
|
|
await configManager.updateConfigs(params, { skipPubsub: true });
|
|
|
return res.apiv3({}, 204);
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('Sync settings update failed', 'external_user_group.update_sync_settings_failed'), 500,
|
|
|
+ new ErrorV3(
|
|
|
+ 'Sync settings update failed',
|
|
|
+ 'external_user_group.update_sync_settings_failed',
|
|
|
+ ),
|
|
|
+ 500,
|
|
|
);
|
|
|
}
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -760,27 +930,47 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* schema:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.put('/ldap/sync', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
+ router.put(
|
|
|
+ '/ldap/sync',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
if (isExecutingSync()) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('There is an ongoing sync process', 'external_user_group.sync_being_executed'), 409,
|
|
|
+ new ErrorV3(
|
|
|
+ 'There is an ongoing sync process',
|
|
|
+ 'external_user_group.sync_being_executed',
|
|
|
+ ),
|
|
|
+ 409,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- const isLdapEnabled = await configManager.getConfig('security:passport-ldap:isEnabled');
|
|
|
+ const isLdapEnabled = await configManager.getConfig(
|
|
|
+ 'security:passport-ldap:isEnabled',
|
|
|
+ );
|
|
|
if (!isLdapEnabled) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('Authentication using ldap is not set', 'external_user_group.ldap.auth_not_set'), 422,
|
|
|
+ new ErrorV3(
|
|
|
+ 'Authentication using ldap is not set',
|
|
|
+ 'external_user_group.ldap.auth_not_set',
|
|
|
+ ),
|
|
|
+ 422,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- await crowi.ldapUserGroupSyncService?.init(req.user.name, req.body.password);
|
|
|
- }
|
|
|
- catch (e) {
|
|
|
+ await crowi.ldapUserGroupSyncService?.init(
|
|
|
+ req.user.name,
|
|
|
+ req.body.password,
|
|
|
+ );
|
|
|
+ } catch (e) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('LDAP group sync failed', 'external_user_group.sync_failed'), 500,
|
|
|
+ new ErrorV3(
|
|
|
+ 'LDAP group sync failed',
|
|
|
+ 'external_user_group.sync_failed',
|
|
|
+ ),
|
|
|
+ 500,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -788,7 +978,8 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
crowi.ldapUserGroupSyncService?.syncExternalUserGroups();
|
|
|
|
|
|
return res.apiv3({}, 202);
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -807,34 +998,64 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* schema:
|
|
|
* type: object
|
|
|
*/
|
|
|
- router.put('/keycloak/sync', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
- async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
+ router.put(
|
|
|
+ '/keycloak/sync',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
if (isExecutingSync()) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('There is an ongoing sync process', 'external_user_group.sync_being_executed'), 409,
|
|
|
+ new ErrorV3(
|
|
|
+ 'There is an ongoing sync process',
|
|
|
+ 'external_user_group.sync_being_executed',
|
|
|
+ ),
|
|
|
+ 409,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const getAuthProviderType = () => {
|
|
|
- let kcHost = configManager.getConfig('external-user-group:keycloak:host');
|
|
|
+ let kcHost = configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:host',
|
|
|
+ );
|
|
|
if (kcHost?.endsWith('/')) {
|
|
|
kcHost = kcHost.slice(0, -1);
|
|
|
}
|
|
|
- const kcGroupRealm = configManager.getConfig('external-user-group:keycloak:groupRealm');
|
|
|
+ const kcGroupRealm = configManager.getConfig(
|
|
|
+ 'external-user-group:keycloak:groupRealm',
|
|
|
+ );
|
|
|
|
|
|
// starts with kcHost, contains kcGroupRealm in path
|
|
|
// see: https://regex101.com/r/3ihDmf/1
|
|
|
const regex = new RegExp(`^${kcHost}/.*/${kcGroupRealm}(/|$).*`);
|
|
|
|
|
|
- const isOidcEnabled = configManager.getConfig('security:passport-oidc:isEnabled');
|
|
|
- const oidcIssuerHost = configManager.getConfig('security:passport-oidc:issuerHost');
|
|
|
+ const isOidcEnabled = configManager.getConfig(
|
|
|
+ 'security:passport-oidc:isEnabled',
|
|
|
+ );
|
|
|
+ const oidcIssuerHost = configManager.getConfig(
|
|
|
+ 'security:passport-oidc:issuerHost',
|
|
|
+ );
|
|
|
|
|
|
- if (isOidcEnabled && oidcIssuerHost != null && regex.test(oidcIssuerHost)) return 'oidc';
|
|
|
+ if (
|
|
|
+ isOidcEnabled &&
|
|
|
+ oidcIssuerHost != null &&
|
|
|
+ regex.test(oidcIssuerHost)
|
|
|
+ )
|
|
|
+ return 'oidc';
|
|
|
|
|
|
- const isSamlEnabled = configManager.getConfig('security:passport-saml:isEnabled');
|
|
|
- const samlEntryPoint = configManager.getConfig('security:passport-saml:entryPoint');
|
|
|
+ const isSamlEnabled = configManager.getConfig(
|
|
|
+ 'security:passport-saml:isEnabled',
|
|
|
+ );
|
|
|
+ const samlEntryPoint = configManager.getConfig(
|
|
|
+ 'security:passport-saml:entryPoint',
|
|
|
+ );
|
|
|
|
|
|
- if (isSamlEnabled && samlEntryPoint != null && regex.test(samlEntryPoint)) return 'saml';
|
|
|
+ if (
|
|
|
+ isSamlEnabled &&
|
|
|
+ samlEntryPoint != null &&
|
|
|
+ regex.test(samlEntryPoint)
|
|
|
+ )
|
|
|
+ return 'saml';
|
|
|
|
|
|
return null;
|
|
|
};
|
|
|
@@ -842,7 +1063,11 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
const authProviderType = getAuthProviderType();
|
|
|
if (authProviderType == null) {
|
|
|
return res.apiv3Err(
|
|
|
- new ErrorV3('Authentication using keycloak is not set', 'external_user_group.keycloak.auth_not_set'), 422,
|
|
|
+ new ErrorV3(
|
|
|
+ 'Authentication using keycloak is not set',
|
|
|
+ 'external_user_group.keycloak.auth_not_set',
|
|
|
+ ),
|
|
|
+ 422,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -851,7 +1076,8 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
crowi.keycloakUserGroupSyncService?.syncExternalUserGroups();
|
|
|
|
|
|
return res.apiv3({}, 202);
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -870,11 +1096,16 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* schema:
|
|
|
* $ref: '#/components/schemas/SyncStatus'
|
|
|
*/
|
|
|
- router.get('/ldap/sync-status', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
+ router.get(
|
|
|
+ '/ldap/sync-status',
|
|
|
+ accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const syncStatus = crowi.ldapUserGroupSyncService?.syncStatus;
|
|
|
return res.apiv3({ ...syncStatus });
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
/**
|
|
|
* @swagger
|
|
|
@@ -893,12 +1124,16 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* schema:
|
|
|
* $ref: '#/components/schemas/SyncStatus'
|
|
|
*/
|
|
|
- router.get('/keycloak/sync-status', accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
|
|
|
+ router.get(
|
|
|
+ '/keycloak/sync-status',
|
|
|
+ accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
|
|
|
+ loginRequiredStrictly,
|
|
|
+ adminRequired,
|
|
|
(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const syncStatus = crowi.keycloakUserGroupSyncService?.syncStatus;
|
|
|
return res.apiv3({ ...syncStatus });
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
return router;
|
|
|
-
|
|
|
};
|