From a7af3a48d91f2d0f1b1bf8fce6b99d3ff4ef2aea Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 28 Apr 2024 19:09:12 -0700 Subject: [PATCH] Continue moving SCIM userId refs to orgMembershipId --- .infisicalignore | 3 +- backend/src/ee/routes/v1/scim-router.ts | 32 ++- .../ldap-config/ldap-config-service.ts | 8 +- .../services/license/__mocks__/licence-fns.ts | 4 +- .../src/ee/services/license/licence-fns.ts | 8 +- .../src/ee/services/license/license-types.ts | 4 +- .../saml-config/saml-config-service.ts | 8 +- backend/src/ee/services/scim/scim-fns.ts | 12 +- backend/src/ee/services/scim/scim-service.ts | 246 +++++++++++------- backend/src/ee/services/scim/scim-types.ts | 8 +- backend/src/server/routes/index.ts | 2 + backend/src/services/org/org-fns.ts | 2 + .../services/user-alias/user-alias-types.ts | 2 +- 13 files changed, 205 insertions(+), 134 deletions(-) diff --git a/.infisicalignore b/.infisicalignore index 348f9e327..d5cc9f15d 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -2,4 +2,5 @@ frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/IdentityRbacSection.tsx:generic-api-key:206 frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:304 frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/MemberRbacSection.tsx:generic-api-key:206 -frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292 \ No newline at end of file +frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292 +docs/self-hosting/configuration/envars.mdx:generic-api-key:106 diff --git a/backend/src/ee/routes/v1/scim-router.ts b/backend/src/ee/routes/v1/scim-router.ts index dea0e3d70..ed2ce92db 100644 --- a/backend/src/ee/routes/v1/scim-router.ts +++ b/backend/src/ee/routes/v1/scim-router.ts @@ -152,8 +152,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log("GET /Users req.query: ", req.query); const users = await req.server.services.scim.listScimUsers({ - offset: req.query.startIndex, + startIndex: req.query.startIndex, limit: req.query.count, filter: req.query.filter, orgId: req.permission.orgId @@ -163,11 +164,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/Users/:userId", + url: "/Users/:orgMembershipId", method: "GET", schema: { params: z.object({ - userId: z.string().trim() + orgMembershipId: z.string().trim() }), response: { 201: z.object({ @@ -192,8 +193,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`GET /Users/${req.params.orgMembershipId}`); const user = await req.server.services.scim.getScimUser({ - userId: req.params.userId, + orgMembershipId: req.params.orgMembershipId, orgId: req.permission.orgId }); return user; @@ -246,6 +248,8 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log("POST /Users req.body: ", req.body); + const primaryEmail = req.body.emails?.find((email) => email.primary)?.value; const user = await req.server.services.scim.createScimUser({ @@ -261,11 +265,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/Users/:userId", + url: "/Users/:orgMembershipId", method: "DELETE", schema: { params: z.object({ - userId: z.string().trim() + orgMembershipId: z.string().trim() }), response: { 200: z.object({}) @@ -273,8 +277,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`DELETE /Users/${req.params.orgMembershipId}`); const user = await req.server.services.scim.deleteScimUser({ - userId: req.params.userId, + orgMembershipId: req.params.orgMembershipId, orgId: req.permission.orgId }); @@ -319,6 +324,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log("POST /Groups req.body: ", req.body); const group = await req.server.services.scim.createScimGroup({ orgId: req.permission.orgId, ...req.body @@ -359,6 +365,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log("GET /Groups req.query: ", req.query); const groups = await req.server.services.scim.listScimGroups({ orgId: req.permission.orgId, offset: req.query.startIndex, @@ -395,6 +402,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`GET /Groups/${req.params.groupId}`); const group = await req.server.services.scim.getScimGroup({ groupId: req.params.groupId, orgId: req.permission.orgId @@ -440,6 +448,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`PUT /Groups/${req.params.groupId} req.body: `, req.body); const group = await req.server.services.scim.updateScimGroupNamePut({ groupId: req.params.groupId, orgId: req.permission.orgId, @@ -501,6 +510,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`PATCH /Groups/${req.params.groupId} req.body: `, req.body); const group = await req.server.services.scim.updateScimGroupNamePatch({ groupId: req.params.groupId, orgId: req.permission.orgId, @@ -524,6 +534,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`DELETE /Groups/${req.params.groupId}`); const group = await req.server.services.scim.deleteScimGroup({ groupId: req.params.groupId, orgId: req.permission.orgId @@ -534,11 +545,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/Users/:userId", + url: "/Users/:orgMembershipId", method: "PUT", schema: { params: z.object({ - userId: z.string().trim() + orgMembershipId: z.string().trim() }), body: z.object({ schemas: z.array(z.string()), @@ -574,8 +585,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), handler: async (req) => { + console.log(`PUT /Users/${req.params.orgMembershipId} req.body: `, req.body); const user = await req.server.services.scim.replaceScimUser({ - userId: req.params.userId, + orgMembershipId: req.params.orgMembershipId, orgId: req.permission.orgId, active: req.body.active }); diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index ce7414747..f712a6ea4 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -31,7 +31,7 @@ import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal import { TUserDALFactory } from "@app/services/user/user-dal"; import { normalizeUsername } from "@app/services/user/user-fns"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; -import { TUserAliasType } from "@app/services/user-alias/user-alias-types"; +import { UserAliasType } from "@app/services/user-alias/user-alias-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -395,7 +395,7 @@ export const ldapConfigServiceFactory = ({ let userAlias = await userAliasDAL.findOne({ externalId, orgId, - aliasType: TUserAliasType.LDAP + aliasType: UserAliasType.LDAP }); const organization = await orgDAL.findOrgById(orgId); @@ -449,7 +449,7 @@ export const ldapConfigServiceFactory = ({ { userId: newUser.id, username, - aliasType: TUserAliasType.LDAP, + aliasType: UserAliasType.LDAP, externalId, emails, orgId @@ -564,7 +564,7 @@ export const ldapConfigServiceFactory = ({ organizationId: organization.id, organizationSlug: organization.slug, authMethod: AuthMethod.LDAP, - authType: TUserAliasType.LDAP, + authType: UserAliasType.LDAP, isUserCompleted, ...(relayState ? { diff --git a/backend/src/ee/services/license/__mocks__/licence-fns.ts b/backend/src/ee/services/license/__mocks__/licence-fns.ts index 20186718d..b5cbf103e 100644 --- a/backend/src/ee/services/license/__mocks__/licence-fns.ts +++ b/backend/src/ee/services/license/__mocks__/licence-fns.ts @@ -17,8 +17,8 @@ export const getDefaultOnPremFeatures = () => { customAlerts: false, auditLogs: false, auditLogsRetentionDays: 0, - samlSSO: true, - scim: true, + samlSSO: false, + scim: false, ldap: false, groups: false, status: null, diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 5046ad125..8a4de57f1 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -24,10 +24,10 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ customAlerts: false, auditLogs: false, auditLogsRetentionDays: 0, - samlSSO: true, - scim: true, - ldap: true, - groups: true, + samlSSO: false, + scim: false, + ldap: false, + groups: false, status: null, trial_end: null, has_used_trial: true, diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 2cc321373..1cea39a83 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -40,8 +40,8 @@ export type TFeatureSet = { customAlerts: false; auditLogs: false; auditLogsRetentionDays: 0; - samlSSO: true; - scim: true; + samlSSO: false; + scim: false; ldap: false; groups: false; status: null; diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 4d824613f..2a7a6a60a 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -27,7 +27,7 @@ import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membe import { TUserDALFactory } from "@app/services/user/user-dal"; import { normalizeUsername } from "@app/services/user/user-fns"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; -import { TUserAliasType } from "@app/services/user-alias/user-alias-types"; +import { UserAliasType } from "@app/services/user-alias/user-alias-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -326,7 +326,7 @@ export const samlConfigServiceFactory = ({ const userAlias = await userAliasDAL.findOne({ externalId, orgId, - aliasType: TUserAliasType.SAML + aliasType: UserAliasType.SAML }); const organization = await orgDAL.findOrgById(orgId); @@ -385,7 +385,7 @@ export const samlConfigServiceFactory = ({ await userAliasDAL.create( { userId: newUser.id, - aliasType: TUserAliasType.SAML, + aliasType: UserAliasType.SAML, externalId, emails: email ? [email] : [], orgId @@ -421,7 +421,7 @@ export const samlConfigServiceFactory = ({ organizationId: organization.id, organizationSlug: organization.slug, authMethod: authProvider, - authType: TUserAliasType.SAML, + authType: UserAliasType.SAML, isUserCompleted, ...(relayState ? { diff --git a/backend/src/ee/services/scim/scim-fns.ts b/backend/src/ee/services/scim/scim-fns.ts index e816cffcf..8668ed5e1 100644 --- a/backend/src/ee/services/scim/scim-fns.ts +++ b/backend/src/ee/services/scim/scim-fns.ts @@ -2,31 +2,31 @@ import { TListScimGroups, TListScimUsers, TScimGroup, TScimUser } from "./scim-t export const buildScimUserList = ({ scimUsers, - offset, + startIndex, limit }: { scimUsers: TScimUser[]; - offset: number; + startIndex: number; limit: number; }): TListScimUsers => { return { Resources: scimUsers, itemsPerPage: limit, schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], - startIndex: offset, + startIndex, totalResults: scimUsers.length }; }; export const buildScimUser = ({ - userId, + orgMembershipId, username, email, firstName, lastName, active }: { - userId: string; + orgMembershipId: string; username: string; email?: string | null; firstName: string; @@ -35,7 +35,7 @@ export const buildScimUser = ({ }): TScimUser => { const scimUser = { schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"], - id: userId, + id: orgMembershipId, userName: username, displayName: `${firstName} ${lastName}`, name: { diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 605003e4f..6700e7f05 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability"; import slugify from "@sindresorhus/slugify"; import jwt from "jsonwebtoken"; -import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups } from "@app/db/schemas"; +import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; @@ -11,16 +11,20 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TOrgPermission } from "@app/lib/types"; -import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; +import { AuthTokenType } from "@app/services/auth/auth-type"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { deleteOrgMembership } from "@app/services/org/org-fns"; +import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TUserDALFactory } from "@app/services/user/user-dal"; +import { normalizeUsername } from "@app/services/user/user-fns"; +import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; +import { UserAliasType } from "@app/services/user-alias/user-alias-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -47,11 +51,16 @@ import { type TScimServiceFactoryDep = { scimDAL: Pick; - userDAL: Pick; + userDAL: Pick< + TUserDALFactory, + "find" | "findOne" | "create" | "transaction" | "findUserEncKeyByUserIdsBatch" | "findById" + >; + userAliasDAL: TUserAliasDALFactory; // TODO: pick orgDAL: Pick< TOrgDALFactory, - "createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" + "createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById" >; + orgMembershipDAL: TOrgMembershipDALFactory; // TODO: Pick projectDAL: Pick; projectMembershipDAL: Pick; groupDAL: Pick< @@ -64,7 +73,7 @@ type TScimServiceFactoryDep = { projectBotDAL: Pick; licenseService: Pick; permissionService: Pick; - smtpService: TSmtpService; + smtpService: Pick; }; export type TScimServiceFactory = ReturnType; @@ -75,7 +84,9 @@ export const scimServiceFactory = ({ licenseService, scimDAL, userDAL, + userAliasDAL, orgDAL, + orgMembershipDAL, projectDAL, projectMembershipDAL, groupDAL, @@ -162,8 +173,13 @@ export const scimServiceFactory = ({ }; // SCIM server endpoints - const listScimUsers = async ({ offset, limit, filter, orgId }: TListScimUsersDTO): Promise => { - console.log("listScimUsers"); // done + const listScimUsers = async ({ startIndex, limit, filter, orgId }: TListScimUsersDTO): Promise => { + console.log("listScimUsers args: ", { + startIndex, + limit, + filter, + orgId + }); // done const org = await orgDAL.findById(orgId); if (!org.scimEnabled) @@ -181,11 +197,11 @@ export const scimServiceFactory = ({ attributeName = "email"; } - return { [attributeName]: parsedValue }; + return { [attributeName]: parsedValue.replace(/"/g, "") }; }; const findOpts = { - ...(offset && { offset }), + ...(startIndex && { offset: startIndex - 1 }), ...(limit && { limit }) }; @@ -197,11 +213,9 @@ export const scimServiceFactory = ({ findOpts ); - console.log("orgDAL.findMembership users: ", users); - const scimUsers = users.map(({ id, username, firstName, lastName, email }) => buildScimUser({ - userId: id ?? "", + orgMembershipId: id ?? "", username, firstName: firstName ?? "", lastName: lastName ?? "", @@ -212,17 +226,20 @@ export const scimServiceFactory = ({ return buildScimUserList({ scimUsers, - offset, + startIndex, limit }); }; - const getScimUser = async ({ userId, orgId }: TGetScimUserDTO) => { - console.log("getScimUser"); // done + const getScimUser = async ({ orgMembershipId, orgId }: TGetScimUserDTO) => { + console.log("getScimUser args: ", { + orgMembershipId, + orgId + }); // done const [membership] = await orgDAL .findMembership({ - userId, - [`${TableName.OrgMembership}.orgId` as "id"]: orgId + [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId, + [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId }) .catch(() => { throw new ScimRequestError({ @@ -246,7 +263,7 @@ export const scimServiceFactory = ({ console.log("getScimUser membership: ", membership); return buildScimUser({ - userId: membership.id, + orgMembershipId: membership.id, username: membership.username, email: membership.email ?? "", firstName: membership.firstName as string, @@ -256,7 +273,14 @@ export const scimServiceFactory = ({ }; const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => { - console.log("createScimUser"); // TODO: update implementation to always create a new user and be based on orgMembershipId + // do we get external ID or not? + console.log("createScimUser args: ", { + username, + email, + firstName, + lastName, + orgId + }); const org = await orgDAL.findById(orgId); if (!org) @@ -271,67 +295,85 @@ export const scimServiceFactory = ({ status: 403 }); - let user = await userDAL.findOne({ - username + const appCfg = getConfig(); + + const userAlias = await userAliasDAL.findOne({ + externalId: username, + orgId, + aliasType: UserAliasType.SAML }); - if (user) { - await userDAL.transaction(async (tx) => { - const [orgMembership] = await orgDAL.findMembership( + const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => { + let user: TUsers; + let orgMembership: TOrgMemberships; + if (userAlias) { + user = await userDAL.findById(userAlias.userId, tx); + orgMembership = await orgMembershipDAL.findOne( { userId: user.id, - [`${TableName.OrgMembership}.orgId` as "id"]: orgId - }, - { tx } - ); - if (orgMembership) - throw new ScimRequestError({ - detail: "User already exists in the database", - status: 409 - }); - - if (!orgMembership) { - await orgDAL.createMembership( - { - userId: user.id, - orgId, - inviteEmail: email, - role: OrgMembershipRole.Member, - status: OrgMembershipStatus.Invited - }, - tx - ); - } - }); - } else { - user = await userDAL.transaction(async (tx) => { - const newUser = await userDAL.create( - { - username, - email, - firstName, - lastName, - authMethods: [AuthMethod.EMAIL], - isGhost: false + orgId }, tx ); - await orgDAL.createMembership( + if (!orgMembership) { + orgMembership = await orgMembershipDAL.create( + { + userId: userAlias.userId, + inviteEmail: email, + orgId, + role: OrgMembershipRole.Member, + status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later + }, + tx + ); + } else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) { + orgMembership = await orgMembershipDAL.updateById( + orgMembership.id, + { + status: OrgMembershipStatus.Accepted + }, + tx + ); + } + } else { + const uniqueUsername = await normalizeUsername(username, userDAL); + user = await userDAL.create( { + username: uniqueUsername, + email, + isEmailVerified: appCfg.TRUST_SAML_EMAILS, + firstName, + lastName, + authMethods: [], + isGhost: false + }, + tx + ); + await userAliasDAL.create( + { + userId: user.id, + aliasType: UserAliasType.SAML, + externalId: username, + emails: email ? [email] : [], + orgId + }, + tx + ); + orgMembership = await orgMembershipDAL.create( + { + userId: user.id, inviteEmail: email, orgId, - userId: newUser.id, role: OrgMembershipRole.Member, status: OrgMembershipStatus.Invited }, tx ); - return newUser; - }); - } + } - const appCfg = getConfig(); + return { user, orgMembership }; + }); if (email) { await smtpService.sendMail({ @@ -346,11 +388,11 @@ export const scimServiceFactory = ({ } return buildScimUser({ - userId: user.id, - username: user.username, - firstName: user.firstName as string, - lastName: user.lastName as string, - email: user.email ?? "", + orgMembershipId: createdOrgMembership.id, + username: createdUser.username, + firstName: createdUser.firstName as string, + lastName: createdUser.lastName as string, + email: createdUser.email ?? "", active: true }); }; @@ -406,7 +448,7 @@ export const scimServiceFactory = ({ } return buildScimUser({ - userId: membership.id, + orgMembershipId: membership.id, username: membership.username, email: membership.email, firstName: membership.firstName as string, @@ -415,12 +457,16 @@ export const scimServiceFactory = ({ }); }; - const replaceScimUser = async ({ userId, active, orgId }: TReplaceScimUserDTO) => { - console.log("replaceScimUser"); // done + const replaceScimUser = async ({ orgMembershipId, active, orgId }: TReplaceScimUserDTO) => { + console.log("replaceScimUser args: ", { + orgMembershipId, + orgId, + active + }); // done const [membership] = await orgDAL .findMembership({ - userId, - [`${TableName.OrgMembership}.orgId` as "id"]: orgId + [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId, + [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId }) .catch(() => { throw new ScimRequestError({ @@ -453,7 +499,7 @@ export const scimServiceFactory = ({ } return buildScimUser({ - userId: membership.id, + orgMembershipId: membership.id, username: membership.username, email: membership.email, firstName: membership.firstName as string, @@ -462,19 +508,15 @@ export const scimServiceFactory = ({ }); }; - const deleteScimUser = async ({ userId, orgId }: TDeleteScimUserDTO) => { - console.log("deleteScimUser"); // done - const [membership] = await orgDAL - .findMembership({ - userId, - [`${TableName.OrgMembership}.orgId` as "id"]: orgId - }) - .catch(() => { - throw new ScimRequestError({ - detail: "User not found", - status: 404 - }); - }); + const deleteScimUser = async ({ orgMembershipId, orgId }: TDeleteScimUserDTO) => { + console.log("deleteScimUser args: ", { + orgMembershipId, + orgId + }); // done + const [membership] = await orgDAL.findMembership({ + [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId, + [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId + }); if (!membership) throw new ScimRequestError({ @@ -593,12 +635,19 @@ export const scimServiceFactory = ({ return { group, newMembers: [] }; }); + const orgMemberships = await orgDAL.findMembership({ + orgId, + $in: { + userId: newGroup.newMembers.map((member) => member.id) + } + }); + return buildScimGroup({ groupId: newGroup.group.id, name: newGroup.group.name, - members: newGroup.newMembers.map((member) => ({ - value: member.id, - display: `${member.firstName} ${member.lastName}` + members: orgMemberships.map(({ id, firstName, lastName }) => ({ + value: id, + display: `${firstName} ${lastName}` })) }); }; @@ -627,15 +676,20 @@ export const scimServiceFactory = ({ groupId: group.id }); + const orgMemberships = await orgDAL.findMembership({ + orgId, + $in: { + userId: users.filter((user) => user.isPartOfGroup).map((user) => user.id) + } + }); + return buildScimGroup({ groupId: group.id, name: group.name, - members: users - .filter((user) => user.isPartOfGroup) - .map((user) => ({ - value: user.id, - display: `${user.firstName} ${user.lastName}` - })) + members: orgMemberships.map(({ id, firstName, lastName }) => ({ + value: id, + display: `${firstName} ${lastName}` + })) }); }; diff --git a/backend/src/ee/services/scim/scim-types.ts b/backend/src/ee/services/scim/scim-types.ts index 73d0ebe78..a82f08656 100644 --- a/backend/src/ee/services/scim/scim-types.ts +++ b/backend/src/ee/services/scim/scim-types.ts @@ -12,7 +12,7 @@ export type TDeleteScimTokenDTO = { // SCIM server endpoint types export type TListScimUsersDTO = { - offset: number; + startIndex: number; limit: number; filter?: string; orgId: string; @@ -27,7 +27,7 @@ export type TListScimUsers = { }; export type TGetScimUserDTO = { - userId: string; + orgMembershipId: string; orgId: string; }; @@ -54,13 +54,13 @@ export type TUpdateScimUserDTO = { }; export type TReplaceScimUserDTO = { - userId: string; + orgMembershipId: string; active: boolean; orgId: string; }; export type TDeleteScimUserDTO = { - userId: string; + orgMembershipId: string; orgId: string; }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 299319e41..b944eb37e 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -292,7 +292,9 @@ export const registerRoutes = async ( licenseService, scimDAL, userDAL, + userAliasDAL, orgDAL, + orgMembershipDAL, projectDAL, projectMembershipDAL, groupDAL, diff --git a/backend/src/services/org/org-fns.ts b/backend/src/services/org/org-fns.ts index ec6d4cb2d..69eac5e07 100644 --- a/backend/src/services/org/org-fns.ts +++ b/backend/src/services/org/org-fns.ts @@ -34,6 +34,8 @@ export const deleteOrgMembership = async ({ tx ); + // TODO: delete associated aliases + return orgMembership; }); diff --git a/backend/src/services/user-alias/user-alias-types.ts b/backend/src/services/user-alias/user-alias-types.ts index 6188732c6..09204644f 100644 --- a/backend/src/services/user-alias/user-alias-types.ts +++ b/backend/src/services/user-alias/user-alias-types.ts @@ -1,4 +1,4 @@ -export enum TUserAliasType { +export enum UserAliasType { LDAP = "ldap", SAML = "saml" }