From 2d3a4a75597facb386a4714d5a91177a0f953b9f Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 26 Jun 2024 18:36:28 +0800 Subject: [PATCH] feat: added support for configurable ldap user identifier --- ...26053953_add-ldap-unique-user-attribute.ts | 19 ++++++++++++++++ backend/src/db/schemas/ldap-configs.ts | 3 ++- backend/src/ee/routes/v1/ldap-router.ts | 10 +++++++-- .../ldap-config/ldap-config-service.ts | 8 ++++++- .../services/ldap-config/ldap-config-types.ts | 3 +++ .../src/hooks/api/ldapConfig/mutations.tsx | 6 +++++ .../components/OrgAuthTab/LDAPModal.tsx | 22 ++++++++++++++++++- 7 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 backend/src/db/migrations/20240626053953_add-ldap-unique-user-attribute.ts diff --git a/backend/src/db/migrations/20240626053953_add-ldap-unique-user-attribute.ts b/backend/src/db/migrations/20240626053953_add-ldap-unique-user-attribute.ts new file mode 100644 index 000000000..dc87ff515 --- /dev/null +++ b/backend/src/db/migrations/20240626053953_add-ldap-unique-user-attribute.ts @@ -0,0 +1,19 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasColumn(TableName.LdapConfig, "uniqueUserAttribute"))) { + await knex.schema.alterTable(TableName.LdapConfig, (tb) => { + tb.string("uniqueUserAttribute").notNullable().defaultTo(""); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.LdapConfig, "uniqueUserAttribute")) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + t.dropColumn("uniqueUserAttribute"); + }); + } +} diff --git a/backend/src/db/schemas/ldap-configs.ts b/backend/src/db/schemas/ldap-configs.ts index 86fd6acb6..460c2cff6 100644 --- a/backend/src/db/schemas/ldap-configs.ts +++ b/backend/src/db/schemas/ldap-configs.ts @@ -26,7 +26,8 @@ export const LdapConfigsSchema = z.object({ updatedAt: z.date(), groupSearchBase: z.string().default(""), groupSearchFilter: z.string().default(""), - searchFilter: z.string().default("") + searchFilter: z.string().default(""), + uniqueUserAttribute: z.string().default("") }); export type TLdapConfigs = z.infer; diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 8cccbaac6..735ba632c 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -70,10 +70,13 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase); } + const externalId = ldapConfig.uniqueUserAttribute ? user[ldapConfig.uniqueUserAttribute] : user.uidNumber; + const username = ldapConfig.uniqueUserAttribute ? externalId : user.uid; + const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({ + externalId, + username, ldapConfigId: ldapConfig.id, - externalId: user.uidNumber, - username: user.uid, firstName: user.givenName ?? user.cn ?? "", lastName: user.sn ?? "", email: user.mail, @@ -138,6 +141,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { url: z.string(), bindDN: z.string(), bindPass: z.string(), + uniqueUserAttribute: z.string(), searchBase: z.string(), searchFilter: z.string(), groupSearchBase: z.string(), @@ -172,6 +176,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { url: z.string().trim(), bindDN: z.string().trim(), bindPass: z.string().trim(), + uniqueUserAttribute: z.string().trim().default("uidNumber"), searchBase: z.string().trim(), searchFilter: z.string().trim().default("(uid={{username}})"), groupSearchBase: z.string().trim(), @@ -213,6 +218,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { url: z.string().trim(), bindDN: z.string().trim(), bindPass: z.string().trim(), + uniqueUserAttribute: z.string().trim(), searchBase: z.string().trim(), searchFilter: z.string().trim(), groupSearchBase: z.string().trim(), 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 63565d94c..62791d7a4 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -122,6 +122,7 @@ export const ldapConfigServiceFactory = ({ url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -200,6 +201,7 @@ export const ldapConfigServiceFactory = ({ encryptedBindPass, bindPassIV, bindPassTag, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -222,6 +224,7 @@ export const ldapConfigServiceFactory = ({ url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -244,7 +247,8 @@ export const ldapConfigServiceFactory = ({ searchBase, searchFilter, groupSearchBase, - groupSearchFilter + groupSearchFilter, + uniqueUserAttribute }; const orgBot = await orgBotDAL.findOne({ orgId }); @@ -345,6 +349,7 @@ export const ldapConfigServiceFactory = ({ url: ldapConfig.url, bindDN, bindPass, + uniqueUserAttribute: ldapConfig.uniqueUserAttribute, searchBase: ldapConfig.searchBase, searchFilter: ldapConfig.searchFilter, groupSearchBase: ldapConfig.groupSearchBase, @@ -381,6 +386,7 @@ export const ldapConfigServiceFactory = ({ url: ldapConfig.url, bindDN: ldapConfig.bindDN, bindCredentials: ldapConfig.bindPass, + uniqueUserAttribute: ldapConfig.uniqueUserAttribute, searchBase: ldapConfig.searchBase, searchFilter: ldapConfig.searchFilter || "(uid={{username}})", // searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"], 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 aa4aa8da7..86f4bf0d5 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-types.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-types.ts @@ -7,6 +7,7 @@ export type TLDAPConfig = { url: string; bindDN: string; bindPass: string; + uniqueUserAttribute: string; searchBase: string; groupSearchBase: string; groupSearchFilter: string; @@ -19,6 +20,7 @@ export type TCreateLdapCfgDTO = { url: string; bindDN: string; bindPass: string; + uniqueUserAttribute: string; searchBase: string; searchFilter: string; groupSearchBase: string; @@ -33,6 +35,7 @@ export type TUpdateLdapCfgDTO = { url: string; bindDN: string; bindPass: string; + uniqueUserAttribute: string; searchBase: string; searchFilter: string; groupSearchBase: string; diff --git a/frontend/src/hooks/api/ldapConfig/mutations.tsx b/frontend/src/hooks/api/ldapConfig/mutations.tsx index a93286cdb..a14838842 100644 --- a/frontend/src/hooks/api/ldapConfig/mutations.tsx +++ b/frontend/src/hooks/api/ldapConfig/mutations.tsx @@ -13,6 +13,7 @@ export const useCreateLDAPConfig = () => { url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -24,6 +25,7 @@ export const useCreateLDAPConfig = () => { url: string; bindDN: string; bindPass: string; + uniqueUserAttribute: string; searchBase: string; searchFilter: string; groupSearchBase: string; @@ -36,6 +38,7 @@ export const useCreateLDAPConfig = () => { url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -60,6 +63,7 @@ export const useUpdateLDAPConfig = () => { url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -71,6 +75,7 @@ export const useUpdateLDAPConfig = () => { url?: string; bindDN?: string; bindPass?: string; + uniqueUserAttribute: string; searchBase?: string; searchFilter?: string; groupSearchBase?: string; @@ -83,6 +88,7 @@ export const useUpdateLDAPConfig = () => { url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx index 8a8a9e467..e8afe9822 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx @@ -20,6 +20,7 @@ const LDAPFormSchema = z.object({ bindPass: z.string().default(""), searchBase: z.string().default(""), searchFilter: z.string().default(""), + uniqueUserAttribute: z.string().default(""), groupSearchBase: z.string().default(""), groupSearchFilter: z.string().default(""), caCert: z.string().optional() @@ -53,6 +54,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) const watchGroupSearchBase = watch("groupSearchBase"); const watchGroupSearchFilter = watch("groupSearchFilter"); const watchCaCert = watch("caCert"); + const watchUniqueUserAttribute = watch("uniqueUserAttribute"); useEffect(() => { if (data) { @@ -64,7 +66,8 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) searchFilter: data?.searchFilter ?? "", groupSearchBase: data?.groupSearchBase ?? "", groupSearchFilter: data?.groupSearchFilter ?? "", - caCert: data?.caCert ?? "" + caCert: data?.caCert ?? "", + uniqueUserAttribute: data?.uniqueUserAttribute ?? "" }); } }, [data]); @@ -73,6 +76,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) url, bindDN, bindPass, + uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, @@ -92,6 +96,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) bindPass, searchBase, searchFilter, + uniqueUserAttribute, groupSearchBase, groupSearchFilter, caCert @@ -105,6 +110,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) bindPass, searchBase, searchFilter, + uniqueUserAttribute, groupSearchBase, groupSearchFilter, caCert @@ -138,6 +144,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) searchFilter: watchSearchFilter, groupSearchBase: watchGroupSearchBase, groupSearchFilter: watchGroupSearchFilter, + uniqueUserAttribute: watchUniqueUserAttribute, caCert: watchCaCert, shouldCloseModal: false }); @@ -217,6 +224,19 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) )} /> + ( + + + + )} + />