mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-29 18:05:37 +00:00
support policy buildiner in global idp
This commit is contained in:
@@ -15,7 +15,8 @@ import {
|
||||
import { Input } from "@app/components/ui/input";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { useRouter, useParams, redirect } from "next/navigation";
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
SettingsContainer,
|
||||
SettingsSection,
|
||||
@@ -189,15 +190,6 @@ export default function GeneralPage() {
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
|
||||
<Alert variant="neutral" className="">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("redirectUrlAbout")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t("redirectUrlAboutDescription")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<SettingsSectionForm>
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -239,9 +231,32 @@ export default function GeneralPage() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("idpAutoProvisionUsersDescription")}
|
||||
</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"idpAutoProvisionUsersDescription"
|
||||
)}
|
||||
</span>
|
||||
{form.watch("autoProvision") && (
|
||||
<FormDescription>
|
||||
{t.rich(
|
||||
"idpAdminAutoProvisionPoliciesTabHint",
|
||||
{
|
||||
policiesTabLink: (
|
||||
chunks
|
||||
) => (
|
||||
<Link
|
||||
href={`/admin/idp/${idpId}/policies`}
|
||||
className="text-primary hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
{chunks}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</FormDescription>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
@@ -375,29 +390,6 @@ export default function GeneralPage() {
|
||||
className="space-y-4"
|
||||
id="general-settings-form"
|
||||
>
|
||||
<Alert variant="neutral">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("idpJmespathAbout")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
"idpJmespathAboutDescription"
|
||||
)}
|
||||
<a
|
||||
href="https://jmespath.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline inline-flex items-center"
|
||||
>
|
||||
{t(
|
||||
"idpJmespathAboutDescriptionLink"
|
||||
)}{" "}
|
||||
<ExternalLink className="ml-1 h-4 w-4" />
|
||||
</a>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="identifierPath"
|
||||
|
||||
@@ -34,7 +34,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||
href: `/admin/idp/${params.idpId}/general`
|
||||
},
|
||||
{
|
||||
title: t("orgPolicies"),
|
||||
title: t("autoProvisionSettings"),
|
||||
href: `/admin/idp/${params.idpId}/policies`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
@@ -34,6 +34,7 @@ import { InfoIcon, ExternalLink, CheckIcon } from "lucide-react";
|
||||
import PolicyTable, { PolicyRow } from "../../../../../components/PolicyTable";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { ListOrgsResponse } from "@server/routers/org";
|
||||
import { ListRolesResponse } from "@server/routers/role";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -50,8 +51,6 @@ import {
|
||||
} from "@app/components/ui/command";
|
||||
import { CaretSortIcon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import { Textarea } from "@app/components/ui/textarea";
|
||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||
import { GetIdpResponse } from "@server/routers/idp";
|
||||
import {
|
||||
SettingsContainer,
|
||||
@@ -64,16 +63,40 @@ import {
|
||||
SettingsSectionForm
|
||||
} from "@app/components/Settings";
|
||||
import { useTranslations } from "next-intl";
|
||||
import RoleMappingConfigFields from "@app/components/RoleMappingConfigFields";
|
||||
import {
|
||||
compileRoleMappingExpression,
|
||||
createMappingBuilderRule,
|
||||
defaultRoleMappingConfig,
|
||||
detectRoleMappingConfig,
|
||||
MappingBuilderRule,
|
||||
RoleMappingMode
|
||||
} from "@app/lib/idpRoleMapping";
|
||||
|
||||
type Organization = {
|
||||
orgId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
function resetRoleMappingStateFromDetected(
|
||||
setMode: (m: RoleMappingMode) => void,
|
||||
setFixed: (v: string[]) => void,
|
||||
setClaim: (v: string) => void,
|
||||
setRules: (v: MappingBuilderRule[]) => void,
|
||||
setRaw: (v: string) => void,
|
||||
stored: string | null | undefined
|
||||
) {
|
||||
const d = detectRoleMappingConfig(stored);
|
||||
setMode(d.mode);
|
||||
setFixed(d.fixedRoleNames);
|
||||
setClaim(d.mappingBuilder.claimPath);
|
||||
setRules(d.mappingBuilder.rules);
|
||||
setRaw(d.rawExpression);
|
||||
}
|
||||
|
||||
export default function PoliciesPage() {
|
||||
const { env } = useEnvContext();
|
||||
const api = createApiClient({ env });
|
||||
const router = useRouter();
|
||||
const { idpId } = useParams();
|
||||
const t = useTranslations();
|
||||
|
||||
@@ -88,14 +111,39 @@ export default function PoliciesPage() {
|
||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||
const [editingPolicy, setEditingPolicy] = useState<PolicyRow | null>(null);
|
||||
|
||||
const [defaultRoleMappingMode, setDefaultRoleMappingMode] =
|
||||
useState<RoleMappingMode>("fixedRoles");
|
||||
const [defaultFixedRoleNames, setDefaultFixedRoleNames] = useState<
|
||||
string[]
|
||||
>([]);
|
||||
const [defaultMappingBuilderClaimPath, setDefaultMappingBuilderClaimPath] =
|
||||
useState("groups");
|
||||
const [defaultMappingBuilderRules, setDefaultMappingBuilderRules] =
|
||||
useState<MappingBuilderRule[]>([createMappingBuilderRule()]);
|
||||
const [defaultRawRoleExpression, setDefaultRawRoleExpression] =
|
||||
useState("");
|
||||
|
||||
const [policyRoleMappingMode, setPolicyRoleMappingMode] =
|
||||
useState<RoleMappingMode>("fixedRoles");
|
||||
const [policyFixedRoleNames, setPolicyFixedRoleNames] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
const [policyMappingBuilderClaimPath, setPolicyMappingBuilderClaimPath] =
|
||||
useState("groups");
|
||||
const [policyMappingBuilderRules, setPolicyMappingBuilderRules] = useState<
|
||||
MappingBuilderRule[]
|
||||
>([createMappingBuilderRule()]);
|
||||
const [policyRawRoleExpression, setPolicyRawRoleExpression] = useState("");
|
||||
const [policyOrgRoles, setPolicyOrgRoles] = useState<
|
||||
{ roleId: number; name: string }[]
|
||||
>([]);
|
||||
|
||||
const policyFormSchema = z.object({
|
||||
orgId: z.string().min(1, { message: t("orgRequired") }),
|
||||
roleMapping: z.string().optional(),
|
||||
orgMapping: z.string().optional()
|
||||
});
|
||||
|
||||
const defaultMappingsSchema = z.object({
|
||||
defaultRoleMapping: z.string().optional(),
|
||||
defaultOrgMapping: z.string().optional()
|
||||
});
|
||||
|
||||
@@ -106,15 +154,15 @@ export default function PoliciesPage() {
|
||||
resolver: zodResolver(policyFormSchema),
|
||||
defaultValues: {
|
||||
orgId: "",
|
||||
roleMapping: "",
|
||||
orgMapping: ""
|
||||
}
|
||||
});
|
||||
|
||||
const policyFormOrgId = form.watch("orgId");
|
||||
|
||||
const defaultMappingsForm = useForm({
|
||||
resolver: zodResolver(defaultMappingsSchema),
|
||||
defaultValues: {
|
||||
defaultRoleMapping: "",
|
||||
defaultOrgMapping: ""
|
||||
}
|
||||
});
|
||||
@@ -127,9 +175,16 @@ export default function PoliciesPage() {
|
||||
if (res.status === 200) {
|
||||
const data = res.data.data;
|
||||
defaultMappingsForm.reset({
|
||||
defaultRoleMapping: data.idp.defaultRoleMapping || "",
|
||||
defaultOrgMapping: data.idp.defaultOrgMapping || ""
|
||||
});
|
||||
resetRoleMappingStateFromDetected(
|
||||
setDefaultRoleMappingMode,
|
||||
setDefaultFixedRoleNames,
|
||||
setDefaultMappingBuilderClaimPath,
|
||||
setDefaultMappingBuilderRules,
|
||||
setDefaultRawRoleExpression,
|
||||
data.idp.defaultRoleMapping
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
@@ -184,11 +239,67 @@ export default function PoliciesPage() {
|
||||
load();
|
||||
}, [idpId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showAddDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orgId = editingPolicy?.orgId || policyFormOrgId;
|
||||
if (!orgId) {
|
||||
setPolicyOrgRoles([]);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
const res = await api
|
||||
.get<AxiosResponse<ListRolesResponse>>(`/org/${orgId}/roles`)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("accessRoleErrorFetch"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t("accessRoleErrorFetchDescription")
|
||||
)
|
||||
});
|
||||
return null;
|
||||
});
|
||||
if (!cancelled && res?.status === 200) {
|
||||
setPolicyOrgRoles(res.data.data.roles);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [showAddDialog, editingPolicy?.orgId, policyFormOrgId, api, t]);
|
||||
|
||||
function resetPolicyDialogRoleMappingState() {
|
||||
const d = defaultRoleMappingConfig();
|
||||
setPolicyRoleMappingMode(d.mode);
|
||||
setPolicyFixedRoleNames(d.fixedRoleNames);
|
||||
setPolicyMappingBuilderClaimPath(d.mappingBuilder.claimPath);
|
||||
setPolicyMappingBuilderRules(d.mappingBuilder.rules);
|
||||
setPolicyRawRoleExpression(d.rawExpression);
|
||||
}
|
||||
|
||||
const onAddPolicy = async (data: PolicyFormValues) => {
|
||||
const roleMappingExpression = compileRoleMappingExpression({
|
||||
mode: policyRoleMappingMode,
|
||||
fixedRoleNames: policyFixedRoleNames,
|
||||
mappingBuilder: {
|
||||
claimPath: policyMappingBuilderClaimPath,
|
||||
rules: policyMappingBuilderRules
|
||||
},
|
||||
rawExpression: policyRawRoleExpression
|
||||
});
|
||||
|
||||
setAddPolicyLoading(true);
|
||||
try {
|
||||
const res = await api.put(`/idp/${idpId}/org/${data.orgId}`, {
|
||||
roleMapping: data.roleMapping,
|
||||
roleMapping: roleMappingExpression,
|
||||
orgMapping: data.orgMapping
|
||||
});
|
||||
if (res.status === 201) {
|
||||
@@ -197,7 +308,7 @@ export default function PoliciesPage() {
|
||||
name:
|
||||
organizations.find((org) => org.orgId === data.orgId)
|
||||
?.name || "",
|
||||
roleMapping: data.roleMapping,
|
||||
roleMapping: roleMappingExpression,
|
||||
orgMapping: data.orgMapping
|
||||
};
|
||||
setPolicies([...policies, newPolicy]);
|
||||
@@ -207,6 +318,7 @@ export default function PoliciesPage() {
|
||||
});
|
||||
setShowAddDialog(false);
|
||||
form.reset();
|
||||
resetPolicyDialogRoleMappingState();
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
@@ -222,12 +334,22 @@ export default function PoliciesPage() {
|
||||
const onEditPolicy = async (data: PolicyFormValues) => {
|
||||
if (!editingPolicy) return;
|
||||
|
||||
const roleMappingExpression = compileRoleMappingExpression({
|
||||
mode: policyRoleMappingMode,
|
||||
fixedRoleNames: policyFixedRoleNames,
|
||||
mappingBuilder: {
|
||||
claimPath: policyMappingBuilderClaimPath,
|
||||
rules: policyMappingBuilderRules
|
||||
},
|
||||
rawExpression: policyRawRoleExpression
|
||||
});
|
||||
|
||||
setEditPolicyLoading(true);
|
||||
try {
|
||||
const res = await api.post(
|
||||
`/idp/${idpId}/org/${editingPolicy.orgId}`,
|
||||
{
|
||||
roleMapping: data.roleMapping,
|
||||
roleMapping: roleMappingExpression,
|
||||
orgMapping: data.orgMapping
|
||||
}
|
||||
);
|
||||
@@ -237,7 +359,7 @@ export default function PoliciesPage() {
|
||||
policy.orgId === editingPolicy.orgId
|
||||
? {
|
||||
...policy,
|
||||
roleMapping: data.roleMapping,
|
||||
roleMapping: roleMappingExpression,
|
||||
orgMapping: data.orgMapping
|
||||
}
|
||||
: policy
|
||||
@@ -250,6 +372,7 @@ export default function PoliciesPage() {
|
||||
setShowAddDialog(false);
|
||||
setEditingPolicy(null);
|
||||
form.reset();
|
||||
resetPolicyDialogRoleMappingState();
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
@@ -287,10 +410,20 @@ export default function PoliciesPage() {
|
||||
};
|
||||
|
||||
const onUpdateDefaultMappings = async (data: DefaultMappingsValues) => {
|
||||
const defaultRoleMappingExpression = compileRoleMappingExpression({
|
||||
mode: defaultRoleMappingMode,
|
||||
fixedRoleNames: defaultFixedRoleNames,
|
||||
mappingBuilder: {
|
||||
claimPath: defaultMappingBuilderClaimPath,
|
||||
rules: defaultMappingBuilderRules
|
||||
},
|
||||
rawExpression: defaultRawRoleExpression
|
||||
});
|
||||
|
||||
setUpdateDefaultMappingsLoading(true);
|
||||
try {
|
||||
const res = await api.post(`/idp/${idpId}/oidc`, {
|
||||
defaultRoleMapping: data.defaultRoleMapping,
|
||||
defaultRoleMapping: defaultRoleMappingExpression,
|
||||
defaultOrgMapping: data.defaultOrgMapping
|
||||
});
|
||||
if (res.status === 200) {
|
||||
@@ -317,25 +450,36 @@ export default function PoliciesPage() {
|
||||
return (
|
||||
<>
|
||||
<SettingsContainer>
|
||||
<Alert variant="neutral" className="mb-6">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("orgPoliciesAbout")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{/*TODO(vlalx): Validate replacing */}
|
||||
{t("orgPoliciesAboutDescription")}{" "}
|
||||
<Link
|
||||
href="https://docs.pangolin.net/manage/identity-providers/auto-provisioning"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{t("orgPoliciesAboutDescriptionLink")}
|
||||
<ExternalLink className="ml-1 h-4 w-4 inline" />
|
||||
</Link>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<PolicyTable
|
||||
policies={policies}
|
||||
onDelete={onDeletePolicy}
|
||||
onAdd={() => {
|
||||
loadOrganizations();
|
||||
form.reset({
|
||||
orgId: "",
|
||||
orgMapping: ""
|
||||
});
|
||||
setEditingPolicy(null);
|
||||
resetPolicyDialogRoleMappingState();
|
||||
setShowAddDialog(true);
|
||||
}}
|
||||
onEdit={(policy) => {
|
||||
setEditingPolicy(policy);
|
||||
form.reset({
|
||||
orgId: policy.orgId,
|
||||
orgMapping: policy.orgMapping || ""
|
||||
});
|
||||
resetRoleMappingStateFromDetected(
|
||||
setPolicyRoleMappingMode,
|
||||
setPolicyFixedRoleNames,
|
||||
setPolicyMappingBuilderClaimPath,
|
||||
setPolicyMappingBuilderRules,
|
||||
setPolicyRawRoleExpression,
|
||||
policy.roleMapping
|
||||
);
|
||||
setShowAddDialog(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
@@ -353,51 +497,58 @@ export default function PoliciesPage() {
|
||||
onUpdateDefaultMappings
|
||||
)}
|
||||
id="policy-default-mappings-form"
|
||||
className="space-y-4"
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<FormField
|
||||
control={defaultMappingsForm.control}
|
||||
name="defaultRoleMapping"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("defaultMappingsRole")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"defaultMappingsRoleDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<RoleMappingConfigFields
|
||||
fieldIdPrefix="admin-idp-default-role"
|
||||
showFreeformRoleNamesHint={true}
|
||||
roleMappingMode={defaultRoleMappingMode}
|
||||
onRoleMappingModeChange={
|
||||
setDefaultRoleMappingMode
|
||||
}
|
||||
roles={[]}
|
||||
fixedRoleNames={defaultFixedRoleNames}
|
||||
onFixedRoleNamesChange={
|
||||
setDefaultFixedRoleNames
|
||||
}
|
||||
mappingBuilderClaimPath={
|
||||
defaultMappingBuilderClaimPath
|
||||
}
|
||||
onMappingBuilderClaimPathChange={
|
||||
setDefaultMappingBuilderClaimPath
|
||||
}
|
||||
mappingBuilderRules={
|
||||
defaultMappingBuilderRules
|
||||
}
|
||||
onMappingBuilderRulesChange={
|
||||
setDefaultMappingBuilderRules
|
||||
}
|
||||
rawExpression={defaultRawRoleExpression}
|
||||
onRawExpressionChange={
|
||||
setDefaultRawRoleExpression
|
||||
}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={defaultMappingsForm.control}
|
||||
name="defaultOrgMapping"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("defaultMappingsOrg")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"defaultMappingsOrgDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
<SettingsSectionFooter>
|
||||
@@ -411,41 +562,20 @@ export default function PoliciesPage() {
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
|
||||
<PolicyTable
|
||||
policies={policies}
|
||||
onDelete={onDeletePolicy}
|
||||
onAdd={() => {
|
||||
loadOrganizations();
|
||||
form.reset({
|
||||
orgId: "",
|
||||
roleMapping: "",
|
||||
orgMapping: ""
|
||||
});
|
||||
setEditingPolicy(null);
|
||||
setShowAddDialog(true);
|
||||
}}
|
||||
onEdit={(policy) => {
|
||||
setEditingPolicy(policy);
|
||||
form.reset({
|
||||
orgId: policy.orgId,
|
||||
roleMapping: policy.roleMapping || "",
|
||||
orgMapping: policy.orgMapping || ""
|
||||
});
|
||||
setShowAddDialog(true);
|
||||
}}
|
||||
/>
|
||||
</SettingsContainer>
|
||||
|
||||
<Credenza
|
||||
open={showAddDialog}
|
||||
onOpenChange={(val) => {
|
||||
setShowAddDialog(val);
|
||||
setEditingPolicy(null);
|
||||
form.reset();
|
||||
if (!val) {
|
||||
setEditingPolicy(null);
|
||||
form.reset();
|
||||
resetPolicyDialogRoleMappingState();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CredenzaContent>
|
||||
<CredenzaContent className="max-w-4xl w-[calc(100vw-2rem)] sm:w-full">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
{editingPolicy
|
||||
@@ -456,7 +586,7 @@ export default function PoliciesPage() {
|
||||
{t("orgPolicyConfig")}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<CredenzaBody className="min-w-0 overflow-x-auto">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(
|
||||
@@ -557,25 +687,34 @@ export default function PoliciesPage() {
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="roleMapping"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("roleMappingPathOptional")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"defaultMappingsRoleDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
<RoleMappingConfigFields
|
||||
fieldIdPrefix="admin-idp-policy-role"
|
||||
showFreeformRoleNamesHint={false}
|
||||
roleMappingMode={policyRoleMappingMode}
|
||||
onRoleMappingModeChange={
|
||||
setPolicyRoleMappingMode
|
||||
}
|
||||
roles={policyOrgRoles}
|
||||
fixedRoleNames={policyFixedRoleNames}
|
||||
onFixedRoleNamesChange={
|
||||
setPolicyFixedRoleNames
|
||||
}
|
||||
mappingBuilderClaimPath={
|
||||
policyMappingBuilderClaimPath
|
||||
}
|
||||
onMappingBuilderClaimPathChange={
|
||||
setPolicyMappingBuilderClaimPath
|
||||
}
|
||||
mappingBuilderRules={
|
||||
policyMappingBuilderRules
|
||||
}
|
||||
onMappingBuilderRulesChange={
|
||||
setPolicyMappingBuilderRules
|
||||
}
|
||||
rawExpression={policyRawRoleExpression}
|
||||
onRawExpressionChange={
|
||||
setPolicyRawRoleExpression
|
||||
}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
|
||||
@@ -340,16 +340,6 @@ export default function Page() {
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
<Alert variant="neutral">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("idpOidcConfigureAlert")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t("idpOidcConfigureAlertDescription")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -369,29 +359,6 @@ export default function Page() {
|
||||
id="create-idp-form"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<Alert variant="neutral">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("idpJmespathAbout")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
"idpJmespathAboutDescription"
|
||||
)}{" "}
|
||||
<a
|
||||
href="https://jmespath.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline inline-flex items-center"
|
||||
>
|
||||
{t(
|
||||
"idpJmespathAboutDescriptionLink"
|
||||
)}{" "}
|
||||
<ExternalLink className="ml-1 h-4 w-4" />
|
||||
</a>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="identifierPath"
|
||||
|
||||
Reference in New Issue
Block a user