From 39b12b1ab8e9c7274e7334392f67267d094c5ec1 Mon Sep 17 00:00:00 2001 From: Derek Ho Date: Mon, 25 Mar 2024 11:09:11 -0400 Subject: [PATCH] Add multi datasource support for internal users tab Signed-off-by: Derek Ho --- public/apps/account/utils.tsx | 12 ++- .../panels/auth-view/auth-view.tsx | 8 +- .../apps/configuration/panels/get-started.tsx | 12 +-- .../internal-user-edit/internal-user-edit.tsx | 27 +++++- .../test/internal-user-edit.test.tsx | 8 +- .../panels/test/get-started.test.tsx | 6 -- .../panels/test/user-list.test.tsx | 4 + .../apps/configuration/panels/user-list.tsx | 26 +++++- public/apps/configuration/top-nav-menu.tsx | 2 + .../utils/action-groups-utils.tsx | 6 +- .../utils/audit-logging-utils.tsx | 2 +- .../utils/internal-user-detail-utils.tsx | 19 ++++- .../utils/internal-user-list-utils.tsx | 16 ++-- .../apps/configuration/utils/request-utils.ts | 5 +- .../configuration/utils/role-detail-utils.tsx | 6 +- .../utils/role-mapping-utils.tsx | 6 +- .../utils/tenancy-config_util.tsx | 2 +- .../apps/configuration/utils/tenant-utils.tsx | 8 +- public/utils/datasource-utils.ts | 27 ++++++ public/utils/login-utils.tsx | 10 ++- public/utils/test/datasource-utils.test.ts | 28 ++++++ server/routes/index.ts | 85 +++++++++++++------ 22 files changed, 244 insertions(+), 81 deletions(-) create mode 100644 public/utils/datasource-utils.ts create mode 100644 public/utils/test/datasource-utils.test.ts diff --git a/public/apps/account/utils.tsx b/public/apps/account/utils.tsx index 31b5c21e9..df3c480be 100644 --- a/public/apps/account/utils.tsx +++ b/public/apps/account/utils.tsx @@ -34,7 +34,7 @@ export async function fetchAccountInfoSafe(http: HttpStart): Promise { - await httpPost(http, API_AUTH_LOGOUT); + await httpPost({ http, url: API_AUTH_LOGOUT }); setShouldShowTenantPopup(null); // Clear everything in the sessionStorage since they can contain sensitive information sessionStorage.clear(); @@ -70,8 +70,12 @@ export async function updateNewPassword( newPassword: string, currentPassword: string ): Promise { - await httpPost(http, API_ENDPOINT_ACCOUNT_INFO, { - password: newPassword, - current_password: currentPassword, + await httpPost({ + http, + url: API_ENDPOINT_ACCOUNT_INFO, + body: { + password: newPassword, + current_password: currentPassword, + }, }); } diff --git a/public/apps/configuration/panels/auth-view/auth-view.tsx b/public/apps/configuration/panels/auth-view/auth-view.tsx index ad0a3dd27..eec379ead 100644 --- a/public/apps/configuration/panels/auth-view/auth-view.tsx +++ b/public/apps/configuration/panels/auth-view/auth-view.tsx @@ -25,6 +25,7 @@ import { getSecurityConfig } from '../../utils/auth-view-utils'; import { InstructionView } from './instruction-view'; import { DataSourceContext } from '../../app-router'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; +import { createDataSourceQuery } from '../../../../utils/datasource-utils'; export function AuthView(props: AppDependencies) { const [authentication, setAuthentication] = React.useState([]); @@ -36,9 +37,10 @@ export function AuthView(props: AppDependencies) { const fetchData = async () => { try { setLoading(true); - const config = await getSecurityConfig(props.coreStart.http, { - dataSourceId: dataSource.id, - }); + const config = await getSecurityConfig( + props.coreStart.http, + createDataSourceQuery(dataSource.id) + ); setAuthentication(config.authc); setAuthorization(config.authz); diff --git a/public/apps/configuration/panels/get-started.tsx b/public/apps/configuration/panels/get-started.tsx index f1fe2149a..d55c8aefa 100644 --- a/public/apps/configuration/panels/get-started.tsx +++ b/public/apps/configuration/panels/get-started.tsx @@ -39,6 +39,7 @@ import { httpDelete } from '../utils/request-utils'; import { createSuccessToast, createUnknownErrorToast, useToastState } from '../utils/toast-utils'; import { SecurityPluginTopNavMenu } from '../top-nav-menu'; import { DataSourceContext } from '../app-router'; +import { getClusterInfoIfEnabled, createDataSourceQuery } from '../../../utils/datasource-utils'; const addBackendStep = { title: 'Add backends', @@ -161,13 +162,6 @@ const setOfSteps = [ }, ]; -export function getClusterInfoIfEnabled(dataSourceEnabled: boolean, cluster: DataSourceOption) { - if (dataSourceEnabled) { - return `for ${cluster.label || 'Local cluster'}`; - } - return ''; -} - export function GetStarted(props: AppDependencies) { const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { dataSource, setDataSource } = useContext(DataSourceContext)!; @@ -258,9 +252,7 @@ export function GetStarted(props: AppDependencies) { await httpDelete({ http: props.coreStart.http, url: API_ENDPOINT_CACHE, - query: { - dataSourceId: dataSource.id, - }, + query: createDataSourceQuery(dataSource.id), }); addToast( createSuccessToast( diff --git a/public/apps/configuration/panels/internal-user-edit/internal-user-edit.tsx b/public/apps/configuration/panels/internal-user-edit/internal-user-edit.tsx index 1237ef9f3..779c2b558 100644 --- a/public/apps/configuration/panels/internal-user-edit/internal-user-edit.tsx +++ b/public/apps/configuration/panels/internal-user-edit/internal-user-edit.tsx @@ -24,7 +24,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { BreadcrumbsPageDependencies } from '../../../types'; import { InternalUserUpdate } from '../../types'; import { ResourceType } from '../../../../../common'; @@ -47,6 +47,9 @@ import { NameRow } from '../../utils/name-row'; import { DocLinks } from '../../constants'; import { constructErrorMessageAndLog } from '../../../error-utils'; import { BackendRolePanel } from './backend-role-panel'; +import { DataSourceContext } from '../../app-router'; +import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; +import { createDataSourceQuery } from '../../../../utils/datasource-utils'; interface InternalUserEditDeps extends BreadcrumbsPageDependencies { action: 'create' | 'edit' | 'duplicate'; @@ -72,13 +75,18 @@ export function InternalUserEdit(props: InternalUserEditDeps) { const [toasts, addToast, removeToast] = useToastState(); const [isFormValid, setIsFormValid] = useState(true); + const { dataSource, setDataSource } = useContext(DataSourceContext)!; React.useEffect(() => { const action = props.action; if (action === 'edit' || action === 'duplicate') { const fetchData = async () => { try { - const user = await getUserDetail(props.coreStart.http, props.sourceUserName); + const user = await getUserDetail( + props.coreStart.http, + props.sourceUserName, + createDataSourceQuery(dataSource.id) + ); setAttributes(buildAttributeState(user.attributes)); setBackendRoles(user.backend_roles); setUserName(generateResourceName(action, props.sourceUserName)); @@ -90,7 +98,7 @@ export function InternalUserEdit(props: InternalUserEditDeps) { fetchData(); } - }, [addToast, props.action, props.coreStart.http, props.sourceUserName]); + }, [addToast, props.action, props.coreStart.http, props.sourceUserName, dataSource.id]); const updateUserHandler = async () => { try { @@ -117,7 +125,12 @@ export function InternalUserEdit(props: InternalUserEditDeps) { updateObject.password = password; } - await updateUser(props.coreStart.http, userName, updateObject); + await updateUser( + props.coreStart.http, + userName, + updateObject, + createDataSourceQuery(dataSource.id) + ); setCrossPageToast(buildUrl(ResourceType.users), { id: 'updateUserSucceeded', @@ -135,6 +148,12 @@ export function InternalUserEdit(props: InternalUserEditDeps) { return ( <> + {props.buildBreadcrumbs(TITLE_TEXT_DICT[props.action])} diff --git a/public/apps/configuration/panels/internal-user-edit/test/internal-user-edit.test.tsx b/public/apps/configuration/panels/internal-user-edit/test/internal-user-edit.test.tsx index d8d5efc5d..8dcc630d8 100644 --- a/public/apps/configuration/panels/internal-user-edit/test/internal-user-edit.test.tsx +++ b/public/apps/configuration/panels/internal-user-edit/test/internal-user-edit.test.tsx @@ -31,6 +31,10 @@ jest.mock('../../../utils/toast-utils', () => ({ createUnknownErrorToast: jest.fn(), useToastState: jest.fn().mockReturnValue([[], jest.fn(), jest.fn()]), })); +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useContext: jest.fn().mockReturnValue({ dataSource: { id: 'test' }, setDataSource: jest.fn() }), // Mock the useContext hook to return dummy datasource and setdatasource function +})); describe('Internal user edit', () => { const sampleUsername = 'user1'; @@ -79,7 +83,9 @@ describe('Internal user edit', () => { /> ); - expect(getUserDetail).toBeCalledWith(mockCoreStart.http, sampleUsername); + expect(getUserDetail).toBeCalledWith(mockCoreStart.http, sampleUsername, { + dataSourceId: 'test', + }); }); it('should not submit if password is empty on creation', () => { diff --git a/public/apps/configuration/panels/test/get-started.test.tsx b/public/apps/configuration/panels/test/get-started.test.tsx index bbe648a6c..09e64578d 100644 --- a/public/apps/configuration/panels/test/get-started.test.tsx +++ b/public/apps/configuration/panels/test/get-started.test.tsx @@ -175,11 +175,5 @@ describe('Get started (landing page)', () => { await button.props().onClick(); // Simulate button click expect(ToastUtils.createSuccessToast).toHaveBeenCalledTimes(1); }); - - it('Tests the GetClusterDescription helper function', () => { - expect(getClusterInfoIfEnabled(false, { id: 'blah', label: 'blah' })).toBe(''); - expect(getClusterInfoIfEnabled(true, { id: '', label: '' })).toBe('for Local cluster'); - expect(getClusterInfoIfEnabled(true, { id: 'test', label: 'test' })).toBe('for test'); - }); }); }); diff --git a/public/apps/configuration/panels/test/user-list.test.tsx b/public/apps/configuration/panels/test/user-list.test.tsx index 32455fa53..2e96573f5 100644 --- a/public/apps/configuration/panels/test/user-list.test.tsx +++ b/public/apps/configuration/panels/test/user-list.test.tsx @@ -37,6 +37,10 @@ jest.mock('../../utils/context-menu', () => ({ .fn() .mockImplementation((buttonText, buttonProps, children) => [children, jest.fn()]), })); +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useContext: jest.fn().mockReturnValue({ dataSource: { id: 'test' }, setDataSource: jest.fn() }), // Mock the useContext hook to return dummy datasource and setdatasource function +})); import { getAuthInfo } from '../../../../utils/auth-info-utils'; import { buildHashUrl } from '../../utils/url-builder'; diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index b75f62283..a118bcf23 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -31,7 +31,7 @@ import { Query, } from '@elastic/eui'; import { Dictionary, difference, isEmpty, map } from 'lodash'; -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { getAuthInfo } from '../../../utils/auth-info-utils'; import { AppDependencies } from '../../types'; import { API_ENDPOINT_INTERNALUSERS, DocLinks } from '../constants'; @@ -48,6 +48,9 @@ import { } from '../utils/internal-user-list-utils'; import { showTableStatusMessage } from '../utils/loading-spinner-utils'; import { buildHashUrl } from '../utils/url-builder'; +import { DataSourceContext } from '../app-router'; +import { SecurityPluginTopNavMenu } from '../top-nav-menu'; +import { createDataSourceQuery } from '../../../utils/datasource-utils'; export function dictView(items: Dictionary) { if (isEmpty(items)) { @@ -103,12 +106,17 @@ export function UserList(props: AppDependencies) { const [currentUsername, setCurrentUsername] = useState(''); const [loading, setLoading] = useState(false); const [query, setQuery] = useState(null); + const { dataSource, setDataSource } = useContext(DataSourceContext)!; React.useEffect(() => { const fetchData = async () => { try { setLoading(true); - const userDataPromise = getUserList(props.coreStart.http, ResourceType.users); + const userDataPromise = getUserList( + props.coreStart.http, + ResourceType.users, + createDataSourceQuery(dataSource.id) + ); setCurrentUsername((await getAuthInfo(props.coreStart.http)).user_name); setUserData(await userDataPromise); } catch (e) { @@ -120,12 +128,16 @@ export function UserList(props: AppDependencies) { }; fetchData(); - }, [props.coreStart.http]); + }, [props.coreStart.http, dataSource.id]); const handleDelete = async () => { const usersToDelete: string[] = selection.map((r) => r.username); try { - await requestDeleteUsers(props.coreStart.http, usersToDelete); + await requestDeleteUsers( + props.coreStart.http, + usersToDelete, + createDataSourceQuery(dataSource.id) + ); // Refresh from server (calling fetchData) does not work here, the server still return the users // that had been just deleted, probably because ES takes some time to sync to all nodes. // So here remove the selected users from local memory directly. @@ -194,6 +206,12 @@ export function UserList(props: AppDependencies) { return ( <> +

Internal users

diff --git a/public/apps/configuration/top-nav-menu.tsx b/public/apps/configuration/top-nav-menu.tsx index ec7e2641a..5f88bda7e 100644 --- a/public/apps/configuration/top-nav-menu.tsx +++ b/public/apps/configuration/top-nav-menu.tsx @@ -32,6 +32,7 @@ export const SecurityPluginTopNavMenu = (props: TopNavMenuProps) => { dataSourceManagement, setDataSource, selectedDataSource, + dataSourcePickerReadOnly, } = props; const { setHeaderActionMenu } = params; const DataSourceMenu = dataSourceManagement?.ui.DataSourceMenu; @@ -45,6 +46,7 @@ export const SecurityPluginTopNavMenu = (props: TopNavMenuProps) => { setMenuMountPoint={setHeaderActionMenu} notifications={coreStart.notifications} dataSourceCallBackFunc={setDataSource} + disableDataSourceSelectable={dataSourcePickerReadOnly} // Single select for now selectedOption={[selectedDataSource]} hideLocalCluster={false} diff --git a/public/apps/configuration/utils/action-groups-utils.tsx b/public/apps/configuration/utils/action-groups-utils.tsx index b832f0b50..048448ead 100644 --- a/public/apps/configuration/utils/action-groups-utils.tsx +++ b/public/apps/configuration/utils/action-groups-utils.tsx @@ -93,7 +93,11 @@ export async function updateActionGroup( groupName: string, updateObject: ActionGroupUpdate ): Promise { - return await httpPost(http, getResourceUrl(API_ENDPOINT_ACTIONGROUPS, groupName), updateObject); + return await httpPost({ + http, + url: getResourceUrl(API_ENDPOINT_ACTIONGROUPS, groupName), + body: updateObject, + }); } export async function requestDeleteActionGroups(http: HttpStart, groups: string[]) { diff --git a/public/apps/configuration/utils/audit-logging-utils.tsx b/public/apps/configuration/utils/audit-logging-utils.tsx index 1af24e552..72b04771d 100644 --- a/public/apps/configuration/utils/audit-logging-utils.tsx +++ b/public/apps/configuration/utils/audit-logging-utils.tsx @@ -19,7 +19,7 @@ import { API_ENDPOINT_AUDITLOGGING, API_ENDPOINT_AUDITLOGGING_UPDATE } from '../ import { httpGet, httpPost } from './request-utils'; export async function updateAuditLogging(http: HttpStart, updateObject: AuditLoggingSettings) { - return await httpPost(http, API_ENDPOINT_AUDITLOGGING_UPDATE, updateObject); + return await httpPost({ http, url: API_ENDPOINT_AUDITLOGGING_UPDATE, body: updateObject }); } export async function getAuditLogging(http: HttpStart): Promise { diff --git a/public/apps/configuration/utils/internal-user-detail-utils.tsx b/public/apps/configuration/utils/internal-user-detail-utils.tsx index b838e2171..d51ad526a 100644 --- a/public/apps/configuration/utils/internal-user-detail-utils.tsx +++ b/public/apps/configuration/utils/internal-user-detail-utils.tsx @@ -13,23 +13,34 @@ * permissions and limitations under the License. */ -import { HttpStart } from 'opensearch-dashboards/public'; +import { HttpFetchQuery, HttpStart } from 'opensearch-dashboards/public'; import { API_ENDPOINT_INTERNALUSERS } from '../constants'; import { InternalUser, InternalUserUpdate } from '../types'; import { httpGet, httpPost } from './request-utils'; import { getResourceUrl } from './resource-utils'; -export async function getUserDetail(http: HttpStart, username: string): Promise { +export async function getUserDetail( + http: HttpStart, + username: string, + query: HttpFetchQuery +): Promise { return await httpGet({ http, url: getResourceUrl(API_ENDPOINT_INTERNALUSERS, username), + query, }); } export async function updateUser( http: HttpStart, username: string, - updateObject: InternalUserUpdate + updateObject: InternalUserUpdate, + query: HttpFetchQuery ): Promise { - return await httpPost(http, getResourceUrl(API_ENDPOINT_INTERNALUSERS, username), updateObject); + return await httpPost({ + http, + url: getResourceUrl(API_ENDPOINT_INTERNALUSERS, username), + body: updateObject, + query, + }); } diff --git a/public/apps/configuration/utils/internal-user-list-utils.tsx b/public/apps/configuration/utils/internal-user-list-utils.tsx index 9308734e1..ea05da217 100644 --- a/public/apps/configuration/utils/internal-user-list-utils.tsx +++ b/public/apps/configuration/utils/internal-user-list-utils.tsx @@ -14,7 +14,7 @@ */ import { map } from 'lodash'; -import { HttpStart } from '../../../../../../src/core/public'; +import { HttpFetchQuery, HttpStart } from '../../../../../../src/core/public'; import { API_ENDPOINT_INTERNALACCOUNTS, API_ENDPOINT_INTERNALUSERS, @@ -37,29 +37,31 @@ export function transformUserData(rawData: DataObject): InternalUs })); } -export async function requestDeleteUsers(http: HttpStart, users: string[]) { +export async function requestDeleteUsers(http: HttpStart, users: string[], query: HttpFetchQuery) { for (const user of users) { - await httpDelete({ http, url: getResourceUrl(API_ENDPOINT_INTERNALUSERS, user) }); + await httpDelete({ http, url: getResourceUrl(API_ENDPOINT_INTERNALUSERS, user), query }); } } async function getUserListRaw( http: HttpStart, - userType: string + userType: string, + query?: HttpFetchQuery ): Promise> { let ENDPOINT = API_ENDPOINT_INTERNALACCOUNTS; if (userType === ResourceType.serviceAccounts) { ENDPOINT = API_ENDPOINT_SERVICEACCOUNTS; } - return await httpGet>({ http, url: ENDPOINT }); + return await httpGet>({ http, url: ENDPOINT, query }); } export async function getUserList( http: HttpStart, - userType: string + userType: string, + query?: HttpFetchQuery ): Promise { - const rawData = await getUserListRaw(http, userType); + const rawData = await getUserListRaw(http, userType, query); return transformUserData(rawData.data); } diff --git a/public/apps/configuration/utils/request-utils.ts b/public/apps/configuration/utils/request-utils.ts index a5e7099a3..b3cb2110b 100644 --- a/public/apps/configuration/utils/request-utils.ts +++ b/public/apps/configuration/utils/request-utils.ts @@ -39,8 +39,9 @@ export async function httpGet(params: RequestType): Promise { return await request(http.get, url, body, query); } -export async function httpPost(http: HttpStart, url: string, body?: object): Promise { - return await request(http.post, url, body); +export async function httpPost(params: RequestType): Promise { + const { http, url, body, query } = params; + return await request(http.post, url, body, query); } export async function httpPut(http: HttpStart, url: string, body?: object): Promise { diff --git a/public/apps/configuration/utils/role-detail-utils.tsx b/public/apps/configuration/utils/role-detail-utils.tsx index bc8ca142e..3686ece7d 100644 --- a/public/apps/configuration/utils/role-detail-utils.tsx +++ b/public/apps/configuration/utils/role-detail-utils.tsx @@ -24,5 +24,9 @@ export async function getRoleDetail(http: HttpStart, roleName: string): Promise< } export async function updateRole(http: HttpStart, roleName: string, updateObject: RoleUpdate) { - return await httpPost(http, getResourceUrl(API_ENDPOINT_ROLES, roleName), updateObject); + return await httpPost({ + http, + url: getResourceUrl(API_ENDPOINT_ROLES, roleName), + body: updateObject, + }); } diff --git a/public/apps/configuration/utils/role-mapping-utils.tsx b/public/apps/configuration/utils/role-mapping-utils.tsx index 049265982..426539fab 100644 --- a/public/apps/configuration/utils/role-mapping-utils.tsx +++ b/public/apps/configuration/utils/role-mapping-utils.tsx @@ -57,5 +57,9 @@ export async function updateRoleMapping( roleName: string, updateObject: RoleMappingDetail ) { - return await httpPost(http, getResourceUrl(API_ENDPOINT_ROLESMAPPING, roleName), updateObject); + return await httpPost({ + http, + url: getResourceUrl(API_ENDPOINT_ROLESMAPPING, roleName), + body: updateObject, + }); } diff --git a/public/apps/configuration/utils/tenancy-config_util.tsx b/public/apps/configuration/utils/tenancy-config_util.tsx index 8850a405c..70ab84c63 100644 --- a/public/apps/configuration/utils/tenancy-config_util.tsx +++ b/public/apps/configuration/utils/tenancy-config_util.tsx @@ -19,7 +19,7 @@ import { httpGet, httpPut, httpPost } from './request-utils'; import { TenancyConfigSettings } from '../panels/tenancy-config/types'; export async function updateTenancyConfig(http: HttpStart, updateObject: TenancyConfigSettings) { - return await httpPost(http, API_ENDPOINT_TENANCY_CONFIGS, updateObject); + return await httpPost({ http, url: API_ENDPOINT_TENANCY_CONFIGS, body: updateObject }); } export async function getTenancyConfig(http: HttpStart): Promise { diff --git a/public/apps/configuration/utils/tenant-utils.tsx b/public/apps/configuration/utils/tenant-utils.tsx index 2fa0772b8..2aa1309dc 100644 --- a/public/apps/configuration/utils/tenant-utils.tsx +++ b/public/apps/configuration/utils/tenant-utils.tsx @@ -97,7 +97,11 @@ export async function updateTenant( tenantName: string, updateObject: TenantUpdate ) { - return await httpPost(http, getResourceUrl(API_ENDPOINT_TENANTS, tenantName), updateObject); + return await httpPost({ + http, + url: getResourceUrl(API_ENDPOINT_TENANTS, tenantName), + body: updateObject, + }); } export async function updateTenancyConfiguration( @@ -116,7 +120,7 @@ export async function requestDeleteTenant(http: HttpStart, tenants: string[]) { } export async function selectTenant(http: HttpStart, selectObject: TenantSelect): Promise { - return await httpPost(http, API_ENDPOINT_MULTITENANCY, selectObject); + return await httpPost({ http, url: API_ENDPOINT_MULTITENANCY, body: selectObject }); } export const RESOLVED_GLOBAL_TENANT = 'Global'; diff --git a/public/utils/datasource-utils.ts b/public/utils/datasource-utils.ts new file mode 100644 index 000000000..79de76a2f --- /dev/null +++ b/public/utils/datasource-utils.ts @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { DataSourceOption } from '../../../../src/plugins/data_source_management/public/components/data_source_selector/data_source_selector'; + +export function createDataSourceQuery(dataSourceId: string) { + return { dataSourceId }; +} + +export function getClusterInfoIfEnabled(dataSourceEnabled: boolean, cluster: DataSourceOption) { + if (dataSourceEnabled) { + return `for ${cluster.label || 'Local cluster'}`; + } + return ''; +} diff --git a/public/utils/login-utils.tsx b/public/utils/login-utils.tsx index e66080c67..7c0d98bec 100644 --- a/public/utils/login-utils.tsx +++ b/public/utils/login-utils.tsx @@ -21,8 +21,12 @@ export async function validateCurrentPassword( userName: string, currentPassword: string ): Promise { - await httpPost(http, '/auth/login', { - username: userName, - password: currentPassword, + await httpPost({ + http, + url: '/auth/login', + body: { + username: userName, + password: currentPassword, + }, }); } diff --git a/public/utils/test/datasource-utils.test.ts b/public/utils/test/datasource-utils.test.ts new file mode 100644 index 000000000..a34ddd951 --- /dev/null +++ b/public/utils/test/datasource-utils.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { createDataSourceQuery, getClusterInfoIfEnabled } from '../datasource-utils'; + +describe('Tests datasource utils', () => { + it('Tests the GetClusterDescription helper function', () => { + expect(getClusterInfoIfEnabled(false, { id: 'blah', label: 'blah' })).toBe(''); + expect(getClusterInfoIfEnabled(true, { id: '', label: '' })).toBe('for Local cluster'); + expect(getClusterInfoIfEnabled(true, { id: 'test', label: 'test' })).toBe('for test'); + }); + + it('Tests the create DataSource query helper function', () => { + expect(createDataSourceQuery('test')).toStrictEqual({ dataSourceId: 'test' }); + }); +}); diff --git a/server/routes/index.ts b/server/routes/index.ts index 7668d358b..8d8da8c05 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -257,7 +257,12 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { if (request.params.resourceName === ResourceType.serviceAccounts.toLowerCase()) { esResp = await client.callAsCurrentUser('opensearch_security.listServiceAccounts'); } else if (request.params.resourceName === 'internalaccounts') { - esResp = await client.callAsCurrentUser('opensearch_security.listInternalAccounts'); + esResp = await wrapRouteWithDataSource( + dataSourceEnabled, + context, + request, + 'opensearch_security.listInternalAccounts' + ); } else { esResp = await wrapRouteWithDataSource( dataSourceEnabled, @@ -359,6 +364,9 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { resourceName: schema.string(), id: schema.string(), }), + query: schema.object({ + dataSourceId: schema.maybe(schema.string()), + }), }, }, async ( @@ -366,13 +374,17 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { request, response ): Promise> => { - const client = context.security_plugin.esClient.asScoped(request); - let esResp; try { - esResp = await client.callAsCurrentUser('opensearch_security.getResource', { - resourceName: request.params.resourceName, - id: request.params.id, - }); + const esResp = await wrapRouteWithDataSource( + dataSourceEnabled, + context, + request, + 'opensearch_security.getResource', + { + resourceName: request.params.resourceName, + id: request.params.id, + } + ); return response.ok({ body: esResp[request.params.id] }); } catch (error) { return errorResponse(response, error); @@ -393,6 +405,9 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { minLength: 1, }), }), + query: schema.object({ + dataSourceId: schema.maybe(schema.string()), + }), }, }, async ( @@ -400,13 +415,17 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { request, response ): Promise> => { - const client = context.security_plugin.esClient.asScoped(request); - let esResp; try { - esResp = await client.callAsCurrentUser('opensearch_security.deleteResource', { - resourceName: request.params.resourceName, - id: request.params.id, - }); + const esResp = await wrapRouteWithDataSource( + dataSourceEnabled, + context, + request, + 'opensearch_security.deleteResource', + { + resourceName: request.params.resourceName, + id: request.params.id, + } + ); return response.ok({ body: { message: esResp.message, @@ -436,6 +455,9 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { resourceName: schema.string(), }), body: schema.any(), + query: schema.object({ + dataSourceId: schema.maybe(schema.string()), + }), }, }, async ( @@ -448,13 +470,17 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { } catch (error) { return response.badRequest({ body: error }); } - const client = context.security_plugin.esClient.asScoped(request); - let esResp; try { - esResp = await client.callAsCurrentUser('opensearch_security.saveResourceWithoutId', { - resourceName: request.params.resourceName, - body: request.body, - }); + const esResp = await wrapRouteWithDataSource( + dataSourceEnabled, + context, + request, + 'opensearch_security.saveResourceWithoutId', + { + resourceName: request.params.resourceName, + body: request.body, + } + ); return response.ok({ body: { message: esResp.message, @@ -480,6 +506,9 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { }), }), body: schema.any(), + query: schema.object({ + dataSourceId: schema.maybe(schema.string()), + }), }, }, async ( @@ -492,14 +521,18 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) { } catch (error) { return response.badRequest({ body: error }); } - const client = context.security_plugin.esClient.asScoped(request); - let esResp; try { - esResp = await client.callAsCurrentUser('opensearch_security.saveResource', { - resourceName: request.params.resourceName, - id: request.params.id, - body: request.body, - }); + const esResp = await wrapRouteWithDataSource( + dataSourceEnabled, + context, + request, + 'opensearch_security.saveResource', + { + resourceName: request.params.resourceName, + id: request.params.id, + body: request.body, + } + ); return response.ok({ body: { message: esResp.message,