Skip to content

Commit

Permalink
Revise data source saved object model
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chu <[email protected]>
  • Loading branch information
noCharger committed Sep 1, 2022
1 parent 5b62400 commit ce7005f
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 92 deletions.
10 changes: 5 additions & 5 deletions src/plugins/data_source/common/data_sources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

Expand All @@ -21,6 +20,7 @@ export interface UsernamePasswordTypedContent extends SavedObjectAttributes {
password: string;
}

export enum CredentialsType {
export enum AuthType {
NoAuth = 'no_auth',
UsernamePasswordType = 'username_password',
}
20 changes: 12 additions & 8 deletions src/plugins/data_source/server/client/configure_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
},
Expand Down Expand Up @@ -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: [],
});

Expand All @@ -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(
Expand Down
12 changes: 8 additions & 4 deletions src/plugins/data_source/server/client/configure_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -53,7 +57,7 @@ export const getCredential = async (
cryptographyClient: CryptographyClient
): Promise<UsernamePasswordTypedContent> => {
try {
const { username, password } = dataSource.attributes.credentials!.credentialsContent;
const { username, password } = dataSource.attributes.auth.credentials!;
const decodedPassword = await cryptographyClient.decodeAndDecrypt(password);
const credential = {
username,
Expand All @@ -72,15 +76,15 @@ 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<DataSourceAttributes>,
cryptographyClient: CryptographyClient
): Promise<Client> => {
if (dataSource.attributes.noAuth) {
if (AuthType.NoAuth === dataSource.attributes.auth.type) {
return rootClient.child();
} else {
const credential = await getCredential(dataSource, cryptographyClient);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -137,65 +137,52 @@ export class DataSourceSavedObjectsClientWrapper {
}
}

private dropCredentials<T = unknown>(attributes: Omit<T, 'credentials'>) {
return attributes;
}

private async validateAndEncryptAttributes<T = unknown>(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<T = unknown>(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;
Expand All @@ -206,61 +193,78 @@ export class DataSourceSavedObjectsClientWrapper {
}

private validateAttributes<T = unknown>(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<T = unknown>(credentials: T) {
if (credentials === undefined) {
throw SavedObjectsErrorHelpers.createBadRequestError('attribute "credentials" required');
}
this.validateAuth(auth);
}

const { type, credentialsContent } = credentials;
private validateAuth<T = unknown>(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<T = unknown>(username: T) {
if (!username) {
throw SavedObjectsErrorHelpers.createBadRequestError('attribute "username" required');
}
return;
}
private async encryptCredentials<T = unknown>(auth: T) {
const {
credentials: { username, password },
} = auth;

private validatePassword<T = unknown>(password: T) {
if (!password) {
throw SavedObjectsErrorHelpers.createBadRequestError('attribute "password" required');
}
return;
return {
...auth,
credentials: {
username,
password: await this.cryptographyClient.encryptAndEncode(password),
},
};
}
}

0 comments on commit ce7005f

Please sign in to comment.