From c553fe40b98a1fd82c88aa52c3c2f1fe72a7f47b Mon Sep 17 00:00:00 2001 From: Louis Chu Date: Thu, 1 Sep 2022 09:22:13 -0700 Subject: [PATCH 1/3] Merge credential object into data source --- .../data_source/common/credentials/index.ts | 11 - .../data_source/common/credentials/types.ts | 29 --- .../data_source/common/data_sources/types.ts | 14 ++ src/plugins/data_source/common/index.ts | 8 - src/plugins/data_source/public/index.ts | 8 - src/plugins/data_source/server/plugin.ts | 17 +- .../credential_saved_objects_type.ts | 36 ---- ...ta_source_saved_objects_client_wrapper.ts} | 190 ++++++++++-------- .../data_source/server/saved_objects/index.ts | 3 +- 9 files changed, 129 insertions(+), 187 deletions(-) delete mode 100644 src/plugins/data_source/common/credentials/index.ts delete mode 100644 src/plugins/data_source/common/credentials/types.ts delete mode 100644 src/plugins/data_source/server/saved_objects/credential_saved_objects_type.ts rename src/plugins/data_source/server/saved_objects/{credential_saved_objects_client_wrapper.ts => data_source_saved_objects_client_wrapper.ts} (54%) diff --git a/src/plugins/data_source/common/credentials/index.ts b/src/plugins/data_source/common/credentials/index.ts deleted file mode 100644 index f8ecfe9f9262..000000000000 --- a/src/plugins/data_source/common/credentials/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { - CredentialMaterialsType, - CredentialSavedObjectAttributes, - CredentialMaterials, - UsernamePasswordTypedContent, -} from './types'; diff --git a/src/plugins/data_source/common/credentials/types.ts b/src/plugins/data_source/common/credentials/types.ts deleted file mode 100644 index 8888a1a1ba9f..000000000000 --- a/src/plugins/data_source/common/credentials/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SavedObjectAttributes } from 'src/core/types'; - -/** - * Each credential's materials type. For the time being, only username/password pairs are supported. - */ -export enum CredentialMaterialsType { - UsernamePasswordType = 'username_password', -} - -export interface CredentialSavedObjectAttributes extends SavedObjectAttributes { - title: string; - credentialMaterials: CredentialMaterials; - description?: string; -} - -export interface CredentialMaterials extends SavedObjectAttributes { - credentialMaterialsType: CredentialMaterialsType; - credentialMaterialsContent: UsernamePasswordTypedContent; -} - -export interface UsernamePasswordTypedContent extends SavedObjectAttributes { - username: string; - password: string; -} diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index 2c4c18554adb..0005efd490b2 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -7,6 +7,20 @@ import { SavedObjectAttributes } from 'src/core/types'; export interface DataSourceAttributes extends SavedObjectAttributes { title: string; + description?: string; endpoint: string; noAuth: boolean; + credentials?: { + type: CredentialsType; + credentialsContent: UsernamePasswordTypedContent; + }; +} + +export interface UsernamePasswordTypedContent extends SavedObjectAttributes { + username: string; + password: string; +} + +export enum CredentialsType { + UsernamePasswordType = 'username_password', } diff --git a/src/plugins/data_source/common/index.ts b/src/plugins/data_source/common/index.ts index 168de1a6e3e6..a98825eb97f0 100644 --- a/src/plugins/data_source/common/index.ts +++ b/src/plugins/data_source/common/index.ts @@ -6,11 +6,3 @@ export const PLUGIN_ID = 'dataSource'; export const PLUGIN_NAME = 'data_source'; export const DATA_SOURCE_SAVED_OBJECT_TYPE = 'data-source'; -export const CREDENTIAL_SAVED_OBJECT_TYPE = 'credential'; - -export { - CredentialMaterialsType, - CredentialSavedObjectAttributes, - CredentialMaterials, - UsernamePasswordTypedContent, -} from './credentials'; diff --git a/src/plugins/data_source/public/index.ts b/src/plugins/data_source/public/index.ts index 05324bbc42af..411838d0b1bd 100644 --- a/src/plugins/data_source/public/index.ts +++ b/src/plugins/data_source/public/index.ts @@ -12,11 +12,3 @@ export function plugin() { } export { DataSourcePublicPluginSetup, DataSourcePublicPluginStart } from './types'; - -export { - CredentialMaterialsType, - CredentialSavedObjectAttributes, - CredentialMaterials, - UsernamePasswordTypedContent, - CREDENTIAL_SAVED_OBJECT_TYPE, -} from '../common'; diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index 0c75370af122..e2b1275a5079 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -23,8 +23,10 @@ import { DataSourcePluginConfigType } from '../config'; import { LoggingAuditor } from './audit/logging_auditor'; import { CryptographyClient } from './cryptography'; import { DataSourceService, DataSourceServiceSetup } from './data_source_service'; -import { credential, CredentialSavedObjectsClientWrapper, dataSource } from './saved_objects'; +import { DataSourceSavedObjectsClientWrapper, dataSource } from './saved_objects'; import { DataSourcePluginSetup, DataSourcePluginStart } from './types'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../common'; + // eslint-disable-next-line @osd/eslint/no-restricted-paths import { ensureRawRequest } from '../../../../src/core/server/http/router'; export class DataSourcePlugin implements Plugin { @@ -41,9 +43,6 @@ export class DataSourcePlugin implements Plugin { - const createWithCredentialMaterialsEncryption = async ( + const createWithCredentialsEncryption = async ( type: string, attributes: T, options?: SavedObjectsCreateOptions ) => { - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { return await wrapperOptions.client.create(type, attributes, options); } @@ -50,7 +48,7 @@ export class CredentialSavedObjectsClientWrapper { return await wrapperOptions.client.create(type, encryptedAttributes, options); }; - const bulkCreateWithCredentialMaterialsEncryption = async ( + const bulkCreateWithCredentialsEncryption = async ( objects: Array>, options?: SavedObjectsCreateOptions ): Promise> => { @@ -58,7 +56,7 @@ export class CredentialSavedObjectsClientWrapper { objects.map(async (object) => { const { type, attributes } = object; - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { return object; } @@ -71,24 +69,24 @@ export class CredentialSavedObjectsClientWrapper { return await wrapperOptions.client.bulkCreate(objects, options); }; - const updateWithCredentialMaterialsEncryption = async ( + const updateWithCredentialsEncryption = async ( type: string, id: string, attributes: Partial, options: SavedObjectsUpdateOptions = {} ): Promise> => { - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { return await wrapperOptions.client.update(type, id, attributes, options); } - const encryptedAttributes: Partial = await this.validateAndEncryptPartialAttributes( + const encryptedAttributes: Partial = await this.validateAndUpdatePartialAttributes( attributes ); return await wrapperOptions.client.update(type, id, encryptedAttributes, options); }; - const bulkUpdateWithCredentialMaterialsEncryption = async ( + const bulkUpdateWithCredentialsEncryption = async ( objects: Array>, options?: SavedObjectsBulkUpdateOptions ): Promise> => { @@ -96,11 +94,11 @@ export class CredentialSavedObjectsClientWrapper { objects.map(async (object) => { const { type, attributes } = object; - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { return object; } - const encryptedAttributes: Partial = await this.validateAndEncryptPartialAttributes( + const encryptedAttributes: Partial = await this.validateAndUpdatePartialAttributes( attributes ); @@ -116,100 +114,139 @@ export class CredentialSavedObjectsClientWrapper { return { ...wrapperOptions.client, - create: createWithCredentialMaterialsEncryption, - bulkCreate: bulkCreateWithCredentialMaterialsEncryption, + create: createWithCredentialsEncryption, + bulkCreate: bulkCreateWithCredentialsEncryption, checkConflicts: wrapperOptions.client.checkConflicts, delete: wrapperOptions.client.delete, find: wrapperOptions.client.find, bulkGet: wrapperOptions.client.bulkGet, get: wrapperOptions.client.get, - update: updateWithCredentialMaterialsEncryption, - bulkUpdate: bulkUpdateWithCredentialMaterialsEncryption, + update: updateWithCredentialsEncryption, + bulkUpdate: bulkUpdateWithCredentialsEncryption, errors: wrapperOptions.client.errors, addToNamespaces: wrapperOptions.client.addToNamespaces, deleteFromNamespaces: wrapperOptions.client.deleteFromNamespaces, }; }; + private isValidUrl(endpoint: string) { + try { + return Boolean(new URL(endpoint)); + } catch (e) { + return false; + } + } + + private dropCredentials(attributes: Omit) { + return attributes; + } + private async validateAndEncryptAttributes(attributes: T) { this.validateAttributes(attributes); - return await this.encryptCredentialMaterials(attributes); + const { noAuth } = attributes; + + // Drop credentials when no Auth + if (!noAuth) { + return this.dropCredentials(attributes); + } + + const { type, credentialsContent } = attributes.credentials; + + switch (type) { + case CredentialsType.UsernamePasswordType: + const { username, password } = credentialsContent; + return { + ...attributes, + credentials: { + type, + credentialsContent: { + username, + password: await this.cryptographyClient.encryptAndEncode(password), + }, + }, + }; + default: + throw SavedObjectsErrorHelpers.createBadRequestError( + `Invalid credential materials type: '${type}'` + ); + } } - private async validateAndEncryptPartialAttributes(attributes: T) { - const { credentialMaterials } = attributes; - const { credentialMaterialsContent } = credentialMaterials; - - if ('password' in credentialMaterialsContent) { - this.validatePassword(credentialMaterialsContent.password); - return { - ...attributes, - credentialMaterials: await this.encryptUsernamePasswordTypedCredentialMaterials( - credentialMaterials - ), - }; - } else { - this.validateAttributes(attributes); + private async validateAndUpdatePartialAttributes(attributes: T) { + const { noAuth, credentials } = attributes; + + // Drop credentials when no Auth + if (!noAuth) { + return this.dropCredentials(attributes); } - return await this.encryptCredentialMaterials(attributes); + const { type, credentialsContent } = credentials; + + switch (type) { + case CredentialsType.UsernamePasswordType: + if ('password' in credentialsContent) { + const { username, password } = credentialsContent; + return { + ...attributes, + credentials: { + type, + credentialsContent: { + username, + password: await this.cryptographyClient.encryptAndEncode(password), + }, + }, + }; + } else { + return attributes; + } + default: + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid credentials type: '${type}'`); + } } private validateAttributes(attributes: T) { - const { title, credentialMaterials } = attributes; + const { title, endpoint, noAuth, credentials } = attributes; if (!title) { throw SavedObjectsErrorHelpers.createBadRequestError('attribute "title" required'); } - this.validateCredentialMaterials(credentialMaterials); + if (!this.isValidUrl(endpoint)) { + throw SavedObjectsErrorHelpers.createBadRequestError('attribute "endpoint" is not valid'); + } + + if (noAuth) { + this.validateCredentials(credentials); + } } - private validateCredentialMaterials(credentialMaterials: T) { - if (credentialMaterials === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialMaterials" required' - ); + private validateCredentials(credentials: T) { + if (credentials === undefined) { + throw SavedObjectsErrorHelpers.createBadRequestError('attribute "credentials" required'); } - const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials; + const { type, credentialsContent } = credentials; - if (credentialMaterialsType === undefined) { + if (!type) { throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialMaterialsType" required for "credentialMaterials"' + 'attribute "type" required for "credentials"' ); } - if (credentialMaterialsContent === undefined) { + if (credentialsContent === undefined) { throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialMaterialsContent" required for "credentialMaterials"' + 'attribute "credentialsContent" required for "credentials"' ); } - } - private async encryptCredentialMaterials(attributes: T) { - const { credentialMaterials } = attributes; + switch (type) { + case CredentialsType.UsernamePasswordType: + const { username, password } = credentialsContent; - const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials; - const { username } = credentialMaterialsContent; - - switch (credentialMaterialsType) { - case CredentialMaterialsType.UsernamePasswordType: this.validateUsername(username); - - return { - ...attributes, - credentialMaterials: { - credentialMaterialsType, - credentialMaterialsContent: { - username, - }, - }, - }; + this.validatePassword(password); default: - throw SavedObjectsErrorHelpers.createBadRequestError( - `Invalid credential materials type: '${credentialMaterialsType}'` - ); + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid credentials type: '${type}'`); } } @@ -226,19 +263,4 @@ export class CredentialSavedObjectsClientWrapper { } return; } - - private async encryptUsernamePasswordTypedCredentialMaterials( - credentialMaterials: T - ) { - const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials; - return { - credentialMaterialsType, - credentialMaterialsContent: { - username: credentialMaterialsContent.username, - password: await this.cryptographyClient.encryptAndEncode( - credentialMaterialsContent.password - ), - }, - }; - } } diff --git a/src/plugins/data_source/server/saved_objects/index.ts b/src/plugins/data_source/server/saved_objects/index.ts index bd1c8a6613a3..76a332b84ced 100644 --- a/src/plugins/data_source/server/saved_objects/index.ts +++ b/src/plugins/data_source/server/saved_objects/index.ts @@ -3,6 +3,5 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { credential } from './credential_saved_objects_type'; export { dataSource } from './data_source'; -export { CredentialSavedObjectsClientWrapper } from './credential_saved_objects_client_wrapper'; +export { DataSourceSavedObjectsClientWrapper } from './data_source_saved_objects_client_wrapper'; From 5b62400d9bcd5f2d378417bbf281f332d38831c2 Mon Sep 17 00:00:00 2001 From: Louis Chu Date: Thu, 1 Sep 2022 09:32:00 -0700 Subject: [PATCH 2/3] Merge credential object into data source Signed-off-by: Louis Chu --- .../server/client/configure_client.test.ts | 48 +++++++------------ .../server/client/configure_client.ts | 31 ++++-------- 2 files changed, 24 insertions(+), 55 deletions(-) diff --git a/src/plugins/data_source/server/client/configure_client.test.ts b/src/plugins/data_source/server/client/configure_client.test.ts index 11523f649685..63cc18d7a3cb 100644 --- a/src/plugins/data_source/server/client/configure_client.test.ts +++ b/src/plugins/data_source/server/client/configure_client.test.ts @@ -5,12 +5,8 @@ import { SavedObjectsClientContract } from '../../../../core/server'; import { loggingSystemMock, savedObjectsClientMock } from '../../../../core/server/mocks'; -import { DATA_SOURCE_SAVED_OBJECT_TYPE, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common'; -import { - CredentialMaterialsType, - CredentialSavedObjectAttributes, -} from '../../common/credentials/types'; -import { DataSourceAttributes } from '../../common/data_sources'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; +import { DataSourceAttributes, CredentialsType } from '../../common/data_sources/types'; import { DataSourcePluginConfigType } from '../../config'; import { ClientMock, parseClientOptionsMock } from './configure_client.test.mocks'; import { OpenSearchClientPoolSetup } from './client_pool'; @@ -21,7 +17,6 @@ import { opensearchClientMock } from '../../../../core/server/opensearch/client/ import { CryptographyClient } from '../cryptography'; const DATA_SOURCE_ID = 'a54b76ec86771ee865a0f74a305dfff8'; -const CREDENETIAL_ID = 'a54dsaadasfasfwe22d23d23d2453df3'; const cryptoClient = new CryptographyClient('test', 'test', new Array(32).fill(0)); // TODO: improve UT @@ -55,6 +50,13 @@ describe('configureClient', () => { title: 'title', endpoint: 'http://localhost', noAuth: false, + credentials: { + type: CredentialsType.UsernamePasswordType, + credentialsContent: { + username: 'username', + password: 'password', + }, + }, } as DataSourceAttributes; clientPoolSetup = { @@ -62,30 +64,12 @@ describe('configureClient', () => { addClientToPool: jest.fn(), }; - const crendentialAttr = { - title: 'cred', - credentialMaterials: { - credentialMaterialsType: CredentialMaterialsType.UsernamePasswordType, - credentialMaterialsContent: { - username: 'username', - password: 'password', - }, - }, - } as CredentialSavedObjectAttributes; - - savedObjectsMock.get - .mockResolvedValueOnce({ - id: DATA_SOURCE_ID, - type: DATA_SOURCE_SAVED_OBJECT_TYPE, - attributes: dataSourceAttr, - references: [{ name: 'user', type: CREDENTIAL_SAVED_OBJECT_TYPE, id: CREDENETIAL_ID }], - }) - .mockResolvedValueOnce({ - id: CREDENETIAL_ID, - type: CREDENTIAL_SAVED_OBJECT_TYPE, - attributes: crendentialAttr, - references: [], - }); + savedObjectsMock.get.mockResolvedValueOnce({ + id: DATA_SOURCE_ID, + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + attributes: dataSourceAttr, + references: [], + }); ClientMock.mockImplementation(() => { return dsClient; @@ -135,7 +119,7 @@ describe('configureClient', () => { ); expect(ClientMock).toHaveBeenCalledTimes(1); - expect(savedObjectsMock.get).toHaveBeenCalledTimes(2); + expect(savedObjectsMock.get).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1); expect(client).toBe(dsClient.child.mock.results[0].value); }); diff --git a/src/plugins/data_source/server/client/configure_client.ts b/src/plugins/data_source/server/client/configure_client.ts index e8af67809981..e24cfe7b9930 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -10,12 +10,9 @@ import { SavedObjectsClientContract, SavedObjectsErrorHelpers, } from '../../../../../src/core/server'; -import { DATA_SOURCE_SAVED_OBJECT_TYPE, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common'; -import { - CredentialSavedObjectAttributes, - UsernamePasswordTypedContent, -} from '../../common/credentials/types'; -import { DataSourceAttributes } from '../../common/data_sources'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; + +import { DataSourceAttributes, UsernamePasswordTypedContent } from '../../common/data_sources'; import { DataSourcePluginConfigType } from '../../config'; import { CryptographyClient } from '../cryptography'; import { parseClientOptions } from './client_config'; @@ -32,7 +29,7 @@ export const configureClient = async ( const dataSource = await getDataSource(dataSourceId, savedObjects); const rootClient = getRootClient(dataSource.attributes, config, openSearchClientPoolSetup); - return getQueryClient(rootClient, dataSource, savedObjects, cryptographyClient); + return getQueryClient(rootClient, dataSource, cryptographyClient); }; export const getDataSource = async ( @@ -52,19 +49,11 @@ export const getDataSource = async ( }; export const getCredential = async ( - credentialId: string, - savedObjects: SavedObjectsClientContract, + dataSource: SavedObject, cryptographyClient: CryptographyClient ): Promise => { try { - const credentialSavedObject = await savedObjects.get( - CREDENTIAL_SAVED_OBJECT_TYPE, - credentialId - ); - const { - username, - password, - } = credentialSavedObject.attributes.credentialMaterials.credentialMaterialsContent; + const { username, password } = dataSource.attributes.credentials!.credentialsContent; const decodedPassword = await cryptographyClient.decodeAndDecrypt(password); const credential = { username, @@ -89,17 +78,13 @@ export const getCredential = async ( const getQueryClient = async ( rootClient: Client, dataSource: SavedObject, - savedObjects: SavedObjectsClientContract, cryptographyClient: CryptographyClient ): Promise => { if (dataSource.attributes.noAuth) { return rootClient.child(); } else { - const credential = await getCredential( - dataSource.references[0].id, - savedObjects, - cryptographyClient - ); + const credential = await getCredential(dataSource, cryptographyClient); + return getBasicAuthClient(rootClient, credential); } }; From ce7005fcfd8de39d8651d5d2add1f96aaba89c72 Mon Sep 17 00:00:00 2001 From: Louis Chu Date: Thu, 1 Sep 2022 11:28:08 -0700 Subject: [PATCH 3/3] Revise data source saved object model Signed-off-by: Louis Chu --- .../data_source/common/data_sources/types.ts | 10 +- .../server/client/configure_client.test.ts | 20 ++- .../server/client/configure_client.ts | 12 +- ...ata_source_saved_objects_client_wrapper.ts | 154 +++++++++--------- 4 files changed, 104 insertions(+), 92 deletions(-) diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index 0005efd490b2..afcf3d662fed 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -9,10 +9,9 @@ export interface DataSourceAttributes extends SavedObjectAttributes { title: string; description?: string; endpoint: string; - noAuth: boolean; - credentials?: { - type: CredentialsType; - credentialsContent: UsernamePasswordTypedContent; + auth: { + type: AuthType; + credentials: UsernamePasswordTypedContent | undefined; }; } @@ -21,6 +20,7 @@ export interface UsernamePasswordTypedContent extends SavedObjectAttributes { password: string; } -export enum CredentialsType { +export enum AuthType { + NoAuth = 'no_auth', UsernamePasswordType = 'username_password', } diff --git a/src/plugins/data_source/server/client/configure_client.test.ts b/src/plugins/data_source/server/client/configure_client.test.ts index 63cc18d7a3cb..f8f8f2cb5802 100644 --- a/src/plugins/data_source/server/client/configure_client.test.ts +++ b/src/plugins/data_source/server/client/configure_client.test.ts @@ -6,7 +6,7 @@ import { SavedObjectsClientContract } from '../../../../core/server'; import { loggingSystemMock, savedObjectsClientMock } from '../../../../core/server/mocks'; import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; -import { DataSourceAttributes, CredentialsType } from '../../common/data_sources/types'; +import { DataSourceAttributes, AuthType } from '../../common/data_sources/types'; import { DataSourcePluginConfigType } from '../../config'; import { ClientMock, parseClientOptionsMock } from './configure_client.test.mocks'; import { OpenSearchClientPoolSetup } from './client_pool'; @@ -49,10 +49,9 @@ describe('configureClient', () => { dataSourceAttr = { title: 'title', endpoint: 'http://localhost', - noAuth: false, - credentials: { - type: CredentialsType.UsernamePasswordType, - credentialsContent: { + auth: { + type: AuthType.UsernamePasswordType, + credentials: { username: 'username', password: 'password', }, @@ -80,11 +79,16 @@ describe('configureClient', () => { ClientMock.mockReset(); }); - test('configure client with noAuth == true, will call new Client() to create client', async () => { + test('configure client with auth.type == no_auth, will call new Client() to create client', async () => { savedObjectsMock.get.mockReset().mockResolvedValueOnce({ id: DATA_SOURCE_ID, type: DATA_SOURCE_SAVED_OBJECT_TYPE, - attributes: { ...dataSourceAttr, noAuth: true }, + attributes: { + ...dataSourceAttr, + auth: { + type: AuthType.NoAuth, + }, + }, references: [], }); @@ -106,7 +110,7 @@ describe('configureClient', () => { expect(client).toBe(dsClient.child.mock.results[0].value); }); - test('configure client with noAuth == false, will first call decrypt()', async () => { + test('configure client with auth.type == username_password, will first call decrypt()', async () => { const spy = jest.spyOn(cryptoClient, 'decodeAndDecrypt').mockResolvedValue('password'); const client = await configureClient( diff --git a/src/plugins/data_source/server/client/configure_client.ts b/src/plugins/data_source/server/client/configure_client.ts index e24cfe7b9930..5ba18261ba74 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -12,7 +12,11 @@ import { } from '../../../../../src/core/server'; import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; -import { DataSourceAttributes, UsernamePasswordTypedContent } from '../../common/data_sources'; +import { + AuthType, + DataSourceAttributes, + UsernamePasswordTypedContent, +} from '../../common/data_sources'; import { DataSourcePluginConfigType } from '../../config'; import { CryptographyClient } from '../cryptography'; import { parseClientOptions } from './client_config'; @@ -53,7 +57,7 @@ export const getCredential = async ( cryptographyClient: CryptographyClient ): Promise => { try { - const { username, password } = dataSource.attributes.credentials!.credentialsContent; + const { username, password } = dataSource.attributes.auth.credentials!; const decodedPassword = await cryptographyClient.decodeAndDecrypt(password); const credential = { username, @@ -72,7 +76,7 @@ export const getCredential = async ( * * @param rootClient root client for the connection with given data source endpoint. * @param dataSource data source saved object - * @param savedObjects scoped saved object client + * @param cryptographyClient cryptography client for password encryption / decrpytion * @returns child client. */ const getQueryClient = async ( @@ -80,7 +84,7 @@ const getQueryClient = async ( dataSource: SavedObject, cryptographyClient: CryptographyClient ): Promise => { - if (dataSource.attributes.noAuth) { + if (AuthType.NoAuth === dataSource.attributes.auth.type) { return rootClient.child(); } else { const credential = await getCredential(dataSource, cryptographyClient); diff --git a/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts index e90a408b4584..ee7aba23b824 100644 --- a/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts +++ b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts @@ -20,7 +20,7 @@ import { SavedObjectsErrorHelpers } from '../../../../core/server'; import { CryptographyClient } from '../cryptography'; import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; -import { CredentialsType } from '../../common/data_sources'; +import { AuthType } from '../../common/data_sources'; /** * Describes the Credential Saved Objects Client Wrapper class, @@ -137,65 +137,52 @@ export class DataSourceSavedObjectsClientWrapper { } } - private dropCredentials(attributes: Omit) { - return attributes; - } - private async validateAndEncryptAttributes(attributes: T) { this.validateAttributes(attributes); - const { noAuth } = attributes; - - // Drop credentials when no Auth - if (!noAuth) { - return this.dropCredentials(attributes); - } - - const { type, credentialsContent } = attributes.credentials; + const { auth } = attributes; - switch (type) { - case CredentialsType.UsernamePasswordType: - const { username, password } = credentialsContent; + switch (auth.type) { + case AuthType.NoAuth: return { ...attributes, - credentials: { - type, - credentialsContent: { - username, - password: await this.cryptographyClient.encryptAndEncode(password), - }, - }, + // Drop the credentials attribute for no_auth + credentials: undefined, + }; + case AuthType.UsernamePasswordType: + return { + ...attributes, + auth: await this.encryptCredentials(auth), }; default: - throw SavedObjectsErrorHelpers.createBadRequestError( - `Invalid credential materials type: '${type}'` - ); + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid auth type: '${type}'`); } } private async validateAndUpdatePartialAttributes(attributes: T) { - const { noAuth, credentials } = attributes; + const { auth } = attributes; - // Drop credentials when no Auth - if (!noAuth) { - return this.dropCredentials(attributes); + if (auth === undefined) { + return attributes; } - const { type, credentialsContent } = credentials; + const { + type, + credentials: { password }, + } = auth; switch (type) { - case CredentialsType.UsernamePasswordType: - if ('password' in credentialsContent) { - const { username, password } = credentialsContent; + case AuthType.NoAuth: + return { + ...attributes, + // Drop the credentials attribute for no_auth + credentials: undefined, + }; + case AuthType.UsernamePasswordType: + if (password) { return { ...attributes, - credentials: { - type, - credentialsContent: { - username, - password: await this.cryptographyClient.encryptAndEncode(password), - }, - }, + auth: await this.encryptCredentials(auth), }; } else { return attributes; @@ -206,61 +193,78 @@ export class DataSourceSavedObjectsClientWrapper { } private validateAttributes(attributes: T) { - const { title, endpoint, noAuth, credentials } = attributes; + const { title, endpoint, auth } = attributes; if (!title) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "title" required'); + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "title" required for "data source" saved object' + ); } if (!this.isValidUrl(endpoint)) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "endpoint" is not valid'); + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "endpoint" is not valid for "data source" saved object' + ); } - if (noAuth) { - this.validateCredentials(credentials); + if (auth === undefined) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth" required for "data source" saved object' + ); } - } - private validateCredentials(credentials: T) { - if (credentials === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "credentials" required'); - } + this.validateAuth(auth); + } - const { type, credentialsContent } = credentials; + private validateAuth(auth: T) { + const { type, credentials } = auth; if (!type) { throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "type" required for "credentials"' - ); - } - - if (credentialsContent === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialsContent" required for "credentials"' + 'attribute "auth.type" required for "data source" saved object' ); } switch (type) { - case CredentialsType.UsernamePasswordType: - const { username, password } = credentialsContent; + case AuthType.NoAuth: + break; + case AuthType.UsernamePasswordType: + if (credentials === undefined) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth.credentials" required for "data source" saved object' + ); + } + + const { username, password } = credentials; + + if (!username) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth.credentials.username" required for "data source" saved object' + ); + } - this.validateUsername(username); - this.validatePassword(password); + if (!password) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth.credentials.password" required for "data source" saved object' + ); + } + + break; default: - throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid credentials type: '${type}'`); + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid auth type: '${type}'`); } } - private validateUsername(username: T) { - if (!username) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "username" required'); - } - return; - } + private async encryptCredentials(auth: T) { + const { + credentials: { username, password }, + } = auth; - private validatePassword(password: T) { - if (!password) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "password" required'); - } - return; + return { + ...auth, + credentials: { + username, + password: await this.cryptographyClient.encryptAndEncode(password), + }, + }; } }