-
Notifications
You must be signed in to change notification settings - Fork 91
ENG-3009: Fix Edit SSO Provider Save button always disabled #7739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7c492b4
fa7ce0f
62d829f
a8ccca4
49746a9
701654e
d1cf5ad
2af2d53
98e00a2
dbfa928
8894d54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| type: Fixed | ||
| description: Fixed Edit SSO Provider Save button being permanently disabled after creating a provider | ||
| pr: 7739 | ||
| labels: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,22 +64,28 @@ export const transformOrganizationToFormValues = ( | |
| openIDProvider: OpenIDProvider, | ||
| ): SSOProviderFormValues => ({ ...openIDProvider }); | ||
|
|
||
| const SSOProviderFormValidationSchema = Yup.object().shape({ | ||
| provider: Yup.string().required().label("Provider"), | ||
| name: Yup.string().required().label("Name"), | ||
| client_id: Yup.string().required().label("Client ID"), | ||
| client_secret: Yup.string().required().label("Client Secret"), | ||
| scopes: Yup.array().of(Yup.string()).label("Scopes"), | ||
| verify_email: Yup.boolean().optional().label("Verify Email"), | ||
| verify_email_field: Yup.string() | ||
| .optional() | ||
| .nullable() | ||
| .label("Userinfo object verify email field"), | ||
| email_field: Yup.string() | ||
| .optional() | ||
| .nullable() | ||
| .label("Userinfo object email field"), | ||
| }); | ||
| export const getSSOProviderFormValidationSchema = (isEditMode: boolean) => | ||
| Yup.object().shape({ | ||
| provider: Yup.string().required().label("Provider"), | ||
| name: Yup.string().required().label("Name"), | ||
| client_id: isEditMode | ||
| ? Yup.string().optional().label("Client ID") | ||
| : Yup.string().required().label("Client ID"), | ||
| client_secret: isEditMode | ||
| ? Yup.string().optional().label("Client Secret") | ||
| : Yup.string().required().label("Client Secret"), | ||
| // nullable — the API type allows null for scopes so that the form save button is not disabled (Array<string> | null) | ||
| scopes: Yup.array().of(Yup.string()).nullable().label("Scopes"), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is ultimately what wound up allowing for the save button to enable, so I'm not sure why it's considered separate. |
||
| verify_email: Yup.boolean().optional().label("Verify Email"), | ||
|
wadesdev marked this conversation as resolved.
|
||
| verify_email_field: Yup.string() | ||
| .optional() | ||
| .nullable() | ||
| .label("Userinfo object verify email field"), | ||
| email_field: Yup.string() | ||
| .optional() | ||
| .nullable() | ||
| .label("Userinfo object email field"), | ||
| }); | ||
|
|
||
| const CustomProviderExtraFields = () => { | ||
| const { | ||
|
|
@@ -183,6 +189,11 @@ const SSOProviderForm = ({ | |
| onSuccess, | ||
| onClose, | ||
| }: SSOProviderFormProps) => { | ||
| const isEditMode = !!openIDProvider; | ||
| const validationSchema = useMemo( | ||
| () => getSSOProviderFormValidationSchema(isEditMode), | ||
| [isEditMode], | ||
| ); | ||
| const [createOpenIDProviderMutationTrigger] = | ||
| useCreateOpenIDProviderMutation(); | ||
| const [updateOpenIDProviderMutation] = useUpdateOpenIDProviderMutation(); | ||
|
|
@@ -221,8 +232,20 @@ const SSOProviderForm = ({ | |
| } | ||
| } | ||
| }; | ||
| if (initialValues.id) { | ||
| const result = await updateOpenIDProviderMutation(values); | ||
| if (isEditMode) { | ||
| // Strip empty credential fields — the backend preserves existing encrypted | ||
| // values when client_id / client_secret are absent from the payload. | ||
| const { | ||
| client_id: clientId, | ||
| client_secret: clientSecret, | ||
| ...rest | ||
| } = values; | ||
| const payload = { | ||
| ...rest, | ||
| ...(clientId ? { client_id: clientId } : {}), | ||
| ...(clientSecret ? { client_secret: clientSecret } : {}), | ||
| }; | ||
| const result = await updateOpenIDProviderMutation(payload); | ||
|
Comment on lines
+237
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The truthy check for credential stripping works correctly for the "keep existing" case. One edge case to be aware of: a user could provide a new
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Backend will support optional updates after https://github.com/ethyca/fidesplus/pull/3284/changes |
||
| handleResult(result); | ||
| } else { | ||
| const result = await createOpenIDProviderMutationTrigger(values); | ||
|
|
@@ -274,7 +297,7 @@ const SSOProviderForm = ({ | |
| initialValues={initialValues} | ||
| enableReinitialize | ||
| onSubmit={handleSubmit} | ||
| validationSchema={SSOProviderFormValidationSchema} | ||
| validationSchema={validationSchema} | ||
| > | ||
| {({ dirty, isValid, values }) => ( | ||
| <Form data-testid="openIDProvider-form"> | ||
|
|
@@ -293,7 +316,7 @@ const SSOProviderForm = ({ | |
| tooltip="Unique identifier for your provider" | ||
| variant="stacked" | ||
| isRequired | ||
| disabled={!!initialValues.id} | ||
| disabled={isEditMode} | ||
| /> | ||
| <CustomTextInput | ||
| id="name" | ||
|
|
@@ -310,7 +333,10 @@ const SSOProviderForm = ({ | |
| type="password" | ||
| tooltip="Client ID for your provider" | ||
| variant="stacked" | ||
| isRequired | ||
| isRequired={!isEditMode} | ||
| placeholder={ | ||
| isEditMode ? "Leave blank to keep existing" : undefined | ||
| } | ||
| /> | ||
| <CustomTextInput | ||
| id="client_secret" | ||
|
|
@@ -319,7 +345,10 @@ const SSOProviderForm = ({ | |
| type="password" | ||
| tooltip="Client secret for your provider" | ||
| variant="stacked" | ||
| isRequired | ||
| isRequired={!isEditMode} | ||
| placeholder={ | ||
| isEditMode ? "Leave blank to keep existing" : undefined | ||
| } | ||
| /> | ||
| {values.provider === "azure" && renderAzureProviderExtraFields()} | ||
| {values.provider === "okta" && renderOktaProviderExtraFields()} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { getSSOProviderFormValidationSchema } from "../SSOProviderForm"; | ||
|
|
||
| const BASE_VALUES = { | ||
| provider: "google", | ||
| name: "My Provider", | ||
| }; | ||
|
|
||
| describe("getSSOProviderFormValidationSchema", () => { | ||
| describe("create mode", () => { | ||
| const schema = getSSOProviderFormValidationSchema(false); | ||
|
|
||
| it("requires client_id", async () => { | ||
| await expect( | ||
| schema.validateAt("client_id", { ...BASE_VALUES, client_id: "" }), | ||
| ).rejects.toThrow("Client ID is a required field"); | ||
| }); | ||
|
|
||
| it("requires client_secret", async () => { | ||
| await expect( | ||
| schema.validateAt("client_secret", { | ||
| ...BASE_VALUES, | ||
| client_secret: "", | ||
| }), | ||
| ).rejects.toThrow("Client Secret is a required field"); | ||
| }); | ||
|
|
||
| it("passes when credentials are provided", async () => { | ||
| await expect( | ||
| schema.validate({ | ||
| ...BASE_VALUES, | ||
| client_id: "my-client-id", | ||
| client_secret: "my-secret", | ||
| }), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("edit mode", () => { | ||
| const schema = getSSOProviderFormValidationSchema(true); | ||
|
|
||
| it("allows empty client_id", async () => { | ||
| await expect( | ||
| schema.validateAt("client_id", { ...BASE_VALUES, client_id: "" }), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("allows empty client_secret", async () => { | ||
| await expect( | ||
| schema.validateAt("client_secret", { | ||
| ...BASE_VALUES, | ||
| client_secret: "", | ||
| }), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("allows undefined client_id and client_secret", async () => { | ||
| await expect(schema.validate(BASE_VALUES)).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("accepts provided credentials (rotation)", async () => { | ||
| await expect( | ||
| schema.validate({ | ||
| ...BASE_VALUES, | ||
| client_id: "new-client-id", | ||
| client_secret: "new-secret", | ||
| }), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
| }); | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good coverage of the validation schema in both modes. The payload-stripping logic in the submit handler (the |
||
Uh oh!
There was an error while loading. Please reload this page.