mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-01 21:46:38 +00:00
support org mapping on org idp
This commit is contained in:
@@ -949,7 +949,7 @@
|
|||||||
"defaultMappingsRole": "Default Role Mapping",
|
"defaultMappingsRole": "Default Role Mapping",
|
||||||
"defaultMappingsRoleDescription": "The result of this expression must return the role name as defined in the organization as a string.",
|
"defaultMappingsRoleDescription": "The result of this expression must return the role name as defined in the organization as a string.",
|
||||||
"defaultMappingsOrg": "Default Organization Mapping",
|
"defaultMappingsOrg": "Default Organization Mapping",
|
||||||
"defaultMappingsOrgDescription": "When set, this expression must return the organization ID or true for the user to access that organization. When unset, defining an organization policy for that org is enough: the user is allowed in as long as a valid role mapping can be resolved for them within the organization.",
|
"defaultMappingsOrgDescription": "When set, this expression must return the organization ID or true for the user to access that organization. When unset, defining a role mapping is enough: the user is allowed in as long as a valid role mapping can be resolved for them within the organization.",
|
||||||
"defaultMappingsSubmit": "Save Default Mappings",
|
"defaultMappingsSubmit": "Save Default Mappings",
|
||||||
"orgPoliciesEdit": "Edit Organization Policy",
|
"orgPoliciesEdit": "Edit Organization Policy",
|
||||||
"org": "Organization",
|
"org": "Organization",
|
||||||
@@ -2026,7 +2026,7 @@
|
|||||||
},
|
},
|
||||||
"internationaldomaindetected": "International Domain Detected",
|
"internationaldomaindetected": "International Domain Detected",
|
||||||
"willbestoredas": "Will be stored as:",
|
"willbestoredas": "Will be stored as:",
|
||||||
"roleMappingDescription": "Determine how roles are assigned to users when they sign in when Auto Provision is enabled.",
|
"roleMappingDescription": "Determine how roles are assigned to users when they sign in with this identity provider.",
|
||||||
"selectRole": "Select a Role",
|
"selectRole": "Select a Role",
|
||||||
"roleMappingExpression": "Expression",
|
"roleMappingExpression": "Expression",
|
||||||
"selectRolePlaceholder": "Choose a role",
|
"selectRolePlaceholder": "Choose a role",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const bodySchema = z.strictObject({
|
|||||||
autoProvision: z.boolean().optional(),
|
autoProvision: z.boolean().optional(),
|
||||||
variant: z.enum(["oidc", "google", "azure"]).optional().default("oidc"),
|
variant: z.enum(["oidc", "google", "azure"]).optional().default("oidc"),
|
||||||
roleMapping: z.string().optional(),
|
roleMapping: z.string().optional(),
|
||||||
|
orgMapping: z.string().nullish(),
|
||||||
tags: z.string().optional()
|
tags: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,6 +106,7 @@ export async function createOrgOidcIdp(
|
|||||||
name,
|
name,
|
||||||
variant,
|
variant,
|
||||||
roleMapping,
|
roleMapping,
|
||||||
|
orgMapping: orgMappingBody,
|
||||||
tags
|
tags
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
@@ -152,11 +154,16 @@ export async function createOrgOidcIdp(
|
|||||||
variant
|
variant
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const orgMapping =
|
||||||
|
orgMappingBody !== undefined
|
||||||
|
? orgMappingBody
|
||||||
|
: `'${orgId}'`;
|
||||||
|
|
||||||
await trx.insert(idpOrg).values({
|
await trx.insert(idpOrg).values({
|
||||||
idpId: idpRes.idpId,
|
idpId: idpRes.idpId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
roleMapping: roleMapping || null,
|
roleMapping: roleMapping || null,
|
||||||
orgMapping: `'${orgId}'`
|
orgMapping
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const bodySchema = z.strictObject({
|
|||||||
scopes: z.string().optional(),
|
scopes: z.string().optional(),
|
||||||
autoProvision: z.boolean().optional(),
|
autoProvision: z.boolean().optional(),
|
||||||
roleMapping: z.string().optional(),
|
roleMapping: z.string().optional(),
|
||||||
|
orgMapping: z.string().nullish(),
|
||||||
tags: z.string().optional()
|
tags: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,6 +111,7 @@ export async function updateOrgOidcIdp(
|
|||||||
namePath,
|
namePath,
|
||||||
name,
|
name,
|
||||||
roleMapping,
|
roleMapping,
|
||||||
|
orgMapping,
|
||||||
tags
|
tags
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
@@ -205,13 +207,20 @@ export async function updateOrgOidcIdp(
|
|||||||
.where(eq(idpOidcConfig.idpId, idpId));
|
.where(eq(idpOidcConfig.idpId, idpId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idpOrgPolicyPatch: {
|
||||||
|
roleMapping?: string;
|
||||||
|
orgMapping?: string | null;
|
||||||
|
} = {};
|
||||||
if (roleMapping !== undefined) {
|
if (roleMapping !== undefined) {
|
||||||
// Update IdP-org policy
|
idpOrgPolicyPatch.roleMapping = roleMapping;
|
||||||
|
}
|
||||||
|
if (orgMapping !== undefined) {
|
||||||
|
idpOrgPolicyPatch.orgMapping = orgMapping;
|
||||||
|
}
|
||||||
|
if (Object.keys(idpOrgPolicyPatch).length > 0) {
|
||||||
await trx
|
await trx
|
||||||
.update(idpOrg)
|
.update(idpOrg)
|
||||||
.set({
|
.set(idpOrgPolicyPatch)
|
||||||
roleMapping
|
|
||||||
})
|
|
||||||
.where(
|
.where(
|
||||||
and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, orgId))
|
and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, orgId))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ export default function GeneralPage() {
|
|||||||
emailPath: z.string().nullable().optional(),
|
emailPath: z.string().nullable().optional(),
|
||||||
namePath: z.string().nullable().optional(),
|
namePath: z.string().nullable().optional(),
|
||||||
scopes: z.string().min(1, { message: t("idpScopeRequired") }),
|
scopes: z.string().min(1, { message: t("idpScopeRequired") }),
|
||||||
autoProvision: z.boolean().default(false)
|
autoProvision: z.boolean().default(false),
|
||||||
|
orgMapping: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Google form schema (simplified)
|
// Google form schema (simplified)
|
||||||
@@ -109,7 +110,8 @@ export default function GeneralPage() {
|
|||||||
.min(1, { message: t("idpClientSecretRequired") }),
|
.min(1, { message: t("idpClientSecretRequired") }),
|
||||||
roleMapping: z.string().nullable().optional(),
|
roleMapping: z.string().nullable().optional(),
|
||||||
roleId: z.number().nullable().optional(),
|
roleId: z.number().nullable().optional(),
|
||||||
autoProvision: z.boolean().default(false)
|
autoProvision: z.boolean().default(false),
|
||||||
|
orgMapping: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Azure form schema (simplified with tenant ID)
|
// Azure form schema (simplified with tenant ID)
|
||||||
@@ -122,7 +124,8 @@ export default function GeneralPage() {
|
|||||||
tenantId: z.string().min(1, { message: t("idpTenantIdRequired") }),
|
tenantId: z.string().min(1, { message: t("idpTenantIdRequired") }),
|
||||||
roleMapping: z.string().nullable().optional(),
|
roleMapping: z.string().nullable().optional(),
|
||||||
roleId: z.number().nullable().optional(),
|
roleId: z.number().nullable().optional(),
|
||||||
autoProvision: z.boolean().default(false)
|
autoProvision: z.boolean().default(false),
|
||||||
|
orgMapping: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
type OidcFormValues = z.infer<typeof OidcFormSchema>;
|
type OidcFormValues = z.infer<typeof OidcFormSchema>;
|
||||||
@@ -160,7 +163,8 @@ export default function GeneralPage() {
|
|||||||
autoProvision: true,
|
autoProvision: true,
|
||||||
roleMapping: null,
|
roleMapping: null,
|
||||||
roleId: null,
|
roleId: null,
|
||||||
tenantId: ""
|
tenantId: "",
|
||||||
|
orgMapping: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,7 +231,8 @@ export default function GeneralPage() {
|
|||||||
clientSecret: data.idpOidcConfig.clientSecret,
|
clientSecret: data.idpOidcConfig.clientSecret,
|
||||||
autoProvision: data.idp.autoProvision,
|
autoProvision: data.idp.autoProvision,
|
||||||
roleMapping: roleMapping || null,
|
roleMapping: roleMapping || null,
|
||||||
roleId: null
|
roleId: null,
|
||||||
|
orgMapping: data.idpOrg?.orgMapping ?? ""
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add variant-specific fields
|
// Add variant-specific fields
|
||||||
@@ -344,12 +349,14 @@ export default function GeneralPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build payload based on variant
|
// Build payload based on variant
|
||||||
|
const orgMappingTrimmed = data.orgMapping?.trim() ?? "";
|
||||||
let payload: any = {
|
let payload: any = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
clientId: data.clientId,
|
clientId: data.clientId,
|
||||||
clientSecret: data.clientSecret,
|
clientSecret: data.clientSecret,
|
||||||
autoProvision: data.autoProvision,
|
autoProvision: data.autoProvision,
|
||||||
roleMapping: roleMappingExpression
|
roleMapping: roleMappingExpression,
|
||||||
|
orgMapping: orgMappingTrimmed === "" ? null : orgMappingTrimmed
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add variant-specific fields
|
// Add variant-specific fields
|
||||||
@@ -532,6 +539,10 @@ export default function GeneralPage() {
|
|||||||
}
|
}
|
||||||
rawExpression={rawRoleExpression}
|
rawExpression={rawRoleExpression}
|
||||||
onRawExpressionChange={setRawRoleExpression}
|
onRawExpressionChange={setRawRoleExpression}
|
||||||
|
orgMappingField={{
|
||||||
|
control: form.control,
|
||||||
|
name: "orgMapping"
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ export default function Page() {
|
|||||||
tenantId: z.string().optional(),
|
tenantId: z.string().optional(),
|
||||||
autoProvision: z.boolean().default(false),
|
autoProvision: z.boolean().default(false),
|
||||||
roleMapping: z.string().nullable().optional(),
|
roleMapping: z.string().nullable().optional(),
|
||||||
roleId: z.number().nullable().optional()
|
roleId: z.number().nullable().optional(),
|
||||||
|
orgMapping: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
type CreateIdpFormValues = z.infer<typeof createIdpFormSchema>;
|
type CreateIdpFormValues = z.infer<typeof createIdpFormSchema>;
|
||||||
@@ -112,7 +113,8 @@ export default function Page() {
|
|||||||
tenantId: "",
|
tenantId: "",
|
||||||
autoProvision: false,
|
autoProvision: false,
|
||||||
roleMapping: null,
|
roleMapping: null,
|
||||||
roleId: null
|
roleId: null,
|
||||||
|
orgMapping: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,7 +179,7 @@ export default function Page() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload: Record<string, unknown> = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
clientId: data.clientId,
|
clientId: data.clientId,
|
||||||
clientSecret: data.clientSecret,
|
clientSecret: data.clientSecret,
|
||||||
@@ -191,6 +193,10 @@ export default function Page() {
|
|||||||
scopes: data.scopes,
|
scopes: data.scopes,
|
||||||
variant: data.type
|
variant: data.type
|
||||||
};
|
};
|
||||||
|
const trimmedOrgMapping = data.orgMapping?.trim();
|
||||||
|
if (trimmedOrgMapping) {
|
||||||
|
payload.orgMapping = trimmedOrgMapping;
|
||||||
|
}
|
||||||
|
|
||||||
// Use the appropriate endpoint based on provider type
|
// Use the appropriate endpoint based on provider type
|
||||||
const endpoint = "oidc";
|
const endpoint = "oidc";
|
||||||
@@ -336,6 +342,10 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
rawExpression={rawRoleExpression}
|
rawExpression={rawRoleExpression}
|
||||||
onRawExpressionChange={setRawRoleExpression}
|
onRawExpressionChange={setRawRoleExpression}
|
||||||
|
orgMappingField={{
|
||||||
|
control: form.control,
|
||||||
|
name: "orgMapping"
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@@ -63,7 +62,7 @@ import {
|
|||||||
SettingsSectionForm
|
SettingsSectionForm
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import RoleMappingConfigFields from "@app/components/RoleMappingConfigFields";
|
import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
|
||||||
import {
|
import {
|
||||||
compileRoleMappingExpression,
|
compileRoleMappingExpression,
|
||||||
createMappingBuilderRule,
|
createMappingBuilderRule,
|
||||||
@@ -499,9 +498,17 @@ export default function PoliciesPage() {
|
|||||||
id="policy-default-mappings-form"
|
id="policy-default-mappings-form"
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
>
|
>
|
||||||
<RoleMappingConfigFields
|
<AutoProvisionConfigWidget
|
||||||
fieldIdPrefix="admin-idp-default-role"
|
showAutoProvisionSwitch={false}
|
||||||
showFreeformRoleNamesHint={true}
|
autoProvision={true}
|
||||||
|
onAutoProvisionChange={() => {}}
|
||||||
|
orgMappingField={{
|
||||||
|
control: defaultMappingsForm.control,
|
||||||
|
name: "defaultOrgMapping",
|
||||||
|
labelKey: "defaultMappingsOrg"
|
||||||
|
}}
|
||||||
|
roleMappingFieldIdPrefix="admin-idp-default-role"
|
||||||
|
showFreeformRoleNamesHint
|
||||||
roleMappingMode={defaultRoleMappingMode}
|
roleMappingMode={defaultRoleMappingMode}
|
||||||
onRoleMappingModeChange={
|
onRoleMappingModeChange={
|
||||||
setDefaultRoleMappingMode
|
setDefaultRoleMappingMode
|
||||||
@@ -528,27 +535,6 @@ export default function PoliciesPage() {
|
|||||||
setDefaultRawRoleExpression
|
setDefaultRawRoleExpression
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={defaultMappingsForm.control}
|
|
||||||
name="defaultOrgMapping"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t("defaultMappingsOrg")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"defaultMappingsOrgDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
<SettingsSectionFooter>
|
<SettingsSectionFooter>
|
||||||
@@ -687,9 +673,15 @@ export default function PoliciesPage() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RoleMappingConfigFields
|
<AutoProvisionConfigWidget
|
||||||
fieldIdPrefix="admin-idp-policy-role"
|
showAutoProvisionSwitch={false}
|
||||||
showFreeformRoleNamesHint={false}
|
autoProvision={true}
|
||||||
|
onAutoProvisionChange={() => {}}
|
||||||
|
orgMappingField={{
|
||||||
|
control: form.control,
|
||||||
|
name: "orgMapping"
|
||||||
|
}}
|
||||||
|
roleMappingFieldIdPrefix="admin-idp-policy-role"
|
||||||
roleMappingMode={policyRoleMappingMode}
|
roleMappingMode={policyRoleMappingMode}
|
||||||
onRoleMappingModeChange={
|
onRoleMappingModeChange={
|
||||||
setPolicyRoleMappingMode
|
setPolicyRoleMappingMode
|
||||||
@@ -716,27 +708,6 @@ export default function PoliciesPage() {
|
|||||||
setPolicyRawRoleExpression
|
setPolicyRawRoleExpression
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="orgMapping"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t("orgMappingPathOptional")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"defaultMappingsOrgDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
|
|||||||
@@ -1,19 +1,33 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import IdpAutoProvisionUsersDescription from "@app/components/IdpAutoProvisionUsersDescription";
|
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||||
import { FormDescription } from "@app/components/ui/form";
|
import RoleMappingConfigFields from "@app/components/RoleMappingConfigFields";
|
||||||
import { SwitchInput } from "@app/components/SwitchInput";
|
import { SwitchInput } from "@app/components/SwitchInput";
|
||||||
import { useTranslations } from "next-intl";
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage
|
||||||
|
} from "@app/components/ui/form";
|
||||||
|
import { Input } from "@app/components/ui/input";
|
||||||
|
import { MappingBuilderRule, RoleMappingMode } from "@app/lib/idpRoleMapping";
|
||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { MappingBuilderRule, RoleMappingMode } from "@app/lib/idpRoleMapping";
|
import { useTranslations } from "next-intl";
|
||||||
import RoleMappingConfigFields from "@app/components/RoleMappingConfigFields";
|
import type { Control } from "react-hook-form";
|
||||||
|
|
||||||
type Role = {
|
type Role = {
|
||||||
roleId: number;
|
roleId: number;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IdpOrgMappingFieldBinding = {
|
||||||
|
control: unknown;
|
||||||
|
name: string;
|
||||||
|
labelKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type AutoProvisionConfigWidgetProps = {
|
type AutoProvisionConfigWidgetProps = {
|
||||||
autoProvision: boolean;
|
autoProvision: boolean;
|
||||||
onAutoProvisionChange: (checked: boolean) => void;
|
onAutoProvisionChange: (checked: boolean) => void;
|
||||||
@@ -28,6 +42,11 @@ type AutoProvisionConfigWidgetProps = {
|
|||||||
onMappingBuilderRulesChange: (rules: MappingBuilderRule[]) => void;
|
onMappingBuilderRulesChange: (rules: MappingBuilderRule[]) => void;
|
||||||
rawExpression: string;
|
rawExpression: string;
|
||||||
onRawExpressionChange: (expression: string) => void;
|
onRawExpressionChange: (expression: string) => void;
|
||||||
|
orgMappingField: IdpOrgMappingFieldBinding;
|
||||||
|
showAutoProvisionSwitch?: boolean;
|
||||||
|
roleMappingFieldIdPrefix?: string;
|
||||||
|
showFreeformRoleNamesHint?: boolean;
|
||||||
|
autoProvisionSwitchId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AutoProvisionConfigWidget({
|
export default function AutoProvisionConfigWidget({
|
||||||
@@ -43,27 +62,50 @@ export default function AutoProvisionConfigWidget({
|
|||||||
mappingBuilderRules,
|
mappingBuilderRules,
|
||||||
onMappingBuilderRulesChange,
|
onMappingBuilderRulesChange,
|
||||||
rawExpression,
|
rawExpression,
|
||||||
onRawExpressionChange
|
onRawExpressionChange,
|
||||||
|
orgMappingField,
|
||||||
|
showAutoProvisionSwitch = true,
|
||||||
|
roleMappingFieldIdPrefix = "org-idp-auto-provision",
|
||||||
|
showFreeformRoleNamesHint = false,
|
||||||
|
autoProvisionSwitchId = "auto-provision-toggle"
|
||||||
}: AutoProvisionConfigWidgetProps) {
|
}: AutoProvisionConfigWidgetProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { isPaidUser } = usePaidStatus();
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
|
||||||
|
const showMappingTabs = showAutoProvisionSwitch === false || autoProvision;
|
||||||
|
|
||||||
|
const orgMappingLabelKey =
|
||||||
|
orgMappingField.labelKey ?? "orgMappingPathOptional";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{showAutoProvisionSwitch && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<SwitchInput
|
<SwitchInput
|
||||||
id="auto-provision-toggle"
|
id={autoProvisionSwitchId}
|
||||||
label={t("idpAutoProvisionUsers")}
|
label={t("idpAutoProvisionUsers")}
|
||||||
defaultChecked={autoProvision}
|
defaultChecked={autoProvision}
|
||||||
onCheckedChange={onAutoProvisionChange}
|
onCheckedChange={onAutoProvisionChange}
|
||||||
disabled={!isPaidUser(tierMatrix.autoProvisioning)}
|
disabled={!isPaidUser(tierMatrix.autoProvisioning)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{autoProvision && (
|
{showMappingTabs && (
|
||||||
|
<HorizontalTabs
|
||||||
|
clientSide
|
||||||
|
defaultTab={0}
|
||||||
|
items={[
|
||||||
|
{ title: t("roleMapping"), href: "#" },
|
||||||
|
{ title: t("orgMapping"), href: "#" }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div className="space-y-4 mt-4 p-1">
|
||||||
<RoleMappingConfigFields
|
<RoleMappingConfigFields
|
||||||
fieldIdPrefix="org-idp-auto-provision"
|
fieldIdPrefix={roleMappingFieldIdPrefix}
|
||||||
showFreeformRoleNamesHint={false}
|
showFreeformRoleNamesHint={
|
||||||
|
showFreeformRoleNamesHint
|
||||||
|
}
|
||||||
roleMappingMode={roleMappingMode}
|
roleMappingMode={roleMappingMode}
|
||||||
onRoleMappingModeChange={onRoleMappingModeChange}
|
onRoleMappingModeChange={onRoleMappingModeChange}
|
||||||
roles={roles}
|
roles={roles}
|
||||||
@@ -74,10 +116,41 @@ export default function AutoProvisionConfigWidget({
|
|||||||
onMappingBuilderClaimPathChange
|
onMappingBuilderClaimPathChange
|
||||||
}
|
}
|
||||||
mappingBuilderRules={mappingBuilderRules}
|
mappingBuilderRules={mappingBuilderRules}
|
||||||
onMappingBuilderRulesChange={onMappingBuilderRulesChange}
|
onMappingBuilderRulesChange={
|
||||||
|
onMappingBuilderRulesChange
|
||||||
|
}
|
||||||
rawExpression={rawExpression}
|
rawExpression={rawExpression}
|
||||||
onRawExpressionChange={onRawExpressionChange}
|
onRawExpressionChange={onRawExpressionChange}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4 mt-4 p-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("defaultMappingsOrgDescription")}
|
||||||
|
</p>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
orgMappingField.control as Control<any>
|
||||||
|
}
|
||||||
|
name={orgMappingField.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(orgMappingLabelKey)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="e.g., ends_with(email, '@organization.com')"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HorizontalTabs>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -79,10 +79,7 @@ export default function RoleMappingConfigFields({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!supportsMultipleRolesPerUser && mappingBuilderRules.length > 1) {
|
||||||
!supportsMultipleRolesPerUser &&
|
|
||||||
mappingBuilderRules.length > 1
|
|
||||||
) {
|
|
||||||
onMappingBuilderRulesChange([mappingBuilderRules[0]]);
|
onMappingBuilderRulesChange([mappingBuilderRules[0]]);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -95,11 +92,7 @@ export default function RoleMappingConfigFields({
|
|||||||
if (!supportsMultipleRolesPerUser && fixedRoleNames.length > 1) {
|
if (!supportsMultipleRolesPerUser && fixedRoleNames.length > 1) {
|
||||||
onFixedRoleNamesChange([fixedRoleNames[0]]);
|
onFixedRoleNamesChange([fixedRoleNames[0]]);
|
||||||
}
|
}
|
||||||
}, [
|
}, [supportsMultipleRolesPerUser, fixedRoleNames, onFixedRoleNamesChange]);
|
||||||
supportsMultipleRolesPerUser,
|
|
||||||
fixedRoleNames,
|
|
||||||
onFixedRoleNamesChange
|
|
||||||
]);
|
|
||||||
|
|
||||||
const fixedRadioId = `${fieldIdPrefix}-fixed-roles-mode`;
|
const fixedRadioId = `${fieldIdPrefix}-fixed-roles-mode`;
|
||||||
const builderRadioId = `${fieldIdPrefix}-mapping-builder-mode`;
|
const builderRadioId = `${fieldIdPrefix}-mapping-builder-mode`;
|
||||||
@@ -116,7 +109,6 @@ export default function RoleMappingConfigFields({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<FormLabel className="mb-2">{t("roleMapping")}</FormLabel>
|
|
||||||
<FormDescription className="mb-4">
|
<FormDescription className="mb-4">
|
||||||
{t("roleMappingDescription")}
|
{t("roleMappingDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
@@ -272,7 +264,9 @@ export default function RoleMappingConfigFields({
|
|||||||
supportsMultipleRolesPerUser={
|
supportsMultipleRolesPerUser={
|
||||||
supportsMultipleRolesPerUser
|
supportsMultipleRolesPerUser
|
||||||
}
|
}
|
||||||
showRemoveButton={mappingBuilderShowsRemoveColumn}
|
showRemoveButton={
|
||||||
|
mappingBuilderShowsRemoveColumn
|
||||||
|
}
|
||||||
rule={rule}
|
rule={rule}
|
||||||
onChange={(nextRule) => {
|
onChange={(nextRule) => {
|
||||||
const nextRules = mappingBuilderRules.map(
|
const nextRules = mappingBuilderRules.map(
|
||||||
@@ -390,12 +384,10 @@ function BuilderRuleRow({
|
|||||||
text: name
|
text: name
|
||||||
}))}
|
}))}
|
||||||
setTags={(nextTags) => {
|
setTags={(nextTags) => {
|
||||||
const prevRoleTags = rule.roleNames.map(
|
const prevRoleTags = rule.roleNames.map((name) => ({
|
||||||
(name) => ({
|
|
||||||
id: name,
|
id: name,
|
||||||
text: name
|
text: name
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
const next =
|
const next =
|
||||||
typeof nextTags === "function"
|
typeof nextTags === "function"
|
||||||
? nextTags(prevRoleTags)
|
? nextTags(prevRoleTags)
|
||||||
|
|||||||
Reference in New Issue
Block a user