From e08bf3059896449d2c008059c47b0b3de8da8c9e Mon Sep 17 00:00:00 2001 From: Bandini <63824432+bandinib-amzn@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:43:37 -0800 Subject: [PATCH] [Multi data source] Add interfaces to register add-on authentication method from plug-in module (#5851) * Adds method to register credential provider during data source plugin setup Signed-off-by: Bandini Bhopi * Adds method to register authentication method with UI elements during data source management plugin setup Signed-off-by: Bandini Bhopi * Adds UT for auth registry in data source plugin Signed-off-by: Bandini Bhopi * Adds UT for auth registry in data source management plugin Signed-off-by: Bandini Bhopi * Adds UT for data_source_management plugin.ts Signed-off-by: Bandini Bhopi * Refactor code Signed-off-by: Bandini Bhopi --------- Signed-off-by: Bandini Bhopi --- CHANGELOG.md | 1 + .../data_source/common/data_sources/types.ts | 6 +- .../authentication_methods_registry.test.ts | 108 ++++++++++++++++++ .../authentication_methods_registry.ts | 34 ++++++ .../data_source/server/auth_registry/index.ts | 9 ++ src/plugins/data_source/server/plugin.ts | 41 +++++-- .../server/routes/test_connection.ts | 4 +- src/plugins/data_source/server/types.ts | 32 +++++- .../authentication_methods_registry.test.ts | 97 ++++++++++++++++ .../authentication_methods_registry.ts | 40 +++++++ .../public/auth_registry/index.ts | 10 ++ .../data_source_management/public/index.ts | 1 + .../data_source_management/public/mocks.ts | 41 +++++++ .../public/plugin.test.ts | 25 ++++ .../data_source_management/public/plugin.ts | 43 ++++++- .../index_pattern_management/public/mocks.ts | 3 + 16 files changed, 480 insertions(+), 15 deletions(-) create mode 100644 src/plugins/data_source/server/auth_registry/authentication_methods_registry.test.ts create mode 100644 src/plugins/data_source/server/auth_registry/authentication_methods_registry.ts create mode 100644 src/plugins/data_source/server/auth_registry/index.ts create mode 100644 src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.test.ts create mode 100644 src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.ts create mode 100644 src/plugins/data_source_management/public/auth_registry/index.ts create mode 100644 src/plugins/data_source_management/public/plugin.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4f091d3d71..ac6c30e58d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Discover] Enhanced the data source selector with added sorting functionality ([#5609](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5609)) - [Multiple Datasource] Add datasource picker component and use it in devtools and tutorial page when multiple datasource is enabled ([#5756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5756)) - [Multiple Datasource] Add datasource picker to import saved object flyout when multiple data source is enabled ([#5781](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5781)) +- [Multiple Datasource] Add interfaces to register add-on authentication method from plug-in module ([#5851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5851)) ### 🐛 Bug Fixes diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index 8763c5306c15..d30e5ee710c8 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -11,11 +11,15 @@ export interface DataSourceAttributes extends SavedObjectAttributes { endpoint: string; auth: { type: AuthType; - credentials: UsernamePasswordTypedContent | SigV4Content | undefined; + credentials: UsernamePasswordTypedContent | SigV4Content | undefined | AuthTypeContent; }; lastUpdatedTime?: string; } +export interface AuthTypeContent { + [key: string]: string; +} + /** * Multiple datasource supports authenticating as IAM user, it doesn't support IAM role. * Because IAM role session requires temporary security credentials through assuming role, diff --git a/src/plugins/data_source/server/auth_registry/authentication_methods_registry.test.ts b/src/plugins/data_source/server/auth_registry/authentication_methods_registry.test.ts new file mode 100644 index 000000000000..c7692acee782 --- /dev/null +++ b/src/plugins/data_source/server/auth_registry/authentication_methods_registry.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthenticationMethodRegistery } from './authentication_methods_registry'; +import { AuthenticationMethod } from '../../server/types'; +import { AuthType } from '../../common/data_sources'; + +const createAuthenticationMethod = ( + authMethod: Partial +): AuthenticationMethod => ({ + name: 'unknown', + authType: AuthType.NoAuth, + credentialProvider: jest.fn(), + ...authMethod, +}); + +describe('AuthenticationMethodRegistery', () => { + let registry: AuthenticationMethodRegistery; + + beforeEach(() => { + registry = new AuthenticationMethodRegistery(); + }); + + it('allows to register authentication method', () => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeB' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeC' })); + + expect( + registry + .getAllAuthenticationMethods() + .map((type) => type.name) + .sort() + ).toEqual(['typeA', 'typeB', 'typeC']); + }); + + it('throws when trying to register the same authentication method twice', () => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeB' })); + expect(() => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + }).toThrowErrorMatchingInlineSnapshot(`"Authentication method 'typeA' is already registered"`); + }); + + describe('#getAuthenticationMethod', () => { + it(`retrieve a type by it's name`, () => { + const typeA = createAuthenticationMethod({ name: 'typeA' }); + const typeB = createAuthenticationMethod({ name: 'typeB' }); + registry.registerAuthenticationMethod(typeA); + registry.registerAuthenticationMethod(typeB); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeC' })); + + expect(registry.getAuthenticationMethod('typeA')).toEqual(typeA); + expect(registry.getAuthenticationMethod('typeB')).toEqual(typeB); + expect(registry.getAuthenticationMethod('unknownType')).toBeUndefined(); + }); + + it('forbids to mutate the registered types', () => { + registry.registerAuthenticationMethod( + createAuthenticationMethod({ + name: 'typeA', + authType: AuthType.NoAuth, + }) + ); + + const typeA = registry.getAuthenticationMethod('typeA')!; + + expect(() => { + typeA.authType = AuthType.SigV4; + }).toThrow(); + expect(() => { + typeA.name = 'foo'; + }).toThrow(); + expect(() => { + typeA.credentialProvider = jest.fn(); + }).toThrow(); + }); + }); + + describe('#getAllTypes', () => { + it('returns all registered types', () => { + const typeA = createAuthenticationMethod({ name: 'typeA' }); + const typeB = createAuthenticationMethod({ name: 'typeB' }); + const typeC = createAuthenticationMethod({ name: 'typeC' }); + registry.registerAuthenticationMethod(typeA); + registry.registerAuthenticationMethod(typeB); + + const registered = registry.getAllAuthenticationMethods(); + expect(registered.length).toEqual(2); + expect(registered).toContainEqual(typeA); + expect(registered).toContainEqual(typeB); + expect(registered).not.toContainEqual(typeC); + }); + + it('does not mutate the registered types when altering the list', () => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeB' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeC' })); + + const types = registry.getAllAuthenticationMethods(); + types.splice(0, 3); + + expect(registry.getAllAuthenticationMethods().length).toEqual(3); + }); + }); +}); diff --git a/src/plugins/data_source/server/auth_registry/authentication_methods_registry.ts b/src/plugins/data_source/server/auth_registry/authentication_methods_registry.ts new file mode 100644 index 000000000000..e2f39498e007 --- /dev/null +++ b/src/plugins/data_source/server/auth_registry/authentication_methods_registry.ts @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { deepFreeze } from '@osd/std'; +import { AuthenticationMethod } from '../../server/types'; + +export type IAuthenticationMethodRegistery = Omit< + AuthenticationMethodRegistery, + 'registerAuthenticationMethod' +>; + +export class AuthenticationMethodRegistery { + private readonly authMethods = new Map(); + /** + * Register a authMethods with function to return credentials inside the registry. + * Authentication Method can only be registered once. subsequent calls with the same method name will throw an error. + */ + public registerAuthenticationMethod(method: AuthenticationMethod) { + if (this.authMethods.has(method.name)) { + throw new Error(`Authentication method '${method.name}' is already registered`); + } + this.authMethods.set(method.name, deepFreeze(method) as AuthenticationMethod); + } + + public getAllAuthenticationMethods() { + return [...this.authMethods.values()]; + } + + public getAuthenticationMethod(name: string) { + return this.authMethods.get(name); + } +} diff --git a/src/plugins/data_source/server/auth_registry/index.ts b/src/plugins/data_source/server/auth_registry/index.ts new file mode 100644 index 000000000000..9352afd8b661 --- /dev/null +++ b/src/plugins/data_source/server/auth_registry/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { + IAuthenticationMethodRegistery, + AuthenticationMethodRegistery, +} from './authentication_methods_registry'; diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index eee9bc0b8e0e..1a90d22960e5 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -23,19 +23,22 @@ import { LoggingAuditor } from './audit/logging_auditor'; import { CryptographyService, CryptographyServiceSetup } from './cryptography_service'; import { DataSourceService, DataSourceServiceSetup } from './data_source_service'; import { DataSourceSavedObjectsClientWrapper, dataSource } from './saved_objects'; -import { DataSourcePluginSetup, DataSourcePluginStart } from './types'; +import { AuthenticationMethod, 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'; import { createDataSourceError } from './lib/error'; import { registerTestConnectionRoute } from './routes/test_connection'; +import { AuthenticationMethodRegistery, IAuthenticationMethodRegistery } from './auth_registry'; export class DataSourcePlugin implements Plugin { private readonly logger: Logger; private readonly cryptographyService: CryptographyService; private readonly dataSourceService: DataSourceService; private readonly config$: Observable; + private started = false; + private authMethodsRegistry = new AuthenticationMethodRegistery(); constructor(private initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -44,7 +47,7 @@ export class DataSourcePlugin implements Plugin(); } - public async setup(core: CoreSetup) { + public async setup(core: CoreSetup) { this.logger.debug('dataSource: Setup'); // Register data source saved object type @@ -95,6 +98,12 @@ export class DataSourcePlugin implements Plugin coreStart.auditTrail); const dataSourceService: DataSourceServiceSetup = await this.dataSourceService.setup(config); + + const authRegistryPromise = core.getStartServices().then(([, , selfStart]) => { + const dataSourcePluginStart = selfStart as DataSourcePluginStart; + return dataSourcePluginStart.getAuthenticationMethodRegistery(); + }); + // Register data source plugin context to route handler context core.http.registerRouteHandlerContext( 'dataSource', @@ -102,24 +111,41 @@ export class DataSourcePlugin implements Plugin { + this.logger.debug(`Registered Credential Provider for authType = ${method.name}`); + if (this.started) { + throw new Error('cannot call `registerCredentialProvider` after service startup.'); + } + this.authMethodsRegistry.registerAuthenticationMethod(method); + }; return { createDataSourceError: (e: any) => createDataSourceError(e), dataSourceEnabled: () => config.enabled, defaultClusterEnabled: () => config.defaultCluster, + registerCredentialProvider, }; } public start(core: CoreStart) { this.logger.debug('dataSource: Started'); - - return {}; + this.started = true; + return { + getAuthenticationMethodRegistery: () => this.authMethodsRegistry, + }; } public stop() { @@ -130,7 +156,8 @@ export class DataSourcePlugin implements Plugin + auditTrailPromise: Promise, + authRegistryPromise: Promise ): IContextProvider, 'dataSource'> => { return (context, req) => { return { diff --git a/src/plugins/data_source/server/routes/test_connection.ts b/src/plugins/data_source/server/routes/test_connection.ts index cba42517e535..85eea97c933c 100644 --- a/src/plugins/data_source/server/routes/test_connection.ts +++ b/src/plugins/data_source/server/routes/test_connection.ts @@ -9,11 +9,13 @@ import { AuthType, DataSourceAttributes, SigV4ServiceName } from '../../common/d import { DataSourceConnectionValidator } from './data_source_connection_validator'; import { DataSourceServiceSetup } from '../data_source_service'; import { CryptographyServiceSetup } from '../cryptography_service'; +import { IAuthenticationMethodRegistery } from '../auth_registry'; export const registerTestConnectionRoute = ( router: IRouter, dataSourceServiceSetup: DataSourceServiceSetup, - cryptography: CryptographyServiceSetup + cryptography: CryptographyServiceSetup, + authRegistryPromise: Promise ) => { router.post( { diff --git a/src/plugins/data_source/server/types.ts b/src/plugins/data_source/server/types.ts index eed435b57301..b54eb5db0eb9 100644 --- a/src/plugins/data_source/server/types.ts +++ b/src/plugins/data_source/server/types.ts @@ -7,11 +7,18 @@ import { LegacyCallAPIOptions, OpenSearchClient, SavedObjectsClientContract, + OpenSearchDashboardsRequest, } from 'src/core/server'; -import { DataSourceAttributes } from '../common/data_sources'; +import { + DataSourceAttributes, + AuthType, + UsernamePasswordTypedContent, + SigV4Content, +} from '../common/data_sources'; import { CryptographyServiceSetup } from './cryptography_service'; import { DataSourceError } from './lib/error'; +import { IAuthenticationMethodRegistery } from './auth_registry'; export interface LegacyClientCallAPIParams { endpoint: string; @@ -29,6 +36,22 @@ export interface DataSourceClientParams { testClientDataSourceAttr?: DataSourceAttributes; } +export interface DataSourceCredentialsProviderOptions { + dataSourceAttr: DataSourceAttributes; + request?: OpenSearchDashboardsRequest; + cryptography?: CryptographyServiceSetup; +} + +export type DataSourceCredentialsProvider = ( + options: DataSourceCredentialsProviderOptions +) => Promise; + +export interface AuthenticationMethod { + name: string; + authType: AuthType; + credentialProvider: DataSourceCredentialsProvider; +} + export interface DataSourcePluginRequestContext { opensearch: { getClient: (dataSourceId: string) => Promise; @@ -55,6 +78,9 @@ export interface DataSourcePluginSetup { createDataSourceError: (err: any) => DataSourceError; dataSourceEnabled: () => boolean; defaultClusterEnabled: () => boolean; + registerCredentialProvider: (method: AuthenticationMethod) => void; +} + +export interface DataSourcePluginStart { + getAuthenticationMethodRegistery: () => IAuthenticationMethodRegistery; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DataSourcePluginStart {} diff --git a/src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.test.ts b/src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.test.ts new file mode 100644 index 000000000000..f2bd07af4dc5 --- /dev/null +++ b/src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthenticationMethodRegistery } from './authentication_methods_registry'; +import React from 'react'; +import { createAuthenticationMethod } from '../mocks'; + +describe('AuthenticationMethodRegistery', () => { + let registry: AuthenticationMethodRegistery; + + beforeEach(() => { + registry = new AuthenticationMethodRegistery(); + }); + + it('allows to register authentication method', () => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeB' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeC' })); + + expect( + registry + .getAllAuthenticationMethods() + .map((type) => type.name) + .sort() + ).toEqual(['typeA', 'typeB', 'typeC']); + }); + + it('throws when trying to register the same authentication method twice', () => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeB' })); + expect(() => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + }).toThrowErrorMatchingInlineSnapshot(`"Authentication method 'typeA' is already registered"`); + }); + + describe('#getAuthenticationMethod', () => { + it(`retrieve a type by it's name`, () => { + const typeA = createAuthenticationMethod({ name: 'typeA' }); + const typeB = createAuthenticationMethod({ name: 'typeB' }); + registry.registerAuthenticationMethod(typeA); + registry.registerAuthenticationMethod(typeB); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeC' })); + + expect(registry.getAuthenticationMethod('typeA')).toEqual(typeA); + expect(registry.getAuthenticationMethod('typeB')).toEqual(typeB); + expect(registry.getAuthenticationMethod('unknownType')).toBeUndefined(); + }); + + it('forbids to mutate the registered types', () => { + registry.registerAuthenticationMethod( + createAuthenticationMethod({ + name: 'typeA', + }) + ); + + const typeA = registry.getAuthenticationMethod('typeA')!; + + expect(() => { + typeA.credentialForm = React.createElement('div', {}, 'Welcome!'); + }).toThrow(); + expect(() => { + typeA.credentialSourceOption = { + value: 'typeA', + }; + }).toThrow(); + }); + }); + + describe('#getAllTypes', () => { + it('returns all registered types', () => { + const typeA = createAuthenticationMethod({ name: 'typeA' }); + const typeB = createAuthenticationMethod({ name: 'typeB' }); + const typeC = createAuthenticationMethod({ name: 'typeC' }); + registry.registerAuthenticationMethod(typeA); + registry.registerAuthenticationMethod(typeB); + + const registered = registry.getAllAuthenticationMethods(); + expect(registered.length).toEqual(2); + expect(registered).toContainEqual(typeA); + expect(registered).toContainEqual(typeB); + expect(registered).not.toContainEqual(typeC); + }); + + it('does not mutate the registered types when altering the list', () => { + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeA' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeB' })); + registry.registerAuthenticationMethod(createAuthenticationMethod({ name: 'typeC' })); + + const types = registry.getAllAuthenticationMethods(); + types.splice(0, 3); + + expect(registry.getAllAuthenticationMethods().length).toEqual(3); + }); + }); +}); diff --git a/src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.ts b/src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.ts new file mode 100644 index 000000000000..98cff913483f --- /dev/null +++ b/src/plugins/data_source_management/public/auth_registry/authentication_methods_registry.ts @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { deepFreeze } from '@osd/std'; +import { EuiSuperSelectOption } from '@elastic/eui'; + +export interface AuthenticationMethod { + name: string; + credentialForm: React.JSX.Element; + credentialSourceOption: EuiSuperSelectOption; +} + +export type IAuthenticationMethodRegistery = Omit< + AuthenticationMethodRegistery, + 'registerAuthenticationMethod' +>; + +export class AuthenticationMethodRegistery { + private readonly authMethods = new Map(); + /** + * Register a authMethods with function to return credentials inside the registry. + * Authentication Method can only be registered once. subsequent calls with the same method name will throw an error. + */ + public registerAuthenticationMethod(method: AuthenticationMethod) { + if (this.authMethods.has(method.name)) { + throw new Error(`Authentication method '${method.name}' is already registered`); + } + this.authMethods.set(method.name, deepFreeze(method) as AuthenticationMethod); + } + + public getAllAuthenticationMethods() { + return [...this.authMethods.values()]; + } + + public getAuthenticationMethod(name: string) { + return this.authMethods.get(name); + } +} diff --git a/src/plugins/data_source_management/public/auth_registry/index.ts b/src/plugins/data_source_management/public/auth_registry/index.ts new file mode 100644 index 000000000000..5cbadd12a51a --- /dev/null +++ b/src/plugins/data_source_management/public/auth_registry/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { + IAuthenticationMethodRegistery, + AuthenticationMethod, + AuthenticationMethodRegistery, +} from './authentication_methods_registry'; diff --git a/src/plugins/data_source_management/public/index.ts b/src/plugins/data_source_management/public/index.ts index 5cd2c6c96d11..e58e8f9bce5e 100644 --- a/src/plugins/data_source_management/public/index.ts +++ b/src/plugins/data_source_management/public/index.ts @@ -12,3 +12,4 @@ export function plugin() { } export { DataSourceManagementPluginStart } from './types'; export { ClusterSelector } from './components/cluster_selector'; +export { DataSourceManagementPlugin, DataSourceManagementPluginSetup } from './plugin'; diff --git a/src/plugins/data_source_management/public/mocks.ts b/src/plugins/data_source_management/public/mocks.ts index c078247956e0..7b170c4a7c79 100644 --- a/src/plugins/data_source_management/public/mocks.ts +++ b/src/plugins/data_source_management/public/mocks.ts @@ -3,10 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ +import React from 'react'; import { throwError } from 'rxjs'; import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; import { AuthType } from './types'; import { coreMock } from '../../../core/public/mocks'; +import { + DataSourceManagementPlugin, + DataSourceManagementPluginSetup, + DataSourceManagementPluginStart, +} from './plugin'; +import { managementPluginMock } from '../../management/public/mocks'; +import { mockManagementPlugin as indexPatternManagementPluginMock } from '../../index_pattern_management/public/mocks'; +import { AuthenticationMethod } from './auth_registry'; /* Mock Types */ @@ -197,3 +206,35 @@ export const mockErrorResponseForSavedObjectsCalls = ( throwError(new Error('Error while fetching data sources')) ); }; + +export interface TestPluginReturn { + setup: DataSourceManagementPluginSetup; + doStart: () => DataSourceManagementPluginStart; +} + +export const testDataSourceManagementPlugin = ( + coreSetup: any, + coreStart: any +): TestPluginReturn => { + const plugin = new DataSourceManagementPlugin(); + const setup = plugin.setup(coreSetup, { + management: managementPluginMock.createSetupContract(), + indexPatternManagement: indexPatternManagementPluginMock.createSetupContract(), + }); + const doStart = () => { + const start = plugin.start(coreStart); + return start; + }; + return { setup, doStart }; +}; + +export const createAuthenticationMethod = ( + authMethod: Partial +): AuthenticationMethod => ({ + name: 'unknown', + credentialForm: React.createElement('div', {}, 'Hello, world!'), + credentialSourceOption: { + value: 'unknown', + }, + ...authMethod, +}); diff --git a/src/plugins/data_source_management/public/plugin.test.ts b/src/plugins/data_source_management/public/plugin.test.ts new file mode 100644 index 000000000000..98615119a0c1 --- /dev/null +++ b/src/plugins/data_source_management/public/plugin.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { coreMock } from '../../../core/public/mocks'; +import { DataSourceManagementPluginStart } from './plugin'; +import { testDataSourceManagementPlugin, createAuthenticationMethod } from './mocks'; + +describe('#dataSourceManagement', () => { + let coreSetup: any; + let coreStart: any; + let mockDataSourceManagementPluginStart: MockedKeys; + beforeEach(() => { + coreSetup = coreMock.createSetup({ pluginStartContract: mockDataSourceManagementPluginStart }); + coreStart = coreMock.createStart(); + }); + it('can register custom authentication method', () => { + const { setup, doStart } = testDataSourceManagementPlugin(coreSetup, coreStart); + const typeA = createAuthenticationMethod({ name: 'typeA' }); + setup.registerAuthenticationMethod(createAuthenticationMethod(typeA)); + const start = doStart(); + const registry = start.getAuthenticationMethodRegistery(); + expect(registry.getAuthenticationMethod('typeA')).toEqual(typeA); + }); +}); diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts index 941107d74638..0c7123e47a94 100644 --- a/src/plugins/data_source_management/public/plugin.ts +++ b/src/plugins/data_source_management/public/plugin.ts @@ -10,18 +10,39 @@ import { PLUGIN_NAME } from '../common'; import { ManagementSetup } from '../../management/public'; import { IndexPatternManagementSetup } from '../../index_pattern_management/public'; import { DataSourceColumn } from './components/data_source_column/data_source_column'; +import { + AuthenticationMethod, + IAuthenticationMethodRegistery, + AuthenticationMethodRegistery, +} from './auth_registry'; export interface DataSourceManagementSetupDependencies { management: ManagementSetup; indexPatternManagement: IndexPatternManagementSetup; } +export interface DataSourceManagementPluginSetup { + registerAuthenticationMethod: (authMethodValues: AuthenticationMethod) => void; +} + +export interface DataSourceManagementPluginStart { + getAuthenticationMethodRegistery: () => IAuthenticationMethodRegistery; +} + const DSM_APP_ID = 'dataSources'; export class DataSourceManagementPlugin - implements Plugin { + implements + Plugin< + DataSourceManagementPluginSetup, + DataSourceManagementPluginStart, + DataSourceManagementSetupDependencies + > { + private started = false; + private authMethodsRegistry = new AuthenticationMethodRegistery(); + public setup( - core: CoreSetup, + core: CoreSetup, { management, indexPatternManagement }: DataSourceManagementSetupDependencies ) { const opensearchDashboardsSection = management.sections.section.opensearchDashboards; @@ -47,9 +68,25 @@ export class DataSourceManagementPlugin return mountManagementSection(core.getStartServices, params); }, }); + + const registerAuthenticationMethod = (authMethod: AuthenticationMethod) => { + if (this.started) { + throw new Error( + 'cannot call `registerAuthenticationMethod` after data source management startup.' + ); + } + this.authMethodsRegistry.registerAuthenticationMethod(authMethod); + }; + + return { registerAuthenticationMethod }; } - public start(core: CoreStart) {} + public start(core: CoreStart) { + this.started = true; + return { + getAuthenticationMethodRegistery: () => this.authMethodsRegistry, + }; + } public stop() {} } diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 295bd7e3faee..dacf876c2f6c 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -53,6 +53,9 @@ const createSetupContract = (): IndexPatternManagementSetup => ({ environment: { update: jest.fn(), }, + columns: { + register: jest.fn(), + }, }); const createStartContract = (): IndexPatternManagementStart => ({