Skip to content

Commit 323559b

Browse files
authored
feat: show warning on unrecognized idp group and role mapping claims (#16485)
1 parent 8a3a79f commit 323559b

File tree

9 files changed

+191
-65
lines changed

9 files changed

+191
-65
lines changed

site/src/api/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ class ApiMethods {
804804
) => {
805805
const params = new URLSearchParams();
806806
params.set("claimField", field);
807-
const response = await this.axios.get<TypesGen.Response>(
807+
const response = await this.axios.get<readonly string[]>(
808808
`/api/v2/organizations/${organization}/settings/idpsync/field-values?${params}`,
809809
);
810810
return response.data;

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { userEvent, within } from "@storybook/test";
2+
import { expect, userEvent, within } from "@storybook/test";
33
import {
44
MockOrganization,
55
MockOrganization2,
@@ -45,10 +45,16 @@ export const MissingGroups: Story = {
4545
},
4646
};
4747

48-
export const MissingClaim: Story = {
48+
export const MissingClaims: Story = {
4949
args: {
5050
claimFieldValues: [],
5151
},
52+
play: async ({ canvasElement }) => {
53+
const user = userEvent.setup();
54+
const warning = canvasElement.querySelector(".lucide-triangle-alert")!;
55+
expect(warning).not.toBe(null);
56+
await user.hover(warning);
57+
},
5258
};
5359

5460
export const AssignDefaultOrgWarningDialog: Story = {

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { TooltipProvider } from "@radix-ui/react-tooltip";
21
import type {
32
Organization,
43
OrganizationSyncSettings,
@@ -30,7 +29,6 @@ import {
3029
type Option,
3130
} from "components/MultiSelectCombobox/MultiSelectCombobox";
3231
import { Spinner } from "components/Spinner/Spinner";
33-
import { Stack } from "components/Stack/Stack";
3432
import { Switch } from "components/Switch/Switch";
3533
import {
3634
Table,
@@ -42,6 +40,7 @@ import {
4240
import {
4341
Tooltip,
4442
TooltipContent,
43+
TooltipProvider,
4544
TooltipTrigger,
4645
} from "components/Tooltip/Tooltip";
4746
import { useFormik } from "formik";

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ import {
2222
} from "components/MultiSelectCombobox/MultiSelectCombobox";
2323
import { Spinner } from "components/Spinner/Spinner";
2424
import { Switch } from "components/Switch/Switch";
25+
import {
26+
Tooltip,
27+
TooltipContent,
28+
TooltipProvider,
29+
TooltipTrigger,
30+
} from "components/Tooltip/Tooltip";
2531
import { useFormik } from "formik";
26-
import { Plus, Trash } from "lucide-react";
32+
import { Plus, Trash, TriangleAlert } from "lucide-react";
2733
import { type FC, useId, useState } from "react";
2834
import { docs } from "utils/docs";
2935
import { isUUID } from "utils/uuid";
@@ -32,16 +38,6 @@ import { ExportPolicyButton } from "./ExportPolicyButton";
3238
import { IdpMappingTable } from "./IdpMappingTable";
3339
import { IdpPillList } from "./IdpPillList";
3440

35-
interface IdpGroupSyncFormProps {
36-
groupSyncSettings: GroupSyncSettings;
37-
groupsMap: Map<string, string>;
38-
groups: Group[];
39-
groupMappingCount: number;
40-
legacyGroupMappingCount: number;
41-
organization: Organization;
42-
onSubmit: (data: GroupSyncSettings) => void;
43-
}
44-
4541
const groupSyncValidationSchema = Yup.object({
4642
field: Yup.string().trim(),
4743
regex_filter: Yup.string().trim(),
@@ -65,15 +61,27 @@ const groupSyncValidationSchema = Yup.object({
6561
.default({}),
6662
});
6763

68-
export const IdpGroupSyncForm = ({
64+
interface IdpGroupSyncFormProps {
65+
groupSyncSettings: GroupSyncSettings;
66+
claimFieldValues: readonly string[] | undefined;
67+
groupsMap: Map<string, string>;
68+
groups: Group[];
69+
groupMappingCount: number;
70+
legacyGroupMappingCount: number;
71+
organization: Organization;
72+
onSubmit: (data: GroupSyncSettings) => void;
73+
}
74+
75+
export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
6976
groupSyncSettings,
77+
claimFieldValues,
7078
groupMappingCount,
7179
legacyGroupMappingCount,
7280
groups,
7381
groupsMap,
7482
organization,
7583
onSubmit,
76-
}: IdpGroupSyncFormProps) => {
84+
}) => {
7785
const form = useFormik<GroupSyncSettings>({
7886
initialValues: {
7987
field: groupSyncSettings?.field ?? "",
@@ -270,6 +278,7 @@ export const IdpGroupSyncForm = ({
270278
<GroupRow
271279
key={idpGroup}
272280
idpGroup={idpGroup}
281+
exists={claimFieldValues?.includes(idpGroup)}
273282
coderGroup={getGroupNames(groups)}
274283
onDelete={handleDelete}
275284
/>
@@ -288,6 +297,7 @@ export const IdpGroupSyncForm = ({
288297
<GroupRow
289298
key={groupId}
290299
idpGroup={idpGroup}
300+
exists={claimFieldValues?.includes(idpGroup)}
291301
coderGroup={getGroupNames([groupId])}
292302
onDelete={handleDelete}
293303
/>
@@ -303,17 +313,48 @@ export const IdpGroupSyncForm = ({
303313

304314
interface GroupRowProps {
305315
idpGroup: string;
316+
exists: boolean | undefined;
306317
coderGroup: readonly string[];
307318
onDelete: (idpOrg: string) => void;
308319
}
309320

310-
const GroupRow: FC<GroupRowProps> = ({ idpGroup, coderGroup, onDelete }) => {
321+
const GroupRow: FC<GroupRowProps> = ({
322+
idpGroup,
323+
exists = true,
324+
coderGroup,
325+
onDelete,
326+
}) => {
311327
return (
312328
<TableRow data-testid={`group-${idpGroup}`}>
313-
<TableCell>{idpGroup}</TableCell>
329+
<TableCell>
330+
<div className="flex flex-row items-center gap-2 text-content-primary">
331+
{idpGroup}
332+
{!exists && (
333+
<TooltipProvider>
334+
<Tooltip>
335+
<TooltipTrigger asChild>
336+
<TriangleAlert className="size-icon-xs cursor-pointer text-content-warning" />
337+
</TooltipTrigger>
338+
<TooltipContent
339+
align="start"
340+
alignOffset={-8}
341+
sideOffset={8}
342+
className="p-2 text-xs text-content-secondary max-w-sm"
343+
>
344+
This value has not be seen in the specified claim field
345+
before. You might want to check your IdP configuration and
346+
ensure that this value is not misspelled.
347+
</TooltipContent>
348+
</Tooltip>
349+
</TooltipProvider>
350+
)}
351+
</div>
352+
</TableCell>
353+
314354
<TableCell>
315355
<IdpPillList roles={coderGroup} />
316356
</TableCell>
357+
317358
<TableCell>
318359
<Button
319360
variant="outline"

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@ import {
99
type Option,
1010
} from "components/MultiSelectCombobox/MultiSelectCombobox";
1111
import { Spinner } from "components/Spinner/Spinner";
12+
import {
13+
Tooltip,
14+
TooltipContent,
15+
TooltipProvider,
16+
TooltipTrigger,
17+
} from "components/Tooltip/Tooltip";
1218
import { useFormik } from "formik";
13-
import { Plus, Trash } from "lucide-react";
19+
import { Plus, Trash, TriangleAlert } from "lucide-react";
1420
import { type FC, useId, useState } from "react";
1521
import * as Yup from "yup";
1622
import { ExportPolicyButton } from "./ExportPolicyButton";
1723
import { IdpMappingTable } from "./IdpMappingTable";
1824
import { IdpPillList } from "./IdpPillList";
1925

20-
interface IdpRoleSyncFormProps {
21-
roleSyncSettings: RoleSyncSettings;
22-
roleMappingCount: number;
23-
organization: Organization;
24-
roles: Role[];
25-
onSubmit: (data: RoleSyncSettings) => void;
26-
}
27-
2826
const roleSyncValidationSchema = Yup.object({
2927
field: Yup.string().trim(),
3028
regex_filter: Yup.string().trim(),
@@ -48,13 +46,23 @@ const roleSyncValidationSchema = Yup.object({
4846
.default({}),
4947
});
5048

51-
export const IdpRoleSyncForm = ({
49+
interface IdpRoleSyncFormProps {
50+
roleSyncSettings: RoleSyncSettings;
51+
claimFieldValues: readonly string[] | undefined;
52+
roleMappingCount: number;
53+
organization: Organization;
54+
roles: Role[];
55+
onSubmit: (data: RoleSyncSettings) => void;
56+
}
57+
58+
export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
5259
roleSyncSettings,
60+
claimFieldValues,
5361
roleMappingCount,
5462
organization,
5563
roles,
5664
onSubmit,
57-
}: IdpRoleSyncFormProps) => {
65+
}) => {
5866
const form = useFormik<RoleSyncSettings>({
5967
initialValues: {
6068
field: roleSyncSettings?.field ?? "",
@@ -210,6 +218,7 @@ export const IdpRoleSyncForm = ({
210218
<RoleRow
211219
key={idpRole}
212220
idpRole={idpRole}
221+
exists={claimFieldValues?.includes(idpRole)}
213222
coderRoles={roles}
214223
onDelete={handleDelete}
215224
/>
@@ -222,17 +231,48 @@ export const IdpRoleSyncForm = ({
222231

223232
interface RoleRowProps {
224233
idpRole: string;
234+
exists: boolean | undefined;
225235
coderRoles: readonly string[];
226236
onDelete: (idpOrg: string) => void;
227237
}
228238

229-
const RoleRow: FC<RoleRowProps> = ({ idpRole, coderRoles, onDelete }) => {
239+
const RoleRow: FC<RoleRowProps> = ({
240+
idpRole,
241+
exists = true,
242+
coderRoles,
243+
onDelete,
244+
}) => {
230245
return (
231246
<TableRow data-testid={`role-${idpRole}`}>
232-
<TableCell>{idpRole}</TableCell>
247+
<TableCell>
248+
<div className="flex flex-row items-center gap-2 text-content-primary">
249+
{idpRole}
250+
{!exists && (
251+
<TooltipProvider>
252+
<Tooltip>
253+
<TooltipTrigger asChild>
254+
<TriangleAlert className="size-icon-xs cursor-pointer text-content-warning" />
255+
</TooltipTrigger>
256+
<TooltipContent
257+
align="start"
258+
alignOffset={-8}
259+
sideOffset={8}
260+
className="p-2 text-xs text-content-secondary max-w-sm"
261+
>
262+
This value has not be seen in the specified claim field
263+
before. You might want to check your IdP configuration and
264+
ensure that this value is not misspelled.
265+
</TooltipContent>
266+
</Tooltip>
267+
</TooltipProvider>
268+
)}
269+
</div>
270+
</TableCell>
271+
233272
<TableCell>
234273
<IdpPillList roles={coderRoles} />
235274
</TableCell>
275+
236276
<TableCell>
237277
<Button
238278
variant="outline"

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getErrorMessage } from "api/errors";
22
import { groupsByOrganization } from "api/queries/groups";
33
import {
44
groupIdpSyncSettings,
5+
organizationIdpSyncClaimFieldValues,
56
patchGroupSyncSettings,
67
patchRoleSyncSettings,
78
roleIdpSyncSettings,
@@ -17,8 +18,8 @@ import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
1718
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
1819
import type { FC } from "react";
1920
import { Helmet } from "react-helmet-async";
20-
import { useMutation, useQueries, useQueryClient } from "react-query";
21-
import { useParams } from "react-router-dom";
21+
import { useMutation, useQueries, useQuery, useQueryClient } from "react-query";
22+
import { useParams, useSearchParams } from "react-router-dom";
2223
import { docs } from "utils/docs";
2324
import { pageTitle } from "utils/page";
2425
import IdpSyncPageView from "./IdpSyncPageView";
@@ -47,6 +48,19 @@ export const IdpSyncPage: FC = () => {
4748
],
4849
});
4950

51+
const [searchParams] = useSearchParams();
52+
const tab = searchParams.get("tab") || "groups";
53+
const field =
54+
tab === "groups"
55+
? groupIdpSyncSettingsQuery.data?.field
56+
: roleIdpSyncSettingsQuery.data?.field;
57+
58+
const fieldValuesQuery = useQuery(
59+
field
60+
? organizationIdpSyncClaimFieldValues(organizationName, field)
61+
: { enabled: false },
62+
);
63+
5064
if (!organization) {
5165
return <EmptyState message="Organization not found" />;
5266
}
@@ -99,8 +113,10 @@ export const IdpSyncPage: FC = () => {
99113
</Cond>
100114
<Cond>
101115
<IdpSyncPageView
116+
tab={tab}
102117
groupSyncSettings={groupIdpSyncSettingsQuery.data}
103118
roleSyncSettings={roleIdpSyncSettingsQuery.data}
119+
claimFieldValues={fieldValuesQuery.data}
104120
groups={groupsQuery.data}
105121
groupsMap={groupsMap}
106122
roles={rolesQuery.data}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy