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..afcf3d662fed 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; + auth: { + type: AuthType; + credentials: UsernamePasswordTypedContent | undefined; + }; +} + +export interface UsernamePasswordTypedContent extends SavedObjectAttributes { + username: string; + password: string; +} + +export enum AuthType { + NoAuth = 'no_auth', + 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/client/configure_client.test.ts b/src/plugins/data_source/server/client/configure_client.test.ts index 11523f649685..f8f8f2cb5802 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, AuthType } 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 @@ -54,7 +49,13 @@ describe('configureClient', () => { dataSourceAttr = { title: 'title', endpoint: 'http://localhost', - noAuth: false, + auth: { + type: AuthType.UsernamePasswordType, + credentials: { + username: 'username', + password: 'password', + }, + }, } as DataSourceAttributes; clientPoolSetup = { @@ -62,30 +63,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; @@ -96,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: [], }); @@ -122,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( @@ -135,7 +123,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..5ba18261ba74 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -10,12 +10,13 @@ import { SavedObjectsClientContract, SavedObjectsErrorHelpers, } from '../../../../../src/core/server'; -import { DATA_SOURCE_SAVED_OBJECT_TYPE, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; + import { - CredentialSavedObjectAttributes, + AuthType, + DataSourceAttributes, UsernamePasswordTypedContent, -} from '../../common/credentials/types'; -import { DataSourceAttributes } from '../../common/data_sources'; +} from '../../common/data_sources'; import { DataSourcePluginConfigType } from '../../config'; import { CryptographyClient } from '../cryptography'; import { parseClientOptions } from './client_config'; @@ -32,7 +33,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 +53,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.auth.credentials!; const decodedPassword = await cryptographyClient.decodeAndDecrypt(password); const credential = { username, @@ -83,23 +76,19 @@ 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 ( rootClient: Client, dataSource: SavedObject, - savedObjects: SavedObjectsClientContract, cryptographyClient: CryptographyClient ): Promise => { - if (dataSource.attributes.noAuth) { + if (AuthType.NoAuth === dataSource.attributes.auth.type) { return rootClient.child(); } else { - const credential = await getCredential( - dataSource.references[0].id, - savedObjects, - cryptographyClient - ); + const credential = await getCredential(dataSource, cryptographyClient); + return getBasicAuthClient(rootClient, credential); } }; 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 ( - type: string, - attributes: T, - options?: SavedObjectsCreateOptions - ) => { - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { - return await wrapperOptions.client.create(type, attributes, options); - } - - const encryptedAttributes = await this.validateAndEncryptAttributes(attributes); - - return await wrapperOptions.client.create(type, encryptedAttributes, options); - }; - - const bulkCreateWithCredentialMaterialsEncryption = async ( - objects: Array>, - options?: SavedObjectsCreateOptions - ): Promise> => { - objects = await Promise.all( - objects.map(async (object) => { - const { type, attributes } = object; - - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { - return object; - } - - return { - ...object, - attributes: await this.validateAndEncryptAttributes(attributes), - }; - }) - ); - return await wrapperOptions.client.bulkCreate(objects, options); - }; - - const updateWithCredentialMaterialsEncryption = async ( - type: string, - id: string, - attributes: Partial, - options: SavedObjectsUpdateOptions = {} - ): Promise> => { - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { - return await wrapperOptions.client.update(type, id, attributes, options); - } - - const encryptedAttributes: Partial = await this.validateAndEncryptPartialAttributes( - attributes - ); - - return await wrapperOptions.client.update(type, id, encryptedAttributes, options); - }; - - const bulkUpdateWithCredentialMaterialsEncryption = async ( - objects: Array>, - options?: SavedObjectsBulkUpdateOptions - ): Promise> => { - objects = await Promise.all( - objects.map(async (object) => { - const { type, attributes } = object; - - if (CREDENTIAL_SAVED_OBJECT_TYPE !== type) { - return object; - } - - const encryptedAttributes: Partial = await this.validateAndEncryptPartialAttributes( - attributes - ); - - return { - ...object, - attributes: encryptedAttributes, - }; - }) - ); - - return await wrapperOptions.client.bulkUpdate(objects, options); - }; - - return { - ...wrapperOptions.client, - create: createWithCredentialMaterialsEncryption, - bulkCreate: bulkCreateWithCredentialMaterialsEncryption, - checkConflicts: wrapperOptions.client.checkConflicts, - delete: wrapperOptions.client.delete, - find: wrapperOptions.client.find, - bulkGet: wrapperOptions.client.bulkGet, - get: wrapperOptions.client.get, - update: updateWithCredentialMaterialsEncryption, - bulkUpdate: bulkUpdateWithCredentialMaterialsEncryption, - errors: wrapperOptions.client.errors, - addToNamespaces: wrapperOptions.client.addToNamespaces, - deleteFromNamespaces: wrapperOptions.client.deleteFromNamespaces, - }; - }; - - private async validateAndEncryptAttributes(attributes: T) { - this.validateAttributes(attributes); - - return await this.encryptCredentialMaterials(attributes); - } - - 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); - } - - return await this.encryptCredentialMaterials(attributes); - } - - private validateAttributes(attributes: T) { - const { title, credentialMaterials } = attributes; - if (!title) { - throw SavedObjectsErrorHelpers.createBadRequestError('attribute "title" required'); - } - - this.validateCredentialMaterials(credentialMaterials); - } - - private validateCredentialMaterials(credentialMaterials: T) { - if (credentialMaterials === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialMaterials" required' - ); - } - - const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials; - - if (credentialMaterialsType === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialMaterialsType" required for "credentialMaterials"' - ); - } - - if (credentialMaterialsContent === undefined) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'attribute "credentialMaterialsContent" required for "credentialMaterials"' - ); - } - } - - private async encryptCredentialMaterials(attributes: T) { - const { credentialMaterials } = attributes; - - const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials; - const { username } = credentialMaterialsContent; - - switch (credentialMaterialsType) { - case CredentialMaterialsType.UsernamePasswordType: - this.validateUsername(username); - - return { - ...attributes, - credentialMaterials: { - credentialMaterialsType, - credentialMaterialsContent: { - username, - }, - }, - }; - default: - throw SavedObjectsErrorHelpers.createBadRequestError( - `Invalid credential materials type: '${credentialMaterialsType}'` - ); - } - } - - 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 - ) { - 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/credential_saved_objects_type.ts b/src/plugins/data_source/server/saved_objects/credential_saved_objects_type.ts deleted file mode 100644 index f98b061f6966..000000000000 --- a/src/plugins/data_source/server/saved_objects/credential_saved_objects_type.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SavedObjectsType } from 'opensearch-dashboards/server'; - -export const credential: SavedObjectsType = { - name: 'credential', - hidden: false, - namespaceType: 'agnostic', - management: { - defaultSearchField: 'title', - // TODO: Support import / export https://github.com/opensearch-project/OpenSearch-Dashboards/issues/1872 - importableAndExportable: false, - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/opensearch-dashboards/credentials/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/management/opensearch-dashboards/credentials/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'credential.show', - }; - }, - }, - mappings: { - dynamic: false, - properties: { - title: { type: 'text' }, - }, - }, - migrations: {}, -}; 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 new file mode 100644 index 000000000000..ee7aba23b824 --- /dev/null +++ b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts @@ -0,0 +1,270 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + SavedObjectsBulkCreateObject, + SavedObjectsBulkResponse, + SavedObjectsBulkUpdateObject, + SavedObjectsBulkUpdateOptions, + SavedObjectsBulkUpdateResponse, + SavedObjectsClientWrapperFactory, + SavedObjectsCreateOptions, + SavedObjectsUpdateOptions, + SavedObjectsUpdateResponse, +} from 'opensearch-dashboards/server'; + +import { SavedObjectsErrorHelpers } from '../../../../core/server'; + +import { CryptographyClient } from '../cryptography'; + +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; +import { AuthType } from '../../common/data_sources'; + +/** + * Describes the Credential Saved Objects Client Wrapper class, + * which contains the factory used to create Saved Objects Client Wrapper instances + */ +export class DataSourceSavedObjectsClientWrapper { + constructor(private cryptographyClient: CryptographyClient) {} + + /** + * Describes the factory used to create instances of Saved Objects Client Wrappers + * for data source spcific operations such as credntials encryption + */ + public wrapperFactory: SavedObjectsClientWrapperFactory = (wrapperOptions) => { + const createWithCredentialsEncryption = async ( + type: string, + attributes: T, + options?: SavedObjectsCreateOptions + ) => { + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { + return await wrapperOptions.client.create(type, attributes, options); + } + + const encryptedAttributes = await this.validateAndEncryptAttributes(attributes); + + return await wrapperOptions.client.create(type, encryptedAttributes, options); + }; + + const bulkCreateWithCredentialsEncryption = async ( + objects: Array>, + options?: SavedObjectsCreateOptions + ): Promise> => { + objects = await Promise.all( + objects.map(async (object) => { + const { type, attributes } = object; + + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { + return object; + } + + return { + ...object, + attributes: await this.validateAndEncryptAttributes(attributes), + }; + }) + ); + return await wrapperOptions.client.bulkCreate(objects, options); + }; + + const updateWithCredentialsEncryption = async ( + type: string, + id: string, + attributes: Partial, + options: SavedObjectsUpdateOptions = {} + ): Promise> => { + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { + return await wrapperOptions.client.update(type, id, attributes, options); + } + + const encryptedAttributes: Partial = await this.validateAndUpdatePartialAttributes( + attributes + ); + + return await wrapperOptions.client.update(type, id, encryptedAttributes, options); + }; + + const bulkUpdateWithCredentialsEncryption = async ( + objects: Array>, + options?: SavedObjectsBulkUpdateOptions + ): Promise> => { + objects = await Promise.all( + objects.map(async (object) => { + const { type, attributes } = object; + + if (DATA_SOURCE_SAVED_OBJECT_TYPE !== type) { + return object; + } + + const encryptedAttributes: Partial = await this.validateAndUpdatePartialAttributes( + attributes + ); + + return { + ...object, + attributes: encryptedAttributes, + }; + }) + ); + + return await wrapperOptions.client.bulkUpdate(objects, options); + }; + + return { + ...wrapperOptions.client, + 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: 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 async validateAndEncryptAttributes(attributes: T) { + this.validateAttributes(attributes); + + const { auth } = attributes; + + switch (auth.type) { + case AuthType.NoAuth: + return { + ...attributes, + // Drop the credentials attribute for no_auth + credentials: undefined, + }; + case AuthType.UsernamePasswordType: + return { + ...attributes, + auth: await this.encryptCredentials(auth), + }; + default: + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid auth type: '${type}'`); + } + } + + private async validateAndUpdatePartialAttributes(attributes: T) { + const { auth } = attributes; + + if (auth === undefined) { + return attributes; + } + + const { + type, + credentials: { password }, + } = auth; + + switch (type) { + case AuthType.NoAuth: + return { + ...attributes, + // Drop the credentials attribute for no_auth + credentials: undefined, + }; + case AuthType.UsernamePasswordType: + if (password) { + return { + ...attributes, + auth: await this.encryptCredentials(auth), + }; + } else { + return attributes; + } + default: + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid credentials type: '${type}'`); + } + } + + private validateAttributes(attributes: T) { + const { title, endpoint, auth } = attributes; + if (!title) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "title" required for "data source" saved object' + ); + } + + if (!this.isValidUrl(endpoint)) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "endpoint" is not valid for "data source" saved object' + ); + } + + if (auth === undefined) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth" required for "data source" saved object' + ); + } + + this.validateAuth(auth); + } + + private validateAuth(auth: T) { + const { type, credentials } = auth; + + if (!type) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth.type" required for "data source" saved object' + ); + } + + switch (type) { + 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' + ); + } + + if (!password) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'attribute "auth.credentials.password" required for "data source" saved object' + ); + } + + break; + default: + throw SavedObjectsErrorHelpers.createBadRequestError(`Invalid auth type: '${type}'`); + } + } + + private async encryptCredentials(auth: T) { + const { + credentials: { username, password }, + } = auth; + + return { + ...auth, + credentials: { + username, + password: await this.cryptographyClient.encryptAndEncode(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';