From 8f48a64fd658bd4897de28c9bf7126d1b59d4d88 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 1 Sep 2024 18:55:17 +0530 Subject: [PATCH] feat: finished fixing scim group --- backend/src/ee/routes/v1/scim-router.ts | 557 +++++++++---------- backend/src/ee/services/scim/scim-fns.ts | 11 +- backend/src/ee/services/scim/scim-service.ts | 269 +++++---- backend/src/ee/services/scim/scim-types.ts | 32 +- backend/src/lib/knex/scim.ts | 1 - 5 files changed, 394 insertions(+), 476 deletions(-) diff --git a/backend/src/ee/routes/v1/scim-router.ts b/backend/src/ee/routes/v1/scim-router.ts index 2c6d2e620..c4e03094c 100644 --- a/backend/src/ee/routes/v1/scim-router.ts +++ b/backend/src/ee/routes/v1/scim-router.ts @@ -28,6 +28,23 @@ const ScimUserSchema = z.object({ active: z.boolean() }); +const ScimGroupSchema = z.object({ + schemas: z.array(z.string()), + id: z.string().trim(), + displayName: z.string().trim(), + members: z + .array( + z.object({ + value: z.string(), + display: z.string().optional() + }) + ) + .optional(), + meta: z.object({ + resourceType: z.string().trim() + }) +}); + export const registerScimRouter = async (server: FastifyZodProvider) => { server.route({ url: "/scim-tokens", @@ -249,304 +266,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { } }); - server.route({ - url: "/Users/:orgMembershipId", - method: "PATCH", - schema: { - params: z.object({ - orgMembershipId: z.string().trim() - }), - body: z.object({ - schemas: z.array(z.string()), - Operations: z.array( - z.union([ - z.object({ - op: z.union([z.literal("remove"), z.literal("Remove")]), - path: z.string().trim() - }), - z.object({ - op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]), - path: z.string().trim().optional(), - value: z.any() - }) - ]) - ) - }), - response: { - 200: ScimUserSchema - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const user = await req.server.services.scim.updateScimUser({ - orgMembershipId: req.params.orgMembershipId, - orgId: req.permission.orgId, - operations: req.body.Operations - }); - - return user; - } - }); - server.route({ - url: "/Groups", - method: "POST", - schema: { - body: z.object({ - schemas: z.array(z.string()), - displayName: z.string().trim(), - members: z - .array( - z.object({ - value: z.string(), - display: z.string() - }) - ) - .optional() // okta-specific - }), - response: { - 200: z.object({ - schemas: z.array(z.string()), - id: z.string().trim(), - displayName: z.string().trim(), - members: z - .array( - z.object({ - value: z.string(), - display: z.string() - }) - ) - .optional(), - meta: z.object({ - resourceType: z.string().trim() - }) - }) - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const group = await req.server.services.scim.createScimGroup({ - orgId: req.permission.orgId, - ...req.body - }); - - return group; - } - }); - - server.route({ - url: "/Groups", - method: "GET", - schema: { - querystring: z.object({ - startIndex: z.coerce.number().default(1), - count: z.coerce.number().default(20), - filter: z.string().trim().optional() - }), - response: { - 200: z.object({ - Resources: z.array( - z.object({ - schemas: z.array(z.string()), - id: z.string().trim(), - displayName: z.string().trim(), - members: z.array( - z.object({ - value: z.string(), - display: z.string() - }) - ), - meta: z.object({ - resourceType: z.string().trim() - }) - }) - ), - itemsPerPage: z.number(), - schemas: z.array(z.string()), - startIndex: z.number(), - totalResults: z.number() - }) - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const groups = await req.server.services.scim.listScimGroups({ - orgId: req.permission.orgId, - startIndex: req.query.startIndex, - filter: req.query.filter, - limit: req.query.count - }); - - return groups; - } - }); - - server.route({ - url: "/Groups/:groupId", - method: "GET", - schema: { - params: z.object({ - groupId: z.string().trim() - }), - response: { - 200: z.object({ - schemas: z.array(z.string()), - id: z.string().trim(), - displayName: z.string().trim(), - members: z.array( - z.object({ - value: z.string(), - display: z.string() - }) - ), - meta: z.object({ - resourceType: z.string().trim() - }) - }) - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const group = await req.server.services.scim.getScimGroup({ - groupId: req.params.groupId, - orgId: req.permission.orgId - }); - return group; - } - }); - - server.route({ - url: "/Groups/:groupId", - method: "PUT", - schema: { - params: z.object({ - groupId: z.string().trim() - }), - body: z.object({ - schemas: z.array(z.string()), - id: z.string().trim(), - displayName: z.string().trim(), - members: z.array( - z.object({ - value: z.string(), - display: z.string() - }) - ) - }), - response: { - 200: z.object({ - schemas: z.array(z.string()), - id: z.string().trim(), - displayName: z.string().trim(), - members: z.array( - z.object({ - value: z.string(), - display: z.string() - }) - ), - meta: z.object({ - resourceType: z.string().trim() - }) - }) - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const group = await req.server.services.scim.updateScimGroupNamePut({ - groupId: req.params.groupId, - orgId: req.permission.orgId, - ...req.body - }); - - return group; - } - }); - - server.route({ - url: "/Groups/:groupId", - method: "PATCH", - schema: { - params: z.object({ - groupId: z.string().trim() - }), - body: z.object({ - schemas: z.array(z.string()), - Operations: z.array( - z.union([ - z.object({ - op: z.union([z.literal("replace"), z.literal("Replace")]), - value: z.object({ - id: z.string().trim(), - displayName: z.string().trim() - }) - }), - z.object({ - op: z.union([z.literal("remove"), z.literal("Remove")]), - path: z.string().trim() - }), - z.object({ - op: z.union([z.literal("add"), z.literal("Add")]), - path: z.string().trim(), - value: z.array( - z.object({ - value: z.string().trim(), - display: z.string().trim().optional() - }) - ) - }) - ]) - ) - }), - response: { - 200: z.object({ - schemas: z.array(z.string()), - id: z.string().trim(), - displayName: z.string().trim(), - members: z.array( - z.object({ - value: z.string(), - display: z.string() - }) - ), - meta: z.object({ - resourceType: z.string().trim() - }) - }) - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const group = await req.server.services.scim.updateScimGroupNamePatch({ - groupId: req.params.groupId, - orgId: req.permission.orgId, - operations: req.body.Operations - }); - - return group; - } - }); - - server.route({ - url: "/Groups/:groupId", - method: "DELETE", - schema: { - params: z.object({ - groupId: z.string().trim() - }), - response: { - 200: z.object({}) - } - }, - onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), - handler: async (req) => { - const group = await req.server.services.scim.deleteScimGroup({ - groupId: req.params.groupId, - orgId: req.permission.orgId - }); - - return group; - } - }); - server.route({ url: "/Users/:orgMembershipId", method: "PUT", @@ -558,10 +277,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { schemas: z.array(z.string()), id: z.string().trim(), userName: z.string().trim(), - name: z.object({ - familyName: z.string().trim(), - givenName: z.string().trim() - }), + name: z + .object({ + familyName: z.string().trim().optional(), + givenName: z.string().trim().optional() + }) + .optional(), displayName: z.string().trim(), active: z.boolean() }), @@ -602,4 +323,236 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { return user; } }); + + server.route({ + url: "/Users/:orgMembershipId", + method: "PATCH", + schema: { + params: z.object({ + orgMembershipId: z.string().trim() + }), + body: z.object({ + schemas: z.array(z.string()), + Operations: z.array( + z.union([ + z.object({ + op: z.union([z.literal("remove"), z.literal("Remove")]), + path: z.string().trim(), + value: z + .object({ + value: z.string() + }) + .array() + .optional() + }), + z.object({ + op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]), + path: z.string().trim().optional(), + value: z.any().optional() + }) + ]) + ) + }), + response: { + 200: ScimUserSchema + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + const user = await req.server.services.scim.updateScimUser({ + orgMembershipId: req.params.orgMembershipId, + orgId: req.permission.orgId, + operations: req.body.Operations + }); + + return user; + } + }); + server.route({ + url: "/Groups", + method: "POST", + schema: { + body: z.object({ + schemas: z.array(z.string()), + displayName: z.string().trim(), + members: z + .array( + z.object({ + value: z.string(), + display: z.string() + }) + ) + .optional() + }), + response: { + 200: ScimGroupSchema + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + const group = await req.server.services.scim.createScimGroup({ + orgId: req.permission.orgId, + ...req.body + }); + + return group; + } + }); + + server.route({ + url: "/Groups", + method: "GET", + schema: { + querystring: z.object({ + startIndex: z.coerce.number().default(1), + count: z.coerce.number().default(20), + filter: z.string().trim().optional(), + excludedAttributes: z.string().trim().optional() + }), + response: { + 200: z.object({ + Resources: z.array(ScimGroupSchema), + itemsPerPage: z.number(), + schemas: z.array(z.string()), + startIndex: z.number(), + totalResults: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + const groups = await req.server.services.scim.listScimGroups({ + orgId: req.permission.orgId, + startIndex: req.query.startIndex, + filter: req.query.filter, + limit: req.query.count, + isMembersExcluded: req.query.excludedAttributes === "members" + }); + + return groups; + } + }); + + server.route({ + url: "/Groups/:groupId", + method: "GET", + schema: { + params: z.object({ + groupId: z.string().trim() + }), + response: { + 200: ScimGroupSchema + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + const group = await req.server.services.scim.getScimGroup({ + groupId: req.params.groupId, + orgId: req.permission.orgId + }); + return group; + } + }); + + server.route({ + url: "/Groups/:groupId", + method: "PUT", + schema: { + params: z.object({ + groupId: z.string().trim() + }), + body: z.object({ + schemas: z.array(z.string()), + id: z.string().trim(), + displayName: z.string().trim(), + members: z.array( + z.object({ + value: z.string(), + display: z.string() + }) + ) + }), + response: { + 200: ScimGroupSchema + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + const group = await req.server.services.scim.replaceScimGroup({ + groupId: req.params.groupId, + orgId: req.permission.orgId, + ...req.body + }); + + return group; + } + }); + + server.route({ + url: "/Groups/:groupId", + method: "PATCH", + schema: { + params: z.object({ + groupId: z.string().trim() + }), + body: z.object({ + schemas: z.array(z.string()), + Operations: z.array( + z.union([ + z.object({ + op: z.union([z.literal("remove"), z.literal("Remove")]), + path: z.string().trim(), + value: z + .object({ + value: z.string() + }) + .array() + .optional() + }), + z.object({ + op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]), + path: z.string().trim().optional(), + value: z.any() + }) + ]) + ) + }), + response: { + 200: ScimGroupSchema + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + console.log(JSON.stringify(req.body, null, 4)); + const group = await req.server.services.scim.updateScimGroup({ + groupId: req.params.groupId, + orgId: req.permission.orgId, + operations: req.body.Operations + }); + console.log(group); + return group; + } + }); + + server.route({ + url: "/Groups/:groupId", + method: "DELETE", + schema: { + params: z.object({ + groupId: z.string().trim() + }), + response: { + 200: z.object({}) + } + }, + onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), + handler: async (req) => { + const group = await req.server.services.scim.deleteScimGroup({ + groupId: req.params.groupId, + orgId: req.permission.orgId + }); + + return group; + } + }); }; diff --git a/backend/src/ee/services/scim/scim-fns.ts b/backend/src/ee/services/scim/scim-fns.ts index ae03cace8..8de22ac54 100644 --- a/backend/src/ee/services/scim/scim-fns.ts +++ b/backend/src/ee/services/scim/scim-fns.ts @@ -114,14 +114,18 @@ export const buildScimGroupList = ({ export const buildScimGroup = ({ groupId, name, - members + members, + updatedAt, + createdAt }: { groupId: string; name: string; members: { value: string; - display: string; + display?: string; }[]; + createdAt: Date; + updatedAt: Date; }): TScimGroup => { const scimGroup = { schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"], @@ -130,7 +134,8 @@ export const buildScimGroup = ({ members, meta: { resourceType: "Group", - location: null + created: createdAt, + lastModified: updatedAt } }; diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 2df75450a..956919cf1 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -10,7 +10,6 @@ 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"; @@ -33,14 +32,7 @@ import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; -import { - buildScimGroup, - buildScimGroupList, - buildScimUser, - buildScimUserList, - extractScimValueFromPath, - parseScimFilter -} from "./scim-fns"; +import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns"; import { TCreateScimGroupDTO, TCreateScimTokenDTO, @@ -612,7 +604,7 @@ export const scimServiceFactory = ({ return {}; // intentionally return empty object upon success }; - const listScimGroups = async ({ orgId, startIndex, limit, filter }: TListScimGroupsDTO) => { + const listScimGroups = async ({ orgId, startIndex, limit, filter, isMembersExcluded }: TListScimGroupsDTO) => { const plan = await licenseService.getPlan(orgId); if (!plan.groups) throw new BadRequestError({ @@ -645,6 +637,21 @@ export const scimServiceFactory = ({ ); const scimGroups: TScimGroup[] = []; + if (isMembersExcluded) { + return buildScimGroupList({ + scimGroups: groups.map((group) => + buildScimGroup({ + groupId: group.id, + name: group.name, + members: [], + createdAt: group.createdAt, + updatedAt: group.updatedAt + }) + ), + startIndex, + limit + }); + } for await (const group of groups) { const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId); @@ -654,7 +661,9 @@ export const scimServiceFactory = ({ members: members.map((member) => ({ value: member.orgMembershipId, display: `${member.firstName ?? ""} ${member.lastName ?? ""}` - })) + })), + createdAt: group.createdAt, + updatedAt: group.updatedAt }); scimGroups.push(scimGroup); } @@ -738,7 +747,9 @@ export const scimServiceFactory = ({ members: orgMemberships.map(({ id, firstName, lastName }) => ({ value: id, display: `${firstName} ${lastName}` - })) + })), + createdAt: newGroup.group.createdAt, + updatedAt: newGroup.group.updatedAt }); }; @@ -781,31 +792,17 @@ export const scimServiceFactory = ({ members: orgMemberships.map(({ id, firstName, lastName }) => ({ value: id, display: `${firstName} ${lastName}` - })) + })), + createdAt: group.createdAt, + updatedAt: group.updatedAt }); }; - const updateScimGroupNamePut = async ({ groupId, orgId, displayName, members }: TUpdateScimGroupNamePutDTO) => { - const plan = await licenseService.getPlan(orgId); - if (!plan.groups) - throw new BadRequestError({ - message: "Failed to update SCIM group due to plan restriction. Upgrade plan to update SCIM group." - }); - - const org = await orgDAL.findById(orgId); - if (!org) { - throw new ScimRequestError({ - detail: "Organization Not Found", - status: 404 - }); - } - - if (!org.scimEnabled) - throw new ScimRequestError({ - detail: "SCIM is disabled for the organization", - status: 403 - }); - + const $replaceGroupDAL = async ( + groupId: string, + orgId: string, + { displayName, members = [] }: { displayName: string; members: { value: string }[] } + ) => { const updatedGroup = await groupDAL.transaction(async (tx) => { const [group] = await groupDAL.update( { @@ -824,74 +821,96 @@ export const scimServiceFactory = ({ }); } - if (members) { - const orgMemberships = await orgMembershipDAL.find({ - $in: { - id: members.map((member) => member.value) - } + const orgMemberships = members.length + ? await orgMembershipDAL.find({ + $in: { + id: members.map((member) => member.value) + } + }) + : []; + + const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId)); + const userGroupMembers = await userGroupMembershipDAL.find({ + groupId: group.id + }); + const directMemberUserIds = userGroupMembers.filter((el) => !el.isPending).map((membership) => membership.userId); + + const pendingGroupAdditionsUserIds = userGroupMembers + .filter((el) => el.isPending) + .map((pendingGroupAddition) => pendingGroupAddition.userId); + + const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds); + const allMembersUserIdsSet = new Set(allMembersUserIds); + + const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string)); + const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId)); + + if (toAddUserIds.length) { + await addUsersToGroupByUserIds({ + group, + userIds: toAddUserIds.map((member) => member.userId as string), + userDAL, + userGroupMembershipDAL, + orgDAL, + groupProjectDAL, + projectKeyDAL, + projectDAL, + projectBotDAL, + tx }); + } - const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId)); - - const directMemberUserIds = ( - await userGroupMembershipDAL.find({ - groupId: group.id, - isPending: false - }) - ).map((membership) => membership.userId); - - const pendingGroupAdditionsUserIds = ( - await userGroupMembershipDAL.find({ - groupId: group.id, - isPending: true - }) - ).map((pendingGroupAddition) => pendingGroupAddition.userId); - - const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds); - const allMembersUserIdsSet = new Set(allMembersUserIds); - - const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string)); - const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId)); - - if (toAddUserIds.length) { - await addUsersToGroupByUserIds({ - group, - userIds: toAddUserIds.map((member) => member.userId as string), - userDAL, - userGroupMembershipDAL, - orgDAL, - groupProjectDAL, - projectKeyDAL, - projectDAL, - projectBotDAL, - tx - }); - } - - if (toRemoveUserIds.length) { - await removeUsersFromGroupByUserIds({ - group, - userIds: toRemoveUserIds, - userDAL, - userGroupMembershipDAL, - groupProjectDAL, - projectKeyDAL, - tx - }); - } + if (toRemoveUserIds.length) { + await removeUsersFromGroupByUserIds({ + group, + userIds: toRemoveUserIds, + userDAL, + userGroupMembershipDAL, + groupProjectDAL, + projectKeyDAL, + tx + }); } return group; }); + return updatedGroup; + }; + + const replaceScimGroup = async ({ groupId, orgId, displayName, members }: TUpdateScimGroupNamePutDTO) => { + const plan = await licenseService.getPlan(orgId); + if (!plan.groups) + throw new BadRequestError({ + message: "Failed to update SCIM group due to plan restriction. Upgrade plan to update SCIM group." + }); + + const org = await orgDAL.findById(orgId); + if (!org) { + throw new ScimRequestError({ + detail: "Organization Not Found", + status: 404 + }); + } + + if (!org.scimEnabled) + throw new ScimRequestError({ + detail: "SCIM is disabled for the organization", + status: 403 + }); + + const updatedGroup = await $replaceGroupDAL(groupId, orgId, { displayName, members }); + return buildScimGroup({ groupId: updatedGroup.id, name: updatedGroup.name, - members + members, + updatedAt: updatedGroup.updatedAt, + createdAt: updatedGroup.createdAt }); }; - const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => { + const updateScimGroup = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => { const plan = await licenseService.getPlan(orgId); if (!plan.groups) throw new BadRequestError({ @@ -913,7 +932,7 @@ export const scimServiceFactory = ({ status: 403 }); - let group = await groupDAL.findOne({ + const group = await groupDAL.findOne({ id: groupId, orgId }); @@ -925,64 +944,28 @@ export const scimServiceFactory = ({ }); } - for await (const operation of operations) { - if (operation.op === "replace" || operation.op === "Replace") { - group = await groupDAL.updateById(group.id, { - name: operation.value.displayName - }); - } else if (operation.op === "add" || operation.op === "Add") { - 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 - }); - } catch { - logger.info("Repeat SCIM user-group add operation"); - } - } else if (operation.op === "remove" || operation.op === "Remove") { - const orgMembershipId = extractScimValueFromPath(operation.path); - if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 }); - const orgMembership = await orgMembershipDAL.findById(orgMembershipId); - if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 }); - await removeUsersFromGroupByUserIds({ - group, - userIds: [orgMembership.userId as string], - userDAL, - userGroupMembershipDAL, - groupProjectDAL, - projectKeyDAL - }); - } else { - throw new ScimRequestError({ - detail: "Invalid Operation", - status: 400 - }); - } - } - const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId); - - return buildScimGroup({ + const scimGroup = buildScimGroup({ groupId: group.id, name: group.name, members: members.map((member) => ({ + value: member.orgMembershipId + })), + createdAt: group.createdAt, + updatedAt: group.updatedAt + }); + scimPatch(scimGroup, operations); + // remove members is a weird case not following scim convention + await $replaceGroupDAL(groupId, orgId, { displayName: scimGroup.displayName, members: scimGroup.members }); + + const updatedScimMembers = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId); + return { + ...scimGroup, + members: updatedScimMembers.map((member) => ({ value: member.orgMembershipId, display: `${member.firstName ?? ""} ${member.lastName ?? ""}` })) - }); + }; }; const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => { @@ -1058,8 +1041,8 @@ export const scimServiceFactory = ({ createScimGroup, getScimGroup, deleteScimGroup, - updateScimGroupNamePut, - updateScimGroupNamePatch, + replaceScimGroup, + updateScimGroup, fnValidateScimToken }; }; diff --git a/backend/src/ee/services/scim/scim-types.ts b/backend/src/ee/services/scim/scim-types.ts index 153d8a1bc..9caa44a03 100644 --- a/backend/src/ee/services/scim/scim-types.ts +++ b/backend/src/ee/services/scim/scim-types.ts @@ -66,6 +66,7 @@ export type TListScimGroupsDTO = { filter?: string; limit: number; orgId: string; + isMembersExcluded?: boolean; }; export type TListScimGroups = { @@ -104,31 +105,7 @@ export type TUpdateScimGroupNamePutDTO = { export type TUpdateScimGroupNamePatchDTO = { groupId: string; orgId: string; - operations: (TRemoveOp | TReplaceOp | TAddOp)[]; -}; - -// akhilmhdh: I know, this is done due to lack of time. Need to change later to support as normalized rather than like this -// Forgive akhil blame tony -type TReplaceOp = { - op: "replace" | "Replace"; - value: { - id: string; - displayName: string; - }; -}; - -type TRemoveOp = { - op: "remove" | "Remove"; - path: string; -}; - -type TAddOp = { - op: "add" | "Add"; - path: string; - value: { - value: string; - display?: string; - }[]; + operations: ScimPatchOperation[]; }; export type TDeleteScimGroupDTO = { @@ -174,10 +151,11 @@ export type TScimGroup = { displayName: string; members: { value: string; - display: string; + display?: string; }[]; meta: { resourceType: string; - location: null; + created: Date; + lastModified: Date; }; }; diff --git a/backend/src/lib/knex/scim.ts b/backend/src/lib/knex/scim.ts index 81e87f656..530a7d45a 100644 --- a/backend/src/lib/knex/scim.ts +++ b/backend/src/lib/knex/scim.ts @@ -26,7 +26,6 @@ export const generateKnexQueryFromScim = ( switch (scimFilterAst.op) { case "eq": { const attrPath = getAttributeField(scimFilterAst.attrPath); - console.log(attrPath, scimFilterAst.compValue); if (attrPath) void query.where(attrPath, scimFilterAst.compValue); break; }