diff --git a/backend/package-lock.json b/backend/package-lock.json index 98b15e5f51..b51573688e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -45,6 +45,7 @@ "jsonwebtoken": "^9.0.2", "jsrp": "^0.2.4", "knex": "^3.0.1", + "ldapjs": "^3.0.7", "libsodium-wrappers": "^0.7.13", "lodash.isequal": "^4.5.0", "ms": "^2.1.3", @@ -2510,6 +2511,83 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@ldapjs/asn1": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz", + "integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA==" + }, + "node_modules/@ldapjs/attribute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz", + "integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.1.0" + } + }, + "node_modules/@ldapjs/change": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz", + "integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/attribute": "1.0.0" + } + }, + "node_modules/@ldapjs/controls": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz", + "integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==", + "dependencies": { + "@ldapjs/asn1": "^1.2.0", + "@ldapjs/protocol": "^1.2.1" + } + }, + "node_modules/@ldapjs/controls/node_modules/@ldapjs/asn1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz", + "integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw==" + }, + "node_modules/@ldapjs/dn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz", + "integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "process-warning": "^2.1.0" + } + }, + "node_modules/@ldapjs/filter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz", + "integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==", + "dependencies": { + "@ldapjs/asn1": "2.0.0", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.1.0" + } + }, + "node_modules/@ldapjs/messages": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz", + "integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==", + "dependencies": { + "@ldapjs/asn1": "^2.0.0", + "@ldapjs/attribute": "^1.0.0", + "@ldapjs/change": "^1.0.0", + "@ldapjs/controls": "^2.1.0", + "@ldapjs/dn": "^1.1.0", + "@ldapjs/filter": "^2.1.1", + "@ldapjs/protocol": "^1.2.1", + "process-warning": "^2.2.0" + } + }, + "node_modules/@ldapjs/protocol": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz", + "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==" + }, "node_modules/@lukeed/ms": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz", @@ -9304,15 +9382,7 @@ "node": ">=0.8.0" } }, - "node_modules/ldapauth-fork/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/ldapjs": { + "node_modules/ldapauth-fork/node_modules/ldapjs": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz", "integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==", @@ -9330,6 +9400,35 @@ "node": ">=10.13.0" } }, + "node_modules/ldapauth-fork/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ldapjs": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz", + "integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==", + "dependencies": { + "@ldapjs/asn1": "^2.0.0", + "@ldapjs/attribute": "^1.0.0", + "@ldapjs/change": "^1.0.0", + "@ldapjs/controls": "^2.1.0", + "@ldapjs/dn": "^1.1.0", + "@ldapjs/filter": "^2.1.1", + "@ldapjs/messages": "^1.3.0", + "@ldapjs/protocol": "^1.2.1", + "abstract-logging": "^2.0.1", + "assert-plus": "^1.0.0", + "backoff": "^2.5.0", + "once": "^1.4.0", + "vasync": "^2.2.1", + "verror": "^1.10.1" + } + }, "node_modules/leven": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", diff --git a/backend/package.json b/backend/package.json index 0f7b5a5907..31b9fdb145 100644 --- a/backend/package.json +++ b/backend/package.json @@ -106,6 +106,7 @@ "jsonwebtoken": "^9.0.2", "jsrp": "^0.2.4", "knex": "^3.0.1", + "ldapjs": "^3.0.7", "libsodium-wrappers": "^0.7.13", "lodash.isequal": "^4.5.0", "ms": "^2.1.3", diff --git a/backend/src/db/migrations/20240423023203_ldap-config-groups.ts b/backend/src/db/migrations/20240423023203_ldap-config-groups.ts new file mode 100644 index 0000000000..732736930e --- /dev/null +++ b/backend/src/db/migrations/20240423023203_ldap-config-groups.ts @@ -0,0 +1,17 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + t.string("groupSearchBase").notNullable().defaultTo(""); + t.string("groupSearchFilter").notNullable().defaultTo(""); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + t.dropColumn("groupSearchBase"); + t.dropColumn("groupSearchFilter"); + }); +} diff --git a/backend/src/db/schemas/ldap-configs.ts b/backend/src/db/schemas/ldap-configs.ts index e3c6c8c75a..70394a65c9 100644 --- a/backend/src/db/schemas/ldap-configs.ts +++ b/backend/src/db/schemas/ldap-configs.ts @@ -23,7 +23,9 @@ export const LdapConfigsSchema = z.object({ caCertIV: z.string(), caCertTag: z.string(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + groupSearchBase: z.string().default(""), + groupSearchFilter: 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 c35d275ae5..0c531146f2 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -11,10 +11,12 @@ import { IncomingMessage } from "node:http"; import { Authenticator } from "@fastify/passport"; import fastifySession from "@fastify/session"; import { FastifyRequest } from "fastify"; +import ldapjs from "ldapjs"; import LdapStrategy from "passport-ldapauth"; import { z } from "zod"; import { LdapConfigsSchema } from "@app/db/schemas"; +import { searchGroups } from "@app/ee/services/ldap-config/ldap-fns"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; @@ -44,26 +46,70 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { }); }; + interface LDAPConfig { + id: string; + organization: string; + isActive: boolean; + url: string; + bindDN: string; + bindPass: string; + searchBase: string; + groupSearchBase: string; + groupSearchFilter: string; + caCert: string; + } + passport.use( new LdapStrategy( getLdapPassportOpts as any, // eslint-disable-next-line async (req: IncomingMessage, user, cb) => { try { - const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({ - externalId: user.uidNumber, - username: user.uid, - firstName: user.givenName, - lastName: user.sn, - emails: user.mail ? [user.mail] : [], - relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState, - orgId: (req as unknown as FastifyRequest).ldapConfig.organization + const ldapConfig = (req as unknown as FastifyRequest).ldapConfig as LDAPConfig; + + const ldapClient = ldapjs.createClient({ + url: ldapConfig.url, + bindDN: ldapConfig.bindDN, + bindCredentials: ldapConfig.bindPass }); - return cb(null, { isUserCompleted, providerAuthToken }); - } catch (err) { - logger.error(err); - return cb(err, false); + ldapClient.bind(ldapConfig.bindDN, ldapConfig.bindPass, (err) => { + if (err) { + ldapClient.unbind(); + return cb(err); + } + + const groupFilter = + ldapConfig.groupSearchFilter || + "(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))"; + const searchFilter = groupFilter.replace("{{.Username}}", user.uid).replace("{{.UserDN}}", user.dn); + + searchGroups(ldapClient, searchFilter, ldapConfig.groupSearchBase) + .then(() => { + // groups here + ldapClient.unbind(); + return server.services.ldap.ldapLogin({ + externalId: user.uidNumber, + username: user.uid, + firstName: user.givenName, + lastName: user.sn, + emails: user.mail ? [user.mail] : [], + relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState, + orgId: (req as unknown as FastifyRequest).ldapConfig.organization + }); + }) + .then(({ isUserCompleted, providerAuthToken }) => { + cb(null, { isUserCompleted, providerAuthToken }); + }) + .catch((err2) => { + ldapClient.unbind(); + logger.error(err); + cb(err2, false); + }); + }); + } catch (error) { + logger.error(error); + return cb(error, false); } } ) @@ -117,6 +163,8 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { bindDN: z.string(), bindPass: z.string(), searchBase: z.string(), + groupSearchBase: z.string(), + groupSearchFilter: z.string(), caCert: z.string() }) } @@ -148,6 +196,8 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { bindDN: z.string().trim(), bindPass: z.string().trim(), searchBase: z.string().trim(), + groupSearchBase: z.string().trim(), + groupSearchFilter: z.string().trim(), caCert: z.string().trim().default("") }), response: { @@ -183,6 +233,8 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { bindDN: z.string().trim(), bindPass: z.string().trim(), searchBase: z.string().trim(), + groupSearchBase: z.string().trim(), + groupSearchFilter: z.string().trim(), caCert: z.string().trim() }) .partial() 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 76e2d40ceb..53631d9855 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -60,6 +60,8 @@ export const ldapConfigServiceFactory = ({ bindDN, bindPass, searchBase, + groupSearchBase, + groupSearchFilter, caCert }: TCreateLdapCfgDTO) => { const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); @@ -135,6 +137,8 @@ export const ldapConfigServiceFactory = ({ bindPassIV, bindPassTag, searchBase, + groupSearchBase, + groupSearchFilter, encryptedCACert, caCertIV, caCertTag @@ -154,6 +158,8 @@ export const ldapConfigServiceFactory = ({ bindDN, bindPass, searchBase, + groupSearchBase, + groupSearchFilter, caCert }: TUpdateLdapCfgDTO) => { const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); @@ -169,7 +175,9 @@ export const ldapConfigServiceFactory = ({ const updateQuery: TLdapConfigsUpdate = { isActive, url, - searchBase + searchBase, + groupSearchBase, + groupSearchFilter }; const orgBot = await orgBotDAL.findOne({ orgId }); @@ -271,6 +279,8 @@ export const ldapConfigServiceFactory = ({ bindDN, bindPass, searchBase: ldapConfig.searchBase, + groupSearchBase: ldapConfig.groupSearchBase, + groupSearchFilter: ldapConfig.groupSearchFilter, caCert }; }; @@ -305,7 +315,7 @@ export const ldapConfigServiceFactory = ({ bindCredentials: ldapConfig.bindPass, searchBase: ldapConfig.searchBase, searchFilter: "(uid={{username}})", - searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"], + // searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"], ...(ldapConfig.caCert !== "" ? { tlsOptions: { 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 4e261f9e99..7cadb489c8 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,8 @@ export type TCreateLdapCfgDTO = { bindDN: string; bindPass: string; searchBase: string; + groupSearchBase: string; + groupSearchFilter: string; caCert: string; } & TOrgPermission; @@ -18,6 +20,8 @@ export type TUpdateLdapCfgDTO = { bindDN: string; bindPass: string; searchBase: string; + groupSearchBase: string; + groupSearchFilter: string; caCert: string; }> & TOrgPermission; diff --git a/backend/src/ee/services/ldap-config/ldap-fns.ts b/backend/src/ee/services/ldap-config/ldap-fns.ts new file mode 100644 index 0000000000..748addea20 --- /dev/null +++ b/backend/src/ee/services/ldap-config/ldap-fns.ts @@ -0,0 +1,36 @@ +import ldap from "ldapjs"; + +export const searchGroups = async ( + ldapClient: ldap.Client, + filter: string, + base: string +): Promise<{ dn: string; cn: string }[]> => { + return new Promise((resolve, reject) => { + ldapClient.search( + base, + { + filter, + scope: "sub" + }, + (err, res) => { + if (err) { + reject(err); + } + + const groups: { dn: string; cn: string }[] = []; + + res.on("searchEntry", (entry) => { + groups.push({ dn: entry.object.dn, cn: entry.object.cn as string }); + }); + + res.on("error", (error) => { + reject(error); + }); + + res.on("end", () => { + resolve(groups); + }); + } + ); + }); +}; diff --git a/backend/src/ee/services/license/__mocks__/licence-fns.ts b/backend/src/ee/services/license/__mocks__/licence-fns.ts index b5cbf103ee..6596a98573 100644 --- a/backend/src/ee/services/license/__mocks__/licence-fns.ts +++ b/backend/src/ee/services/license/__mocks__/licence-fns.ts @@ -19,7 +19,7 @@ export const getDefaultOnPremFeatures = () => { auditLogsRetentionDays: 0, samlSSO: false, scim: false, - ldap: false, + ldap: true, groups: false, status: null, trial_end: null, diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 8a4de57f1e..e9975e7d43 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -26,7 +26,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ auditLogsRetentionDays: 0, samlSSO: false, scim: false, - ldap: false, + ldap: true, groups: false, status: null, trial_end: null, diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 1cea39a834..3e6fef8e61 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -42,7 +42,7 @@ export type TFeatureSet = { auditLogsRetentionDays: 0; samlSSO: false; scim: false; - ldap: false; + ldap: true; groups: false; status: null; trial_end: null; diff --git a/frontend/src/hooks/api/ldapConfig/index.tsx b/frontend/src/hooks/api/ldapConfig/index.tsx index bc93e7fcfd..9d81189196 100644 --- a/frontend/src/hooks/api/ldapConfig/index.tsx +++ b/frontend/src/hooks/api/ldapConfig/index.tsx @@ -1 +1,2 @@ -export { useCreateLDAPConfig, useGetLDAPConfig, useUpdateLDAPConfig } from "./queries"; +export { useCreateLDAPConfig, useUpdateLDAPConfig } from "./mutations"; +export { useGetLDAPConfig } from "./queries"; diff --git a/frontend/src/hooks/api/ldapConfig/mutations.tsx b/frontend/src/hooks/api/ldapConfig/mutations.tsx new file mode 100644 index 0000000000..be24f2e84c --- /dev/null +++ b/frontend/src/hooks/api/ldapConfig/mutations.tsx @@ -0,0 +1,93 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { ldapConfigKeys } from "./queries"; + +export const useCreateLDAPConfig = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + organizationId, + isActive, + url, + bindDN, + bindPass, + searchBase, + groupSearchBase, + groupSearchFilter, + caCert + }: { + organizationId: string; + isActive: boolean; + url: string; + bindDN: string; + bindPass: string; + searchBase: string; + groupSearchBase: string; + groupSearchFilter: string; + caCert?: string; + }) => { + const { data } = await apiRequest.post("/api/v1/ldap/config", { + organizationId, + isActive, + url, + bindDN, + bindPass, + searchBase, + groupSearchBase, + groupSearchFilter, + caCert + }); + + return data; + }, + onSuccess(_, dto) { + queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId)); + } + }); +}; + +export const useUpdateLDAPConfig = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + organizationId, + isActive, + url, + bindDN, + bindPass, + searchBase, + groupSearchBase, + groupSearchFilter, + caCert + }: { + organizationId: string; + isActive?: boolean; + url?: string; + bindDN?: string; + bindPass?: string; + searchBase?: string; + groupSearchBase?: string; + groupSearchFilter?: string; + caCert?: string; + }) => { + const { data } = await apiRequest.patch("/api/v1/ldap/config", { + organizationId, + isActive, + url, + bindDN, + bindPass, + searchBase, + groupSearchBase, + groupSearchFilter, + caCert + }); + + return data; + }, + onSuccess(_, dto) { + queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId)); + } + }); +}; diff --git a/frontend/src/hooks/api/ldapConfig/queries.tsx b/frontend/src/hooks/api/ldapConfig/queries.tsx index 4c97c5fc69..bc22c62edb 100644 --- a/frontend/src/hooks/api/ldapConfig/queries.tsx +++ b/frontend/src/hooks/api/ldapConfig/queries.tsx @@ -1,8 +1,8 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -const ldapConfigKeys = { +export const ldapConfigKeys = { getLDAPConfig: (orgId: string) => [{ orgId }, "organization-ldap"] as const }; @@ -17,79 +17,3 @@ export const useGetLDAPConfig = (organizationId: string) => { enabled: true }); }; - -export const useCreateLDAPConfig = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ - organizationId, - isActive, - url, - bindDN, - bindPass, - searchBase, - caCert - }: { - organizationId: string; - isActive: boolean; - url: string; - bindDN: string; - bindPass: string; - searchBase: string; - caCert?: string; - }) => { - const { data } = await apiRequest.post("/api/v1/ldap/config", { - organizationId, - isActive, - url, - bindDN, - bindPass, - searchBase, - caCert - }); - - return data; - }, - onSuccess(_, dto) { - queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId)); - } - }); -}; - -export const useUpdateLDAPConfig = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ - organizationId, - isActive, - url, - bindDN, - bindPass, - searchBase, - caCert - }: { - organizationId: string; - isActive?: boolean; - url?: string; - bindDN?: string; - bindPass?: string; - searchBase?: string; - caCert?: string; - }) => { - const { data } = await apiRequest.patch("/api/v1/ldap/config", { - organizationId, - isActive, - url, - bindDN, - bindPass, - searchBase, - caCert - }); - - return data; - }, - onSuccess(_, dto) { - queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId)); - } - }); -}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx index 3734d685d6..773cba3f43 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx @@ -14,6 +14,8 @@ const LDAPFormSchema = z.object({ bindDN: z.string().default(""), bindPass: z.string().default(""), searchBase: z.string().default(""), + groupSearchBase: z.string().default(""), + groupSearchFilter: z.string().default(""), caCert: z.string().optional() }); @@ -27,7 +29,7 @@ type Props = { export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { const { currentOrg } = useOrganization(); - + const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig(); const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateLDAPConfig(); const { data } = useGetLDAPConfig(currentOrg?.id ?? ""); @@ -43,12 +45,22 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) bindDN: data?.bindDN ?? "", bindPass: data?.bindPass ?? "", searchBase: data?.searchBase ?? "", + groupSearchBase: data?.groupSearchBase ?? "", + groupSearchFilter: data?.groupSearchFilter ?? "", caCert: data?.caCert ?? "" }); } }, [data]); - const onSSOModalSubmit = async ({ url, bindDN, bindPass, searchBase, caCert }: TLDAPFormData) => { + const onSSOModalSubmit = async ({ + url, + bindDN, + bindPass, + searchBase, + groupSearchBase, + groupSearchFilter, + caCert + }: TLDAPFormData) => { try { if (!currentOrg) return; @@ -60,6 +72,8 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) bindDN, bindPass, searchBase, + groupSearchBase, + groupSearchFilter, caCert }); } else { @@ -70,6 +84,8 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) bindDN, bindPass, searchBase, + groupSearchBase, + groupSearchFilter, caCert }); } @@ -139,6 +155,32 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) )} /> + ( + + + + )} + /> + ( + + + + )} + /> { const { currentOrg } = useOrganization(); const { subscription } = useSubscription(); - + const { data } = useGetLDAPConfig(currentOrg?.id ?? ""); + const { mutateAsync } = useUpdateLDAPConfig(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addLDAP", @@ -63,7 +64,9 @@ export const OrgLDAPSection = (): JSX.Element => { url: "", bindDN: "", bindPass: "", - searchBase: "" + searchBase: "", + groupSearchBase: "", + groupSearchFilter: "" }); }