mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 06:28:11 +00:00
Merge pull request #4825 from Infisical/feature/ldap-gateway-support
feature: ldap gateway support
This commit is contained in:
+32
-31
@@ -10,7 +10,7 @@ import {
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { DistinguishedNameRegex } from "@app/lib/regex";
|
import { DistinguishedNameRegex } from "@app/lib/regex";
|
||||||
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
|
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
|
||||||
import { getLdapConnectionClient, LdapProvider, TLdapConnection } from "@app/services/app-connection/ldap";
|
import { executeWithPotentialGateway, LdapProvider, TLdapConnection } from "@app/services/app-connection/ldap";
|
||||||
|
|
||||||
import { generatePassword } from "../shared/utils";
|
import { generatePassword } from "../shared/utils";
|
||||||
import {
|
import {
|
||||||
@@ -71,17 +71,18 @@ export const ldapPasswordRotationFactory: TRotationFactory<
|
|||||||
TLdapPasswordRotationWithConnection,
|
TLdapPasswordRotationWithConnection,
|
||||||
TLdapPasswordRotationGeneratedCredentials,
|
TLdapPasswordRotationGeneratedCredentials,
|
||||||
TLdapPasswordRotationInput["temporaryParameters"]
|
TLdapPasswordRotationInput["temporaryParameters"]
|
||||||
> = (secretRotation, appConnectionDAL, kmsService) => {
|
> = (secretRotation, appConnectionDAL, kmsService, gatewayService, gatewayV2Service) => {
|
||||||
const { connection, parameters, secretsMapping, activeIndex } = secretRotation;
|
const { connection, parameters, secretsMapping, activeIndex } = secretRotation;
|
||||||
|
|
||||||
const { dn, passwordRequirements } = parameters;
|
const { dn, passwordRequirements } = parameters;
|
||||||
|
|
||||||
const $verifyCredentials = async (credentials: Pick<TLdapConnection["credentials"], "dn" | "password">) => {
|
const $verifyCredentials = async (credentials: Pick<TLdapConnection["credentials"], "dn" | "password">) => {
|
||||||
try {
|
try {
|
||||||
const client = await getLdapConnectionClient({ ...connection.credentials, ...credentials });
|
await executeWithPotentialGateway(
|
||||||
|
{ ...connection, credentials: { ...connection.credentials, ...credentials } },
|
||||||
client.unbind();
|
gatewayV2Service,
|
||||||
client.destroy();
|
async () => {}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to verify credentials - ${(error as Error).message}`);
|
throw new Error(`Failed to verify credentials - ${(error as Error).message}`);
|
||||||
}
|
}
|
||||||
@@ -92,17 +93,7 @@ export const ldapPasswordRotationFactory: TRotationFactory<
|
|||||||
|
|
||||||
if (!credentials.url.startsWith("ldaps")) throw new Error("Password Rotation requires an LDAPS connection");
|
if (!credentials.url.startsWith("ldaps")) throw new Error("Password Rotation requires an LDAPS connection");
|
||||||
|
|
||||||
const client = await getLdapConnectionClient(
|
|
||||||
currentPassword
|
|
||||||
? {
|
|
||||||
...credentials,
|
|
||||||
password: currentPassword,
|
|
||||||
dn
|
|
||||||
}
|
|
||||||
: credentials
|
|
||||||
);
|
|
||||||
const isConnectionRotation = credentials.dn === dn;
|
const isConnectionRotation = credentials.dn === dn;
|
||||||
|
|
||||||
const password = generatePassword(passwordRequirements);
|
const password = generatePassword(passwordRequirements);
|
||||||
|
|
||||||
let changes: ldap.Change[] | ldap.Change;
|
let changes: ldap.Change[] | ldap.Change;
|
||||||
@@ -147,22 +138,32 @@ export const ldapPasswordRotationFactory: TRotationFactory<
|
|||||||
throw new Error(`Unhandled provider: ${credentials.provider as LdapProvider}`);
|
throw new Error(`Unhandled provider: ${credentials.provider as LdapProvider}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await executeWithPotentialGateway(
|
||||||
const userDn = await getDN(dn, client);
|
{
|
||||||
await new Promise((resolve, reject) => {
|
...connection,
|
||||||
client.modify(userDn, changes, (err) => {
|
credentials: currentPassword
|
||||||
if (err) {
|
? {
|
||||||
logger.error(err, "LDAP Password Rotation Failed");
|
...credentials,
|
||||||
reject(new Error(`Provider Modify Error: ${err.message}`));
|
password: currentPassword,
|
||||||
} else {
|
dn
|
||||||
resolve(true);
|
}
|
||||||
}
|
: credentials
|
||||||
|
},
|
||||||
|
gatewayV2Service,
|
||||||
|
async (client) => {
|
||||||
|
const userDn = await getDN(dn, client);
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
client.modify(userDn, changes, (err) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err, "LDAP Password Rotation Failed");
|
||||||
|
reject(new Error(`Provider Modify Error: ${err.message}`));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
} finally {
|
);
|
||||||
client.unbind();
|
|
||||||
client.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
await $verifyCredentials({ dn, password });
|
await $verifyCredentials({ dn, password });
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { BadRequestError } from "../errors";
|
import { BadRequestError } from "../errors";
|
||||||
import { isPrivateIp } from "../ip/ipRange";
|
import { isPrivateIp } from "../ip/ipRange";
|
||||||
|
|
||||||
export const blockLocalAndPrivateIpAddresses = async (url: string) => {
|
export const blockLocalAndPrivateIpAddresses = async (url: string, isGateway = false) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
if (appCfg.isDevelopmentMode) return;
|
if (appCfg.isDevelopmentMode || isGateway) return;
|
||||||
|
|
||||||
const validUrl = new URL(url);
|
const validUrl = new URL(url);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import ldap from "ldapjs";
|
import ldap from "ldapjs";
|
||||||
|
|
||||||
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
|
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { GatewayProxyProtocol } from "@app/lib/gateway";
|
||||||
|
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
@@ -8,6 +13,66 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
|
|||||||
import { LdapConnectionMethod } from "./ldap-connection-enums";
|
import { LdapConnectionMethod } from "./ldap-connection-enums";
|
||||||
import { TLdapConnectionConfig } from "./ldap-connection-types";
|
import { TLdapConnectionConfig } from "./ldap-connection-types";
|
||||||
|
|
||||||
|
const LDAP_TIMEOUT = 15_000;
|
||||||
|
|
||||||
|
const parseLdapUrl = (url: string): { protocol: string; host: string; port: number } => {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
const isSSL = urlObj.protocol === "ldaps:";
|
||||||
|
const defaultPort = isSSL ? 636 : 389;
|
||||||
|
|
||||||
|
return {
|
||||||
|
protocol: urlObj.protocol.replace(":", ""),
|
||||||
|
host: urlObj.hostname,
|
||||||
|
port: urlObj.port ? parseInt(urlObj.port, 10) : defaultPort
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const constructLdapUrl = (protocol: string, host: string, port: number): string => {
|
||||||
|
return `${protocol}://${host}:${port}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupLdapClientHandlers = <T>(
|
||||||
|
client: ldap.Client,
|
||||||
|
dn: string,
|
||||||
|
password: string,
|
||||||
|
onSuccess: (client: ldap.Client) => T | Promise<T>
|
||||||
|
): Promise<T> => {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const handleError = (errorType: string, err: Error) => {
|
||||||
|
logger.error(err, errorType);
|
||||||
|
client.destroy();
|
||||||
|
reject(new Error(`${errorType.replace("LDAP ", "")} - ${err.message}`));
|
||||||
|
};
|
||||||
|
|
||||||
|
client.on("error", (err: Error) => handleError("LDAP Error", err));
|
||||||
|
client.on("connectError", (err: Error) => handleError("LDAP Connection Error", err));
|
||||||
|
client.on("connectRefused", (err: Error) => handleError("LDAP Connection Refused", err));
|
||||||
|
client.on("connectTimeout", (err: Error) => handleError("LDAP Connection Timeout", err));
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
client.bind(dn, password, (err) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err, "LDAP Bind Error");
|
||||||
|
client.destroy();
|
||||||
|
reject(new Error(`Bind Error: ${err.message}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = onSuccess(client);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.then((value) => resolve(value)).catch(reject);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const getLdapConnectionListItem = () => {
|
export const getLdapConnectionListItem = () => {
|
||||||
return {
|
return {
|
||||||
name: "LDAP" as const,
|
name: "LDAP" as const,
|
||||||
@@ -16,8 +81,6 @@ export const getLdapConnectionListItem = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const LDAP_TIMEOUT = 15_000;
|
|
||||||
|
|
||||||
export const getLdapConnectionClient = async ({
|
export const getLdapConnectionClient = async ({
|
||||||
url,
|
url,
|
||||||
dn,
|
dn,
|
||||||
@@ -25,78 +88,112 @@ export const getLdapConnectionClient = async ({
|
|||||||
sslCertificate,
|
sslCertificate,
|
||||||
sslRejectUnauthorized = true
|
sslRejectUnauthorized = true
|
||||||
}: TLdapConnectionConfig["credentials"]) => {
|
}: TLdapConnectionConfig["credentials"]) => {
|
||||||
await blockLocalAndPrivateIpAddresses(url);
|
await blockLocalAndPrivateIpAddresses(url, false);
|
||||||
|
|
||||||
const isSSL = url.startsWith("ldaps");
|
const isSSL = url.startsWith("ldaps");
|
||||||
|
|
||||||
return new Promise<ldap.Client>((resolve, reject) => {
|
const client = ldap.createClient({
|
||||||
const client = ldap.createClient({
|
url,
|
||||||
url,
|
timeout: LDAP_TIMEOUT,
|
||||||
timeout: LDAP_TIMEOUT,
|
connectTimeout: LDAP_TIMEOUT,
|
||||||
connectTimeout: LDAP_TIMEOUT,
|
tlsOptions: isSSL
|
||||||
tlsOptions: isSSL
|
? {
|
||||||
? {
|
rejectUnauthorized: sslRejectUnauthorized,
|
||||||
rejectUnauthorized: sslRejectUnauthorized,
|
ca: sslCertificate ? [sslCertificate] : undefined
|
||||||
ca: sslCertificate ? [sslCertificate] : undefined
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("error", (err: Error) => {
|
|
||||||
logger.error(err, "LDAP Error");
|
|
||||||
client.destroy();
|
|
||||||
reject(new Error(`Provider Error - ${err.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connectError", (err: Error) => {
|
|
||||||
logger.error(err, "LDAP Connection Error");
|
|
||||||
client.destroy();
|
|
||||||
reject(new Error(`Provider Connect Error - ${err.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connectRefused", (err: Error) => {
|
|
||||||
logger.error(err, "LDAP Connection Refused");
|
|
||||||
client.destroy();
|
|
||||||
reject(new Error(`Provider Connection Refused - ${err.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connectTimeout", (err: Error) => {
|
|
||||||
logger.error(err, "LDAP Connection Timeout");
|
|
||||||
client.destroy();
|
|
||||||
reject(new Error(`Provider Connection Timeout - ${err.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
client.bind(dn, password, (err) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error(err, "LDAP Bind Error");
|
|
||||||
reject(new Error(`Bind Error: ${err.message}`));
|
|
||||||
client.destroy();
|
|
||||||
}
|
}
|
||||||
|
: undefined
|
||||||
resolve(client);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return setupLdapClientHandlers<ldap.Client>(client, dn, password, (ldapClient) => ldapClient);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateLdapConnectionCredentials = async ({ credentials }: TLdapConnectionConfig) => {
|
export const executeWithPotentialGateway = async <T>(
|
||||||
let client: ldap.Client | undefined;
|
config: TLdapConnectionConfig,
|
||||||
|
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||||
|
operation: (client: ldap.Client) => Promise<T>
|
||||||
|
): Promise<T> => {
|
||||||
|
const { gatewayId, credentials } = config;
|
||||||
|
const { protocol, host, port } = parseLdapUrl(credentials.url);
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
try {
|
if (gatewayId && gatewayV2Service) {
|
||||||
client = await getLdapConnectionClient(credentials);
|
await blockLocalAndPrivateIpAddresses(credentials.url, true);
|
||||||
|
const platformConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({
|
||||||
|
gatewayId,
|
||||||
|
targetHost: host,
|
||||||
|
targetPort: port
|
||||||
|
});
|
||||||
|
|
||||||
// this shouldn't occur as handle connection error events in client but here as fallback
|
if (!platformConnectionDetails) {
|
||||||
if (!client.connected) {
|
throw new BadRequestError({ message: "Unable to connect to gateway, no platform connection details found" });
|
||||||
throw new BadRequestError({ message: "Unable to connect to LDAP server" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return credentials;
|
return withGatewayV2Proxy(
|
||||||
} catch (e: unknown) {
|
async (proxyPort) => {
|
||||||
throw new BadRequestError({
|
const proxyUrl = constructLdapUrl(protocol, "localhost", proxyPort);
|
||||||
message: `Unable to validate connection: ${(e as Error).message || "verify credentials"}`
|
const isSSL = protocol === "ldaps";
|
||||||
});
|
|
||||||
|
const client = ldap.createClient({
|
||||||
|
url: proxyUrl,
|
||||||
|
timeout: LDAP_TIMEOUT,
|
||||||
|
connectTimeout: LDAP_TIMEOUT,
|
||||||
|
tlsOptions: isSSL
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: config.credentials.sslRejectUnauthorized,
|
||||||
|
ca: config.credentials.sslCertificate ? [config.credentials.sslCertificate] : undefined,
|
||||||
|
servername: host,
|
||||||
|
// bypass hostname verification for development
|
||||||
|
...(appCfg.isDevelopmentMode ? { checkServerIdentity: () => undefined } : {})
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
return setupLdapClientHandlers<T>(client, credentials.dn, credentials.password, async (ldapClient) => {
|
||||||
|
try {
|
||||||
|
return await operation(ldapClient);
|
||||||
|
} finally {
|
||||||
|
ldapClient.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: GatewayProxyProtocol.Tcp,
|
||||||
|
relayHost: platformConnectionDetails.relayHost,
|
||||||
|
gateway: platformConnectionDetails.gateway,
|
||||||
|
relay: platformConnectionDetails.relay
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-gateway path - calls getLdapConnectionClient which has validation
|
||||||
|
const client = await getLdapConnectionClient(credentials);
|
||||||
|
try {
|
||||||
|
return await operation(client);
|
||||||
} finally {
|
} finally {
|
||||||
client?.destroy();
|
client.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateLdapConnectionCredentials = async (
|
||||||
|
config: TLdapConnectionConfig,
|
||||||
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||||
|
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await executeWithPotentialGateway(config, gatewayV2Service, async (client) => {
|
||||||
|
// this shouldn't occur as handle connection error events in client but here as fallback
|
||||||
|
if (!client.connected) {
|
||||||
|
throw new BadRequestError({ message: "Unable to connect to LDAP server" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return config.credentials;
|
||||||
|
} catch (error) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Unable to validate connection: ${
|
||||||
|
(error as Error)?.message?.replaceAll(config.credentials.password, "********************") ??
|
||||||
|
"verify credentials"
|
||||||
|
}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("met
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and(
|
export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and(
|
||||||
GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP)
|
GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP, {
|
||||||
|
supportsGateways: true
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UpdateLdapConnectionSchema = z
|
export const UpdateLdapConnectionSchema = z
|
||||||
@@ -84,7 +86,11 @@ export const UpdateLdapConnectionSchema = z
|
|||||||
AppConnections.UPDATE(AppConnection.LDAP).credentials
|
AppConnections.UPDATE(AppConnection.LDAP).credentials
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP));
|
.and(
|
||||||
|
GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP, {
|
||||||
|
supportsGateways: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const LdapConnectionListItemSchema = z
|
export const LdapConnectionListItemSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export type TLdapConnectionInput = z.infer<typeof CreateLdapConnectionSchema> &
|
|||||||
|
|
||||||
export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema;
|
export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TLdapConnectionConfig = DiscriminativePick<TLdapConnection, "method" | "app" | "credentials"> & {
|
export type TLdapConnectionConfig = DiscriminativePick<
|
||||||
|
TLdapConnectionInput,
|
||||||
|
"method" | "app" | "credentials" | "gatewayId"
|
||||||
|
> & {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
+59
@@ -4,8 +4,10 @@ import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -18,8 +20,11 @@ import {
|
|||||||
TextArea,
|
TextArea,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||||
|
import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types";
|
||||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||||
import { DistinguishedNameRegex, UserPrincipalNameRegex } from "@app/helpers/string";
|
import { DistinguishedNameRegex, UserPrincipalNameRegex } from "@app/helpers/string";
|
||||||
|
import { gatewaysQueryKeys } from "@app/hooks/api";
|
||||||
import {
|
import {
|
||||||
LdapConnectionMethod,
|
LdapConnectionMethod,
|
||||||
LdapConnectionProvider,
|
LdapConnectionProvider,
|
||||||
@@ -84,6 +89,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
|||||||
defaultValues: appConnection ?? {
|
defaultValues: appConnection ?? {
|
||||||
app: AppConnection.LDAP,
|
app: AppConnection.LDAP,
|
||||||
method: LdapConnectionMethod.SimpleBind,
|
method: LdapConnectionMethod.SimpleBind,
|
||||||
|
gatewayId: null,
|
||||||
credentials: {
|
credentials: {
|
||||||
provider: LdapConnectionProvider.ActiveDirectory,
|
provider: LdapConnectionProvider.ActiveDirectory,
|
||||||
url: "",
|
url: "",
|
||||||
@@ -104,6 +110,8 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
|||||||
|
|
||||||
const selectedProvider = watch("credentials.provider");
|
const selectedProvider = watch("credentials.provider");
|
||||||
const sslEnabled = watch("credentials.url")?.startsWith("ldaps://") ?? false;
|
const sslEnabled = watch("credentials.url")?.startsWith("ldaps://") ?? false;
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
@@ -114,6 +122,57 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isUpdate && <GenericAppConnectionsFields />}
|
{!isUpdate && <GenericAppConnectionsFields />}
|
||||||
|
{subscription.gateway && (
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGatewayPermissionActions.AttachGateways}
|
||||||
|
a={OrgPermissionSubjects.Gateway}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="gatewayId"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Gateway"
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
isDisabled={isAllowed}
|
||||||
|
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
value={value as string}
|
||||||
|
onValueChange={onChange}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
isLoading={isGatewaysLoading}
|
||||||
|
placeholder="Default: Internet Gateway"
|
||||||
|
position="popper"
|
||||||
|
>
|
||||||
|
<SelectItem
|
||||||
|
value={null as unknown as string}
|
||||||
|
onClick={() => onChange(undefined)}
|
||||||
|
>
|
||||||
|
Internet Gateway
|
||||||
|
</SelectItem>
|
||||||
|
{gateways?.map((el) => (
|
||||||
|
<SelectItem value={el.id} key={el.id}>
|
||||||
|
{el.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
)}
|
||||||
<div className="grid grid-cols-2 items-center gap-2">
|
<div className="grid grid-cols-2 items-center gap-2">
|
||||||
<Controller
|
<Controller
|
||||||
name="method"
|
name="method"
|
||||||
|
|||||||
Reference in New Issue
Block a user