diff --git a/backend/src/ee/routes/v1/scim-router.ts b/backend/src/ee/routes/v1/scim-router.ts index 88a1df457..e3456c70a 100644 --- a/backend/src/ee/routes/v1/scim-router.ts +++ b/backend/src/ee/routes/v1/scim-router.ts @@ -350,7 +350,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { schemas: z.array(z.string()), id: z.string().trim(), displayName: z.string().trim(), - members: z.array(z.any()).length(0), + members: z.array( + z.object({ + value: z.string(), + display: z.string() + }) + ), meta: z.object({ resourceType: z.string().trim() }) @@ -423,7 +428,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { displayName: z.string().trim(), members: z.array( z.object({ - value: z.string(), // infisical orgMembershipId + value: z.string(), display: z.string() }) ) diff --git a/backend/src/ee/services/group/user-group-membership-dal.ts b/backend/src/ee/services/group/user-group-membership-dal.ts index 64983d24f..cadbbdc36 100644 --- a/backend/src/ee/services/group/user-group-membership-dal.ts +++ b/backend/src/ee/services/group/user-group-membership-dal.ts @@ -162,17 +162,50 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => { } }; - const findUserGroupMembershipsInOrg = async (userId: string, orgId: string) => { + const findGroupMembershipsByUserIdInOrg = async (userId: string, orgId: string) => { try { const docs = await db .replicaNode()(TableName.UserGroupMembership) .join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`) + .join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`) + .join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`) .where(`${TableName.UserGroupMembership}.userId`, userId) - .where(`${TableName.Groups}.orgId`, orgId); + .where(`${TableName.Groups}.orgId`, orgId) + .select( + db.ref("id").withSchema(TableName.UserGroupMembership), + db.ref("groupId").withSchema(TableName.UserGroupMembership), + db.ref("name").withSchema(TableName.Groups).as("groupName"), + db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"), + db.ref("firstName").withSchema(TableName.Users).as("firstName"), + db.ref("lastName").withSchema(TableName.Users).as("lastName") + ); return docs; } catch (error) { - throw new DatabaseError({ error, name: "findTest" }); + throw new DatabaseError({ error, name: "Find group memberships by user id in org" }); + } + }; + + const findGroupMembershipsByGroupIdInOrg = async (groupId: string, orgId: string) => { + try { + const docs = await db + .replicaNode()(TableName.UserGroupMembership) + .join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`) + .join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`) + .join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`) + .where(`${TableName.Groups}.id`, groupId) + .where(`${TableName.Groups}.orgId`, orgId) + .select( + db.ref("id").withSchema(TableName.UserGroupMembership), + db.ref("groupId").withSchema(TableName.UserGroupMembership), + db.ref("name").withSchema(TableName.Groups).as("groupName"), + db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"), + db.ref("firstName").withSchema(TableName.Users).as("firstName"), + db.ref("lastName").withSchema(TableName.Users).as("lastName") + ); + return docs; + } catch (error) { + throw new DatabaseError({ error, name: "Find group memberships by group id in org" }); } }; @@ -182,6 +215,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => { findUserGroupMembershipsInProject, findGroupMembersNotInProject, deletePendingUserGroupMembershipsByUserIds, - findUserGroupMembershipsInOrg + findGroupMembershipsByUserIdInOrg, + findGroupMembershipsByGroupIdInOrg }; }; diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 2f39e9168..5787924dd 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -9,6 +9,7 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou import { TScimDALFactory } from "@app/ee/services/scim/scim-dal"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TOrgPermission } from "@app/lib/types"; import { AuthTokenType } from "@app/services/auth/auth-type"; @@ -51,6 +52,7 @@ import { TListScimUsers, TListScimUsersDTO, TReplaceScimUserDTO, + TScimGroup, TScimTokenJwtPayload, TUpdateScimGroupNamePatchDTO, TUpdateScimGroupNamePutDTO, @@ -83,7 +85,8 @@ type TScimServiceFactoryDep = { | "insertMany" | "filterProjectsByUserMembership" | "delete" - | "findUserGroupMembershipsInOrg" + | "findGroupMembershipsByUserIdInOrg" + | "findGroupMembershipsByGroupIdInOrg" >; projectKeyDAL: Pick; projectBotDAL: Pick; @@ -252,7 +255,10 @@ export const scimServiceFactory = ({ status: 403 }); - const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId); + const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg( + membership.userId, + orgId + ); return buildScimUser({ orgMembershipId: membership.id, @@ -263,7 +269,7 @@ export const scimServiceFactory = ({ active: membership.isActive, groups: groupMembershipsInOrg.map((group) => ({ value: group.groupId, - display: group.name + display: group.groupName })) }); }; @@ -509,7 +515,10 @@ export const scimServiceFactory = ({ isActive: active }); - const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId); + const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg( + membership.userId, + orgId + ); return buildScimUser({ orgMembershipId: membership.id, @@ -520,7 +529,7 @@ export const scimServiceFactory = ({ active, groups: groupMembershipsInOrg.map((group) => ({ value: group.groupId, - display: group.name + display: group.groupName })) }); }; @@ -589,13 +598,20 @@ export const scimServiceFactory = ({ } ); - const scimGroups = groups.map((group) => - buildScimGroup({ + const scimGroups: TScimGroup[] = []; + + for await (const group of groups) { + const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId); + const scimGroup = buildScimGroup({ groupId: group.id, name: group.name, - members: [] // does this need to be populated? - }) - ); + members: members.map((member) => ({ + value: member.orgMembershipId, + display: `${member.firstName ?? ""} ${member.lastName ?? ""}` + })) + }); + scimGroups.push(scimGroup); + } return buildScimGroupList({ scimGroups, @@ -872,23 +888,27 @@ export const scimServiceFactory = ({ break; } case "add": { - const orgMemberships = await orgMembershipDAL.find({ - $in: { - id: operation.value.map((member) => member.value) - } - }); + try { + const orgMemberships = await orgMembershipDAL.find({ + $in: { + id: operation.value.map((member) => member.value) + } + }); - await addUsersToGroupByUserIds({ - group, - userIds: orgMemberships.map((membership) => membership.userId as string), - userDAL, - userGroupMembershipDAL, - orgDAL, - groupProjectDAL, - projectKeyDAL, - projectDAL, - projectBotDAL - }); + await addUsersToGroupByUserIds({ + group, + userIds: orgMemberships.map((membership) => membership.userId as string), + userDAL, + userGroupMembershipDAL, + orgDAL, + groupProjectDAL, + projectKeyDAL, + projectDAL, + projectBotDAL + }); + } catch { + logger.info("Repeat SCIM user-group add operation"); + } break; } @@ -916,10 +936,15 @@ export const scimServiceFactory = ({ } } + const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId); + return buildScimGroup({ groupId: group.id, name: group.name, - members: [] + members: members.map((member) => ({ + value: member.orgMembershipId, + display: `${member.firstName ?? ""} ${member.lastName ?? ""}` + })) }); };