From a3b7df4e6b0ddd7176d5bd23d0f6717e8cf75cd7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 8 May 2025 23:13:46 +0400 Subject: [PATCH] fix: addressed requested changes --- backend/src/@types/fastify.d.ts | 2 +- .../20250507003056_identity-ldap-auth.ts | 1 - backend/src/db/schemas/identity-ldap-auths.ts | 1 - .../ee/services/audit-log/audit-log-types.ts | 2 +- backend/src/lib/api-docs/constants.ts | 43 +++++ .../routes/v1/identity-ldap-auth-router.ts | 93 ++++++----- .../identity-ldap-auth-service.ts | 8 +- .../identity-ldap-auth-types.ts | 2 - .../src/hooks/api/identities/mutations.tsx | 4 - frontend/src/hooks/api/identities/types.ts | 3 - .../IdentitySection/IdentityLdapAuthForm.tsx | 150 ++++++++---------- .../ViewIdentityLdapAuthContent.tsx | 3 - 12 files changed, 166 insertions(+), 146 deletions(-) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 74571e2d0..748a7d431 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -152,7 +152,7 @@ declare module "fastify" { identityId: string; user: { uid: string; - mail: string; + mail?: string; }; }; kmipUser: { diff --git a/backend/src/db/migrations/20250507003056_identity-ldap-auth.ts b/backend/src/db/migrations/20250507003056_identity-ldap-auth.ts index db7ba5281..da9912022 100644 --- a/backend/src/db/migrations/20250507003056_identity-ldap-auth.ts +++ b/backend/src/db/migrations/20250507003056_identity-ldap-auth.ts @@ -23,7 +23,6 @@ export async function up(knex: Knex): Promise { t.string("url").notNullable(); t.string("searchBase").notNullable(); t.string("searchFilter").notNullable(); - t.string("uniqueAttribute").notNullable(); t.jsonb("allowedFields").nullable(); diff --git a/backend/src/db/schemas/identity-ldap-auths.ts b/backend/src/db/schemas/identity-ldap-auths.ts index e843648bc..d5b15fc6a 100644 --- a/backend/src/db/schemas/identity-ldap-auths.ts +++ b/backend/src/db/schemas/identity-ldap-auths.ts @@ -22,7 +22,6 @@ export const IdentityLdapAuthsSchema = z.object({ url: z.string(), searchBase: z.string(), searchFilter: z.string(), - uniqueAttribute: z.string(), allowedFields: z.unknown().nullable().optional(), createdAt: z.date(), updatedAt: z.date() diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 92f4d54a7..a2caf2bff 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -1056,7 +1056,7 @@ interface LoginIdentityLdapAuthEvent { metadata: { identityId: string; ldapUsername: string; - ldapEmail: string; + ldapEmail?: string; }; } diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 62ba2c974..7e82fe6f1 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -185,6 +185,49 @@ export const UNIVERSAL_AUTH = { } } as const; +export const LDAP_AUTH = { + LOGIN: { + identityId: "The ID of the identity to login.", + username: "The username of the LDAP user to login.", + password: "The password of the LDAP user to login." + }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + url: "The URL of the LDAP server.", + allowedFields: + "The comma-separated array of key/value pairs of required fields that the LDAP entry must have in order to authenticate.", + searchBase: "The base DN to search for the LDAP user.", + searchFilter: "The filter to use to search for the LDAP user.", + bindDN: "The DN of the user to bind to the LDAP server.", + bindPass: "The password of the user to bind to the LDAP server.", + ldapCaCertificate: "The PEM-encoded CA certificate for the LDAP server.", + accessTokenTTL: "The lifetime for an access token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from." + }, + UPDATE: { + identityId: "The ID of the identity to update the configuration for.", + url: "The new URL of the LDAP server.", + allowedFields: "The comma-separated list of allowed fields to return from the LDAP user.", + searchBase: "The new base DN to search for the LDAP user.", + searchFilter: "The new filter to use to search for the LDAP user.", + bindDN: "The new DN of the user to bind to the LDAP server.", + bindPass: "The new password of the user to bind to the LDAP server.", + ldapCaCertificate: "The new PEM-encoded CA certificate for the LDAP server.", + accessTokenTTL: "The new lifetime for an access token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve the configuration for." + }, + REVOKE: { + identityId: "The ID of the identity to revoke the configuration for." + } +} as const; + export const AWS_AUTH = { LOGIN: { identityId: "The ID of the identity to login.", diff --git a/backend/src/server/routes/v1/identity-ldap-auth-router.ts b/backend/src/server/routes/v1/identity-ldap-auth-router.ts index bfc195f0a..ec3b64592 100644 --- a/backend/src/server/routes/v1/identity-ldap-auth-router.ts +++ b/backend/src/server/routes/v1/identity-ldap-auth-router.ts @@ -15,7 +15,7 @@ import { z } from "zod"; import { IdentityLdapAuthsSchema } from "@app/db/schemas/identity-ldap-auths"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; -import { ApiDocsTags } from "@app/lib/api-docs"; +import { ApiDocsTags, LDAP_AUTH } from "@app/lib/api-docs"; import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -45,6 +45,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) ...ldapConfig, isActive: true, groupSearchBase: "", + uniqueUserAttribute: "", groupSearchFilter: "" }; @@ -120,9 +121,9 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) tags: [ApiDocsTags.LdapAuth], description: "Login with LDAP Auth", body: z.object({ - identityId: z.string().trim(), - username: z.string(), - password: z.string() + identityId: z.string().trim().describe(LDAP_AUTH.LOGIN.identityId), + username: z.string().describe(LDAP_AUTH.LOGIN.username), + password: z.string().describe(LDAP_AUTH.LOGIN.password) }), response: { 200: z.object({ @@ -147,11 +148,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) }, handler: async (req) => { - if ( - !req.passportMachineIdentity?.identityId || - !req.passportMachineIdentity.user.mail || - !req.passportMachineIdentity.user.uid - ) { + if (!req.passportMachineIdentity?.identityId || !req.passportMachineIdentity.user.uid) { throw new UnauthorizedError({ message: "Invalid request. Missing identity ID or LDAP entry details." }); } @@ -200,29 +197,40 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(LDAP_AUTH.ATTACH.identityId) }), body: z .object({ - url: z.string().trim().min(1), - bindDN: z.string().trim().min(1), - bindPass: z.string().trim().min(1), - searchBase: z.string().trim().min(1), - uniqueAttribute: z.string().trim().min(1).default("uidNumber"), - searchFilter: z.string().trim().min(1).default("(uid={{username}})"), - allowedFields: AllowedFieldsSchema.array().optional(), - ldapCaCertificate: z.string().trim().optional(), - + url: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.url), + bindDN: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindDN), + bindPass: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindPass), + searchBase: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.searchBase), + searchFilter: z.string().trim().min(1).default("(uid={{username}})").describe(LDAP_AUTH.ATTACH.searchFilter), + allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields), + ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]), - accessTokenTTL: z.number().int().min(0).max(315360000).default(2592000), - accessTokenMaxTTL: z.number().int().min(1).max(315360000).default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0) + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps), + accessTokenTTL: z + .number() + .int() + .min(0) + .max(315360000) + .default(2592000) + .describe(LDAP_AUTH.ATTACH.accessTokenTTL), + accessTokenMaxTTL: z + .number() + .int() + .min(1) + .max(315360000) + .default(2592000) + .describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit) }) .refine( (val) => val.accessTokenTTL <= val.accessTokenMaxTTL, @@ -286,27 +294,38 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().trim().describe(LDAP_AUTH.UPDATE.identityId) }), body: z .object({ - url: z.string().trim().min(1), - bindDN: z.string().trim().min(1), - bindPass: z.string().trim().min(1), - searchBase: z.string().trim().min(1), - uniqueAttribute: z.string().trim().min(1).default("uidNumber"), - searchFilter: z.string().trim().min(1).default("(uid={{username}})"), - allowedFields: AllowedFieldsSchema.array().optional(), + url: z.string().trim().min(1).describe(LDAP_AUTH.UPDATE.url), + bindDN: z.string().trim().min(1).describe(LDAP_AUTH.UPDATE.bindDN), + bindPass: z.string().trim().min(1).describe(LDAP_AUTH.UPDATE.bindPass), + searchBase: z.string().trim().min(1).describe(LDAP_AUTH.UPDATE.searchBase), + searchFilter: z.string().trim().min(1).default("(uid={{username}})").describe(LDAP_AUTH.UPDATE.searchFilter), + allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.UPDATE.allowedFields), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .optional(), - accessTokenTTL: z.number().int().min(0).max(315360000).optional(), - accessTokenNumUsesLimit: z.number().int().min(0).optional(), - accessTokenMaxTTL: z.number().int().max(315360000).min(0).optional() + .optional() + .describe(LDAP_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(LDAP_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z + .number() + .int() + .min(0) + .optional() + .describe(LDAP_AUTH.UPDATE.accessTokenNumUsesLimit), + accessTokenMaxTTL: z + .number() + .int() + .max(315360000) + .min(0) + .optional() + .describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL) }) .refine( (val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true), @@ -370,7 +389,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().trim().describe(LDAP_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ @@ -427,7 +446,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().trim().describe(LDAP_AUTH.REVOKE.identityId) }), response: { 200: z.object({ diff --git a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts index 86343e3e2..7462c9228 100644 --- a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts +++ b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts @@ -88,7 +88,6 @@ export const identityLdapAuthServiceFactory = ({ url: ldapAuth.url, bindDN, bindPass, - uniqueUserAttribute: ldapAuth.uniqueAttribute, searchBase: ldapAuth.searchBase, searchFilter: ldapAuth.searchFilter, caCert: ldapCaCertificate || "", @@ -100,9 +99,8 @@ export const identityLdapAuthServiceFactory = ({ url: ldapAuth.url, bindDN, bindCredentials: bindPass, - uniqueUserAttribute: ldapAuth.uniqueAttribute, searchBase: ldapAuth.searchBase, - searchFilter: ldapAuth.searchFilter || "(uid={{username}})", + searchFilter: ldapAuth.searchFilter, ...(ldapCaCertificate ? { tlsOptions: { @@ -178,7 +176,6 @@ export const identityLdapAuthServiceFactory = ({ url, searchBase, searchFilter, - uniqueAttribute, bindDN, bindPass, ldapCaCertificate, @@ -287,7 +284,6 @@ export const identityLdapAuthServiceFactory = ({ encryptedBindPass, searchBase, searchFilter, - uniqueAttribute, url, encryptedLdapCaCertificate, accessTokenMaxTTL, @@ -308,7 +304,6 @@ export const identityLdapAuthServiceFactory = ({ url, searchBase, searchFilter, - uniqueAttribute, bindDN, bindPass, ldapCaCertificate, @@ -428,7 +423,6 @@ export const identityLdapAuthServiceFactory = ({ url, searchBase, searchFilter, - uniqueAttribute, encryptedBindDN, encryptedBindPass, encryptedLdapCaCertificate, diff --git a/backend/src/services/identity-ldap-auth/identity-ldap-auth-types.ts b/backend/src/services/identity-ldap-auth/identity-ldap-auth-types.ts index cba6acbcb..0e6feb5fb 100644 --- a/backend/src/services/identity-ldap-auth/identity-ldap-auth-types.ts +++ b/backend/src/services/identity-ldap-auth/identity-ldap-auth-types.ts @@ -17,7 +17,6 @@ export type TAttachLdapAuthDTO = { url: string; searchBase: string; searchFilter: string; - uniqueAttribute: string; bindDN: string; bindPass: string; ldapCaCertificate?: string; @@ -34,7 +33,6 @@ export type TUpdateLdapAuthDTO = { url?: string; searchBase?: string; searchFilter?: string; - uniqueAttribute?: string; bindDN?: string; bindPass?: string; allowedFields?: TAllowedFields[]; diff --git a/frontend/src/hooks/api/identities/mutations.tsx b/frontend/src/hooks/api/identities/mutations.tsx index e19ec7458..e0077527f 100644 --- a/frontend/src/hooks/api/identities/mutations.tsx +++ b/frontend/src/hooks/api/identities/mutations.tsx @@ -1064,7 +1064,6 @@ export const useAddIdentityLdapAuth = () => { bindPass, searchBase, searchFilter, - uniqueAttribute, ldapCaCertificate, allowedFields, accessTokenTTL, @@ -1080,7 +1079,6 @@ export const useAddIdentityLdapAuth = () => { bindPass, searchBase, searchFilter, - uniqueAttribute, ldapCaCertificate, allowedFields, accessTokenTTL, @@ -1113,7 +1111,6 @@ export const useUpdateIdentityLdapAuth = () => { bindPass, searchBase, searchFilter, - uniqueAttribute, ldapCaCertificate, allowedFields, accessTokenTTL, @@ -1129,7 +1126,6 @@ export const useUpdateIdentityLdapAuth = () => { bindPass, searchBase, searchFilter, - uniqueAttribute, ldapCaCertificate, allowedFields, accessTokenTTL, diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index a97067e41..c5f8cbc4a 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -433,7 +433,6 @@ export type AddIdentityLdapAuthDTO = { bindPass: string; searchBase: string; searchFilter: string; - uniqueAttribute: string; ldapCaCertificate?: string; allowedFields?: { key: string; @@ -455,7 +454,6 @@ export type UpdateIdentityLdapAuthDTO = { bindPass?: string; searchBase?: string; searchFilter?: string; - uniqueAttribute?: string; ldapCaCertificate?: string; allowedFields?: { key: string; @@ -480,7 +478,6 @@ export type IdentityLdapAuth = { bindPass: string; searchBase: string; searchFilter: string; - uniqueAttribute: string; ldapCaCertificate?: string; allowedFields?: { key: string; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx index 94658e0b5..e2dbc8b28 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx @@ -35,7 +35,6 @@ const schema = z bindDN: z.string(), bindPass: z.string(), searchBase: z.string(), - uniqueAttribute: z.string(), // defaults to uidNumber searchFilter: z.string(), // defaults to (uid={{username}}) ldapCaCertificate: z .string() @@ -112,7 +111,6 @@ export const IdentityLdapAuthForm = ({ bindDN: "", bindPass: "", searchBase: "", - uniqueAttribute: "uidNumber", searchFilter: "(uid={{username}})", accessTokenTTL: "2592000", accessTokenMaxTTL: "2592000", @@ -140,7 +138,6 @@ export const IdentityLdapAuthForm = ({ bindDN: data.bindDN, bindPass: data.bindPass, searchBase: data.searchBase, - uniqueAttribute: data.uniqueAttribute, searchFilter: data.searchFilter, ldapCaCertificate: data.ldapCaCertificate || undefined, allowedFields: data.allowedFields, @@ -161,7 +158,6 @@ export const IdentityLdapAuthForm = ({ bindDN: "", bindPass: "", searchBase: "", - uniqueAttribute: "uidNumber", searchFilter: "(uid={{username}})", ldapCaCertificate: undefined, allowedFields: [], @@ -185,7 +181,6 @@ export const IdentityLdapAuthForm = ({ bindDN, bindPass, searchBase, - uniqueAttribute, searchFilter, ldapCaCertificate, allowedFields, @@ -206,7 +201,6 @@ export const IdentityLdapAuthForm = ({ bindPass, searchBase, searchFilter, - uniqueAttribute, ldapCaCertificate, allowedFields, accessTokenTTL: Number(accessTokenTTL), @@ -223,7 +217,6 @@ export const IdentityLdapAuthForm = ({ bindPass, searchBase, searchFilter, - uniqueAttribute, ldapCaCertificate, allowedFields, accessTokenTTL: Number(accessTokenTTL), @@ -259,8 +252,8 @@ export const IdentityLdapAuthForm = ({ "bindPass", "searchBase", "searchFilter", - "uniqueAttribute", "accessTokenTTL", + "allowedFields", "accessTokenMaxTTL", "accessTokenNumUsesLimit" ].includes(Object.keys(fields)[0]) @@ -334,21 +327,6 @@ export const IdentityLdapAuthForm = ({ )} /> - ( - - - - )} - /> - - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - - - ( - -