From d222bbf1317bd583e52f6aa1d30c568623051a01 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 23 Apr 2024 15:04:02 -0700 Subject: [PATCH] Update ldap group mapping schema, replace group input field with select --- .../20240423023203_ldap-config-groups.ts | 5 +- backend/src/db/schemas/ldap-group-maps.ts | 2 +- backend/src/ee/routes/v1/ldap-router.ts | 15 ++++- .../ldap-config/ldap-config-service.ts | 64 ++++++++++++------- .../services/ldap-config/ldap-config-types.ts | 1 + .../ldap-config/ldap-group-map-dal.ts | 34 +++++++++- .../services/license/__mocks__/licence-fns.ts | 2 +- .../src/ee/services/license/licence-fns.ts | 2 +- .../src/ee/services/license/license-types.ts | 2 +- frontend/src/hooks/api/ldapConfig/types.ts | 6 +- .../OrgAuthTab/LDAPGroupMapModal.tsx | 31 +++++++-- .../components/OrgAuthTab/OrgLDAPSection.tsx | 1 - 12 files changed, 123 insertions(+), 42 deletions(-) diff --git a/backend/src/db/migrations/20240423023203_ldap-config-groups.ts b/backend/src/db/migrations/20240423023203_ldap-config-groups.ts index 43d1a389b..dd4da5123 100644 --- a/backend/src/db/migrations/20240423023203_ldap-config-groups.ts +++ b/backend/src/db/migrations/20240423023203_ldap-config-groups.ts @@ -10,8 +10,9 @@ export async function up(knex: Knex): Promise { t.uuid("ldapConfigId").notNullable(); t.foreign("ldapConfigId").references("id").inTable(TableName.LdapConfig).onDelete("CASCADE"); t.string("ldapGroupCN").notNullable(); - t.string("groupSlug").notNullable(); - t.unique(["ldapGroupCN", "groupSlug", "ldapConfigId"]); + t.uuid("groupId").notNullable(); + t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE"); + t.unique(["ldapGroupCN", "groupId", "ldapConfigId"]); }); } diff --git a/backend/src/db/schemas/ldap-group-maps.ts b/backend/src/db/schemas/ldap-group-maps.ts index 3cb744238..d51d151b8 100644 --- a/backend/src/db/schemas/ldap-group-maps.ts +++ b/backend/src/db/schemas/ldap-group-maps.ts @@ -11,7 +11,7 @@ export const LdapGroupMapsSchema = z.object({ id: z.string().uuid(), ldapConfigId: z.string().uuid(), ldapGroupCN: z.string(), - groupSlug: z.string() + groupId: z.string().uuid() }); export type TLdapGroupMaps = z.infer; diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index dd0bde278..77e4b1da9 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -71,6 +71,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { // If group search values are not provided, proceed directly to LDAP login return await server.services.ldap .ldapLogin({ + ldapConfigId: ldapConfig.id, externalId: user.uidNumber, username: user.uid, firstName: user.givenName, @@ -111,6 +112,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { // groups here ldapClient.unbind(); return server.services.ldap.ldapLogin({ + ldapConfigId: ldapConfig.id, externalId: user.uidNumber, username: user.uid, firstName: user.givenName, @@ -292,7 +294,18 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { configId: z.string().trim() }), response: { - 200: z.array(LdapGroupMapsSchema) + 200: z.array( + z.object({ + id: z.string(), + ldapConfigId: z.string(), + ldapGroupCN: z.string(), + group: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }) + }) + ) } }, handler: async (req) => { 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 f4f66830e..5071a5db5 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -37,7 +37,7 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal"; type TLdapConfigServiceFactoryDep = { ldapConfigDAL: Pick; - ldapGroupMapDAL: Pick; + ldapGroupMapDAL: Pick; orgDAL: Pick< TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" @@ -57,6 +57,7 @@ export const ldapConfigServiceFactory = ({ ldapGroupMapDAL, orgDAL, orgBotDAL, + groupDAL, userDAL, userAliasDAL, permissionService, @@ -343,7 +344,17 @@ export const ldapConfigServiceFactory = ({ return { opts, ldapConfig }; }; - const ldapLogin = async ({ externalId, username, firstName, lastName, emails, orgId, relayState }: TLdapLoginDTO) => { + const ldapLogin = async ({ + // ldapConfigId, + externalId, + username, + firstName, + lastName, + emails, + groups, + orgId, + relayState + }: TLdapLoginDTO) => { const appCfg = getConfig(); let userAlias = await userAliasDAL.findOne({ externalId, @@ -419,23 +430,28 @@ export const ldapConfigServiceFactory = ({ const user = await userDAL.findOne({ id: userAlias.userId }); - // if (groups) { // TODO - // /** - // * TODO: - // * - Query for groups matching name - // * - Provision, de-provision user to groups accordingly - // */ - - // console.log("there are groups"); - - // const matchingGroups = await groupDAL.find({ - // $in: { - // name: groups.map((group) => group.cn) - // } - // }); - - // console.log("found matching groups"); - // } + if (groups) { + // TODO + // const m = await ldapGroupMapDAL.find({ + // ldapConfigId, + // $in: { + // ldapGroupCN: groups.map((group) => group.cn) + // } + // }); + /** + * TODO: + * - Find relevant group maps + * - Query for groups matching name + * - Provision, de-provision user to groups accordingly + */ + // console.log("there are groups"); + // const matchingGroups = await groupDAL.find({ + // $in: { + // name: groups.map((group) => group.cn) + // } + // }); + // console.log("found matching groups"); + } const isUserCompleted = Boolean(user.isAccepted); @@ -483,9 +499,7 @@ export const ldapConfigServiceFactory = ({ if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" }); - const groupMaps = await ldapGroupMapDAL.find({ - ldapConfigId - }); + const groupMaps = await ldapGroupMapDAL.findLdapGroupMapsByLdapConfigId(ldapConfigId); return groupMaps; }; @@ -507,13 +521,15 @@ export const ldapConfigServiceFactory = ({ id: ldapConfigId, orgId }); - if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" }); + const group = await groupDAL.findOne({ slug: groupSlug, orgId }); + if (!group) throw new BadRequestError({ message: "Failed to find group" }); + const groupMap = await ldapGroupMapDAL.create({ ldapConfigId, ldapGroupCN, - groupSlug + groupId: group.id }); return groupMap; diff --git a/backend/src/ee/services/ldap-config/ldap-config-types.ts b/backend/src/ee/services/ldap-config/ldap-config-types.ts index 345e6fba1..116e1b940 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-types.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-types.ts @@ -31,6 +31,7 @@ export type TGetLdapCfgDTO = { } & TOrgPermission; export type TLdapLoginDTO = { + ldapConfigId: string; externalId: string; username: string; firstName: string; diff --git a/backend/src/ee/services/ldap-config/ldap-group-map-dal.ts b/backend/src/ee/services/ldap-config/ldap-group-map-dal.ts index 1ecb5c9ba..b446f8163 100644 --- a/backend/src/ee/services/ldap-config/ldap-group-map-dal.ts +++ b/backend/src/ee/services/ldap-config/ldap-group-map-dal.ts @@ -1,11 +1,41 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; -import { ormify } from "@app/lib/knex"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify, selectAllTableCols } from "@app/lib/knex"; export type TLdapGroupMapDALFactory = ReturnType; export const ldapGroupMapDALFactory = (db: TDbClient) => { const ldapGroupMapOrm = ormify(db, TableName.LdapGroupMap); - return { ...ldapGroupMapOrm }; + const findLdapGroupMapsByLdapConfigId = async (ldapConfigId: string) => { + try { + const docs = await db(TableName.LdapGroupMap) + .where(`${TableName.LdapGroupMap}.ldapConfigId`, ldapConfigId) + .join(TableName.Groups, `${TableName.LdapGroupMap}.groupId`, `${TableName.Groups}.id`) + .select(selectAllTableCols(TableName.LdapGroupMap)) + .select( + db.ref("id").withSchema(TableName.Groups).as("groupId"), + db.ref("name").withSchema(TableName.Groups).as("groupSlug"), + db.ref("slug").withSchema(TableName.Groups).as("groupName") + ); + + return docs.map((doc) => { + return { + id: doc.id, + ldapConfigId: doc.ldapConfigId, + ldapGroupCN: doc.ldapGroupCN, + group: { + id: doc.groupId, + name: doc.groupName, + slug: doc.groupSlug + } + }; + }); + } catch (error) { + throw new DatabaseError({ error, name: "findGroupMaps" }); + } + }; + + return { ...ldapGroupMapOrm, findLdapGroupMapsByLdapConfigId }; }; diff --git a/backend/src/ee/services/license/__mocks__/licence-fns.ts b/backend/src/ee/services/license/__mocks__/licence-fns.ts index 6596a9857..f87d23c78 100644 --- a/backend/src/ee/services/license/__mocks__/licence-fns.ts +++ b/backend/src/ee/services/license/__mocks__/licence-fns.ts @@ -20,7 +20,7 @@ export const getDefaultOnPremFeatures = () => { samlSSO: false, scim: false, ldap: true, - groups: false, + groups: true, status: null, trial_end: null, has_used_trial: true, diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index e9975e7d4..787897dda 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -27,7 +27,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ samlSSO: false, scim: false, ldap: true, - groups: false, + groups: true, 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 3e6fef8e6..e7d5f02fe 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -43,7 +43,7 @@ export type TFeatureSet = { samlSSO: false; scim: false; ldap: true; - groups: false; + groups: true; status: null; trial_end: null; has_used_trial: true; diff --git a/frontend/src/hooks/api/ldapConfig/types.ts b/frontend/src/hooks/api/ldapConfig/types.ts index d3488d4c8..0d3519c14 100644 --- a/frontend/src/hooks/api/ldapConfig/types.ts +++ b/frontend/src/hooks/api/ldapConfig/types.ts @@ -2,5 +2,9 @@ export type LDAPGroupMap = { id: string; ldapConfigId: string; ldapGroupCN: string; - groupSlug: string; + group: { + id: string; + name: string; + slug: string; + }; }; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx index 6ee72dab2..e8c9828f6 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx @@ -14,6 +14,8 @@ import { Input, Modal, ModalContent, + Select, + SelectItem, Table, TableContainer, TableSkeleton, @@ -27,7 +29,9 @@ import { useCreateLDAPGroupMapping, useDeleteLDAPGroupMapping, useGetLDAPConfig, - useGetLDAPGroupMaps} from "@app/hooks/api"; + useGetLDAPGroupMaps, + useGetOrganizationGroups +} from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z.object({ @@ -56,6 +60,7 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: const { currentOrg } = useOrganization(); const { data: ldapConfig } = useGetLDAPConfig(currentOrg?.id ?? ""); + const { data: groups } = useGetOrganizationGroups(currentOrg?.id ?? ""); const { data: groupMaps, isLoading } = useGetLDAPGroupMaps(ldapConfig?.id ?? ""); const { mutateAsync: createLDAPGroupMapping, isLoading: createIsLoading } = useCreateLDAPGroupMapping(); @@ -152,15 +157,27 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: ( + defaultValue="" + render={({ field: { onChange, ...field }, fieldState: { error } }) => (
- + @@ -183,11 +200,11 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: {isLoading && } {!isLoading && - groupMaps?.map(({ id, ldapGroupCN, groupSlug }) => { + groupMaps?.map(({ id, ldapGroupCN, group: { name } }) => { return ( {ldapGroupCN} - {groupSlug} + {name} { diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgLDAPSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgLDAPSection.tsx index e2f6e6419..4c2d4009e 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgLDAPSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgLDAPSection.tsx @@ -83,7 +83,6 @@ export const OrgLDAPSection = (): JSX.Element => { }; const openLDAPGroupMapModal = () => { - console.log("openLDAPGroupMapModal sub: ", subscription); if (!subscription?.ldap) { handlePopUpOpen("upgradePlan"); return;