From a8f8f087d3ba622970ff9075beab2a1693e3a7d3 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:14:56 +0100 Subject: [PATCH] [Fleet] fix validation of output secret fields (#172795) ## Summary Closes https://github.com/elastic/kibana/issues/172481 Fixed validation of secret output fields. Updated cypress tests that validates output secrets. Create a new remote elasticsearch output and verify that service token field is required when clicking Save without filling it in. image Same for Logstash ssl key: image Kafka password and ssl key: image In edit mode, when the secret field is empty, the validation is shown again and goes away when clicking on Cancel changes. image image ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../cypress/e2e/fleet_settings_outputs.cy.ts | 79 ++++++++++++++++++- .../fleet/cypress/screens/fleet_outputs.ts | 11 +++ .../components/edit_output_flyout/index.tsx | 1 + .../output_form_kafka_authentication.tsx | 2 + .../output_form_remote_es.tsx | 1 + .../output_form_secret_form_row.test.tsx | 14 +++- .../output_form_secret_form_row.tsx | 15 +++- .../output_form_validators.tsx | 4 +- 8 files changed, 118 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts index 28b124116502a..5101c4d93fd39 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts @@ -21,6 +21,7 @@ import { loadESOutput, loadKafkaOutput, loadLogstashOutput, + loadRemoteESOutput, resetKafkaOutputForm, selectESOutput, selectKafkaOutput, @@ -134,6 +135,80 @@ describe('Outputs', () => { }); }); + describe('Remote ES', () => { + it('displays proper error messages', () => { + selectRemoteESOutput(); + cy.getBySel(SETTINGS_SAVE_BTN).click(); + + cy.contains('Name is required'); + cy.contains('URL is required'); + cy.contains('Service Token is required'); + shouldDisplayError(SETTINGS_OUTPUTS.NAME_INPUT); + shouldDisplayError('serviceTokenSecretInput'); + }); + + describe('Form submit', () => { + let outputId: string; + + before(() => { + interceptOutputId((id) => { + outputId = id; + }); + }); + + after(() => { + cleanupOutput(outputId); + }); + + it('saves the output', () => { + selectRemoteESOutput(); + + cy.getBySel(SETTINGS_OUTPUTS.NAME_INPUT).type('name'); + cy.get('[placeholder="Specify host URL"').clear().type('https://localhost:5000'); + cy.getBySel('serviceTokenSecretInput').type('service_token'); + + cy.intercept('POST', '**/api/fleet/outputs').as('saveOutput'); + + cy.getBySel(SETTINGS_SAVE_BTN).click(); + + cy.wait('@saveOutput').then((interception) => { + const responseBody = interception.response?.body; + cy.visit(`/app/fleet/settings/outputs/${responseBody?.item?.id}`); + expect(responseBody?.item.service_token).to.equal(undefined); + expect(responseBody?.item.secrets.service_token.id).not.to.equal(undefined); + }); + + cy.get('[placeholder="Specify host URL"').should('have.value', 'https://localhost:5000'); + cy.getBySel(SETTINGS_OUTPUTS.NAME_INPUT).should('have.value', 'name'); + }); + }); + + describe('Form edit', () => { + let outputId: string; + + before(() => { + loadRemoteESOutput().then((data) => { + outputId = data.item.id; + }); + }); + after(() => { + cleanupOutput(outputId); + }); + + it('edits the output', () => { + visit(`/app/fleet/settings/outputs/${outputId}`); + + cy.get('[placeholder="Specify host URL"').clear().type('https://localhost:5001'); + + cy.getBySel(SETTINGS_SAVE_BTN).click(); + cy.getBySel(SETTINGS_CONFIRM_MODAL_BTN).click(); + visit(`/app/fleet/settings/outputs/${outputId}`); + + cy.get('[placeholder="Specify host URL"').should('have.value', 'https://localhost:5001'); + }); + }); + }); + describe('Kafka', () => { describe('Form validation', () => { it('renders all form fields', () => { @@ -301,7 +376,7 @@ describe('Outputs', () => { cy.contains('Name is required'); cy.contains('Host is required'); cy.contains('Username is required'); - // cy.contains('Password is required'); + cy.contains('Password is required'); cy.contains('Default topic is required'); cy.contains('Topic is required'); cy.contains( @@ -310,7 +385,7 @@ describe('Outputs', () => { cy.contains('Must be a key, value pair i.e. "http.response.code: 200"'); shouldDisplayError(SETTINGS_OUTPUTS.NAME_INPUT); shouldDisplayError(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_INPUT); - // shouldDisplayError(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_PASSWORD_INPUT); // TODO + shouldDisplayError(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_PASSWORD_INPUT); shouldDisplayError(SETTINGS_OUTPUTS_KAFKA.TOPICS_DEFAULT_TOPIC_INPUT); shouldDisplayError(SETTINGS_OUTPUTS_KAFKA.TOPICS_CONDITION_INPUT); shouldDisplayError(SETTINGS_OUTPUTS_KAFKA.TOPICS_TOPIC_INPUT); diff --git a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts index e1d1a3530df9a..e439961e287d7 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts @@ -84,6 +84,17 @@ export const loadESOutput = () => hosts: ['https://bla.co'], }); +export const loadRemoteESOutput = () => + loadOutput({ + name: 'remote_es', + type: 'remote_elasticsearch', + is_default: false, + is_default_monitoring: false, + hosts: ['https://bla.co'], + secrets: { service_token: 'token' }, + preset: 'balanced', + }); + export const loadLogstashOutput = () => loadOutput({ name: 'ls', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx index d9fa988aa55cc..3023cf1397faa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx @@ -227,6 +227,7 @@ export const EditOutputFlyout: React.FunctionComponent = })} {...inputs.sslKeySecretInput.formRowProps} onUsePlainText={onUsePlainText} + cancelEdit={inputs.sslKeySecretInput.cancelEdit} > = (props) defaultMessage: 'Service Token', })} {...inputs.serviceTokenSecretInput.formRowProps} + cancelEdit={inputs.serviceTokenSecretInput.cancelEdit} onUsePlainText={onUsePlainText} > { const initialValue = 'initial value'; const clear = jest.fn(); const onUsePlainText = jest.fn(); + const cancelEdit = jest.fn(); it('should switch to edit mode when the replace button is clicked', () => { const { getByText, queryByText, container } = render( @@ -23,6 +24,7 @@ describe('SecretFormRow', () => { initialValue={initialValue} clear={clear} onUsePlainText={onUsePlainText} + cancelEdit={cancelEdit} > @@ -38,13 +40,14 @@ describe('SecretFormRow', () => { expect(queryByText(initialValue)).not.toBeInTheDocument(); }); - it('should call the clear function when the cancel button is clicked', () => { + it('should call the cancelEdit function when the cancel button is clicked', () => { const { getByText } = render( @@ -53,12 +56,17 @@ describe('SecretFormRow', () => { fireEvent.click(getByText('Replace Test Secret')); fireEvent.click(getByText('Cancel Test Secret change')); - expect(clear).toHaveBeenCalled(); + expect(cancelEdit).toHaveBeenCalled(); }); it('should call the onUsePlainText function when the revert link is clicked', () => { const { getByText } = render( - + ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx index fc55835557403..f483503af9e43 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx @@ -29,7 +29,18 @@ export const SecretFormRow: React.FC<{ clear: () => void; initialValue?: any; onUsePlainText: () => void; -}> = ({ fullWidth, error, isInvalid, children, clear, title, initialValue, onUsePlainText }) => { + cancelEdit: () => void; +}> = ({ + fullWidth, + error, + isInvalid, + children, + clear, + title, + initialValue, + onUsePlainText, + cancelEdit, +}) => { const hasInitialValue = initialValue !== undefined; const [editMode, setEditMode] = useState(!initialValue); const valueHiddenPanel = ( @@ -66,7 +77,7 @@ export const SecretFormRow: React.FC<{ { setEditMode(false); - clear(); + cancelEdit(); }} color="primary" iconType="refresh" diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx index 28218fed7e942..26e63803df0e7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx @@ -11,11 +11,11 @@ import { safeLoad } from 'js-yaml'; const toSecretValidator = (validator: (value: string) => string[] | undefined) => (value: string | { id: string } | undefined) => { - if (!value || typeof value === 'object') { + if (typeof value === 'object') { return undefined; } - return validator(value); + return validator(value ?? ''); }; export function validateKafkaHosts(value: string[]) {