From 00c44a965de84f731f39fdb8862f0d837854851c Mon Sep 17 00:00:00 2001 From: Yibo Wang Date: Tue, 30 Aug 2022 08:03:58 -0700 Subject: [PATCH] Refactor of credential editing page layout & refactor backend field validation method --- .../create_credential_form.tsx | 6 +- .../common/components/header/header.tsx | 9 +- .../components/edit_credential.tsx | 338 ++++++++++++------ .../edit_credential_wizard.tsx | 6 +- .../data_source/common/credentials/types.ts | 2 +- ...credential_saved_objects_client_wrapper.ts | 56 +-- 6 files changed, 275 insertions(+), 142 deletions(-) diff --git a/src/plugins/credential_management/public/components/common/components/create_credential_form/create_credential_form.tsx b/src/plugins/credential_management/public/components/common/components/create_credential_form/create_credential_form.tsx index 592a8d9dc524..91971f9b9b6f 100644 --- a/src/plugins/credential_management/public/components/common/components/create_credential_form/create_credential_form.tsx +++ b/src/plugins/credential_management/public/components/common/components/create_credential_form/create_credential_form.tsx @@ -16,6 +16,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import { DocLinksStart } from 'src/core/public'; import { context as contextType } from '../../../../../../opensearch_dashboards_react/public'; @@ -150,11 +151,14 @@ export class CredentialForm extends React.Component; + return
; } renderContent = () => { diff --git a/src/plugins/credential_management/public/components/common/components/header/header.tsx b/src/plugins/credential_management/public/components/common/components/header/header.tsx index 95cccfa99c2d..a78e24e45aa3 100644 --- a/src/plugins/credential_management/public/components/common/components/header/header.tsx +++ b/src/plugins/credential_management/public/components/common/components/header/header.tsx @@ -16,23 +16,22 @@ import { CredentialManagementContext } from '../../../../types'; export const Header = ({ prompt, docLinks, + headerTitle, }: { prompt?: React.ReactNode; docLinks: DocLinksStart; + headerTitle: string; }) => { const changeTitle = useOpenSearchDashboards().services.chrome .docTitle.change; - const createCredentialHeader = i18n.translate('credentialManagement.createIndexPatternHeader', { - defaultMessage: 'Create Stored Credential', - }); - changeTitle(createCredentialHeader); + changeTitle(headerTitle); return (

- {createCredentialHeader} + {headerTitle} <> {' '} { - const { savedObjects } = this.context.services; - this.setState({ isLoading: true }); - try { - await savedObjects.client.delete(CREDENTIAL_SAVED_OBJECT_TYPE, this.props.credential.id); - this.props.history.push(''); - } catch (e) { - const deleteCredentialFailMsg = ( - - ); - this.setState((prevState) => ({ - toasts: prevState.toasts.concat([ - { - title: deleteCredentialFailMsg, - id: deleteCredentialFailMsg.props.id, - color: 'warning', - iconType: 'alert', - }, - ]), - })); - } - this.setState({ isLoading: false }); - }; - delelteButtonRender() { return ( <> -
- - - - - -
+ {/* */} + + + + + + - {this.state.isVisible ? ( + {this.state.isDeleteModalVisible ? ( { - this.setState({ isVisible: false }); + this.setState({ isDeleteModalVisible: false }); }} onConfirm={this.confirmDelete} cancelButtonText={LocalizedContent.cancelButtonOnDeleteCancelText} @@ -148,87 +127,156 @@ export class EditCredentialComponent extends React.Component< ); } - renderContent() { + updatePasswordRender() { + const closeModal = () => this.setState({ isUpdateModalVisible: false }); + return ( + <> + Update Password + + {this.state.isUpdateModalVisible ? ( + + + +

Update password

+
+
+ + + this.setState({ password: e.target.value })} + /> + + + + Cancel + this.updateCredential(true)} + fill + > + Update + + +
+ ) : null} + + ); + } + + renderContent = () => { const options = [ { value: CredentialMaterialsType.UsernamePasswordType, text: 'Username and Password Credential', }, ]; - return ( - + {this.delelteButtonRender()} - - + + + + + +

Save Credentials

+
+
+
+ + Credential Name

} - description={

The name of credential that you want to create

} + title={

Credential Details

} + description={ +

+ The credential information is used for reference in tables and when adding to a data + source connection +

+ } > - + this.setState({ credentialName: e.target.value })} /> + + this.setState({ credentialDescription: e.target.value })} + /> + + +
+ + + + +

Authentication Details

+
+
+
+ + Credential Type} + title={

Authentication Details

} description={ -
-

- The type of credential that you want to create{' '} - - Credential Types Supported - -

-
    -
  • - For username_password_credential type: this type can be used for{' '} - credentials in format of username, password.{' '} -
  • -
  • Ex: OpenSearch basic auth
  • -
-
    -
  • - For aws_iam_credential type: this type can only be used for aws iam - credential, with aws_access_key_id, aws_secret_access_key, and region (optional) -
  • -
-
+

Modify these to update the authentication type and associated details

} > - - this.setState({ credentialMaterialsType: e.target.value })} - options={options} - /> + + Username & password - + + + this.setState({ username: e.target.value })} /> - this.setState({ password: e.target.value })} - /> + + + ********* + + {this.updatePasswordRender()} +
- - Update - - -
+ + {/* bottom bar implementation */} + + + + + Cancel changes + + + + this.updateCredential(false)} + > + Save changes + + + + + + ); - } + }; removeToast = (id: string) => { this.setState((prevState) => ({ @@ -258,24 +306,63 @@ export class EditCredentialComponent extends React.Component< } removeCredential = async () => { - this.setState({ isVisible: true }); + this.setState({ isDeleteModalVisible: true }); + }; + + updateCredentialPassword = async () => { + this.setState({ isUpdateModalVisible: true }); }; - updateCredential = async () => { + updateCredential = async (isUpdatePassword: boolean) => { const { savedObjects } = this.context.services; - this.setState({ isLoading: true }); + const { originalCredential } = this.props; + + this.setState({ isLoading: true, isUpdateModalVisible: false }); + try { - await savedObjects.client.update('credential', this.props.credential.id, { - title: this.state.credentialName, + const credentialAttributes = { + title: isUpdatePassword ? originalCredential.title : this.state.credentialName, + description: isUpdatePassword + ? originalCredential.description + : this.state.credentialDescription, credentialMaterials: { credentialMaterialsType: this.state.credentialMaterialsType, credentialMaterialsContent: { - username: this.state.username, + username: isUpdatePassword ? originalCredential.username : this.state.username, password: this.state.password, }, }, - }); - this.props.history.push(''); + }; + if (!isUpdatePassword) { + delete credentialAttributes.credentialMaterials.credentialMaterialsContent.password; + } + await savedObjects.client.update( + CREDENTIAL_SAVED_OBJECT_TYPE, + this.props.credential.id, + credentialAttributes + ); + if (isUpdatePassword) { + this.setState({ password: '' }); + + const editCredentialSuccessMsg = ( + + ); + this.setState((prevState) => ({ + toasts: prevState.toasts.concat([ + { + title: editCredentialSuccessMsg, + id: editCredentialSuccessMsg.props.id, + color: 'success', + iconType: 'check', + }, + ]), + })); + } else { + this.props.history.push(''); + } } catch (e) { const editCredentialFailMsg = ( { + const { savedObjects } = this.context.services; + this.setState({ isLoading: true }); + try { + await savedObjects.client.delete(CREDENTIAL_SAVED_OBJECT_TYPE, this.props.credential.id); + this.props.history.push(''); + } catch (e) { + const deleteCredentialFailMsg = ( + + ); + this.setState((prevState) => ({ + toasts: prevState.toasts.concat([ + { + title: deleteCredentialFailMsg, + id: deleteCredentialFailMsg.props.id, + color: 'warning', + iconType: 'alert', + }, + ]), + })); + } + this.setState({ isLoading: false }); + }; } export const EditCredential = withRouter(EditCredentialComponent); diff --git a/src/plugins/credential_management/public/components/edit_credential_wizard/edit_credential_wizard.tsx b/src/plugins/credential_management/public/components/edit_credential_wizard/edit_credential_wizard.tsx index 8975c5542134..fe36a05c355e 100644 --- a/src/plugins/credential_management/public/components/edit_credential_wizard/edit_credential_wizard.tsx +++ b/src/plugins/credential_management/public/components/edit_credential_wizard/edit_credential_wizard.tsx @@ -28,6 +28,7 @@ const EditCredentialWizard: React.FunctionComponent(); + const [originalCredential, setOriginalCredential] = useState(); const [toasts, setToasts] = useState([]); /* Fetch credential by id*/ @@ -49,6 +50,7 @@ const EditCredentialWizard: React.FunctionComponent; + if (credential && originalCredential) { + return ; } else { return

Credential not found!

; } diff --git a/src/plugins/data_source/common/credentials/types.ts b/src/plugins/data_source/common/credentials/types.ts index 8888a1a1ba9f..4ee42e3d8f50 100644 --- a/src/plugins/data_source/common/credentials/types.ts +++ b/src/plugins/data_source/common/credentials/types.ts @@ -25,5 +25,5 @@ export interface CredentialMaterials extends SavedObjectAttributes { export interface UsernamePasswordTypedContent extends SavedObjectAttributes { username: string; - password: string; + password?: string; } diff --git a/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts b/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts index 4e116ce61023..829b96d40d9a 100644 --- a/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts +++ b/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts @@ -20,6 +20,7 @@ import { SavedObjectsErrorHelpers } from '../../../../core/server'; import { CryptographyClient } from '../cryptography'; import { CredentialMaterialsType, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common'; +import { credential } from './credential_saved_objects_type'; /** * Describes the Credential Saved Objects Client Wrapper class, @@ -138,15 +139,14 @@ export class CredentialSavedObjectsClientWrapper { } private async validateAndEncryptPartialAttributes(attributes: T) { - this.validateCredentialMaterials(attributes.credentialMaterials); + this.validateAttributes(attributes); return await this.encryptCredentialMaterials(attributes); } private validateAttributes(attributes: T) { const { title, credentialMaterials } = attributes; - - if (title === undefined) { + if (!title) { throw SavedObjectsErrorHelpers.createBadRequestError('attribute "title" required'); } @@ -175,33 +175,33 @@ export class CredentialSavedObjectsClientWrapper { } } - private validateUsernamePasswordTypedContent(credentialMaterialsContent: T) { - const { username, password } = credentialMaterialsContent; - - if (username === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "username" required'); - } - - if (password === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "password" required'); - } - - return; - } - private async encryptCredentialMaterials(attributes: T) { const { credentialMaterials } = attributes; const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials; + const { username, password } = credentialMaterialsContent; switch (credentialMaterialsType) { case CredentialMaterialsType.UsernamePasswordType: - this.validateUsernamePasswordTypedContent(credentialMaterialsContent); + this.validateUsername(username); + if (password !== undefined) { + this.validatePassword(password); + + return { + ...attributes, + credentialMaterials: await this.encryptUsernamePasswordTypedCredentialMaterials( + credentialMaterials + ), + }; + } return { ...attributes, - credentialMaterials: await this.encryptUsernamePasswordTypedCredentialMaterials( - credentialMaterials - ), + credentialMaterials: { + credentialMaterialsType, + credentialMaterialsContent: { + username, + }, + }, }; default: throw SavedObjectsErrorHelpers.createBadRequestError( @@ -210,6 +210,20 @@ export class CredentialSavedObjectsClientWrapper { } } + private validateUsername(username: T) { + if (!username) { + throw SavedObjectsErrorHelpers.createBadRequestError('attribute "username" required'); + } + return; + } + + private validatePassword(password: T) { + if (!password) { + throw SavedObjectsErrorHelpers.createBadRequestError('attribute "password" required'); + } + return; + } + private async encryptUsernamePasswordTypedCredentialMaterials( credentialMaterials: T ) {