diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 468826191..9520c3e2e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @cliu123 @cwperks @DarshitChanpura @davidlago @derek-ho @peternied @RyanL1997 @scrawfor99 +* @cliu123 @cwperks @DarshitChanpura @derek-ho @peternied @RyanL1997 @scrawfor99 diff --git a/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml b/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml index 92540eb6d..bef6e4aea 100644 --- a/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml +++ b/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml @@ -17,6 +17,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] + iteration: [1, 2, 3, 4, 5, 6, 7, 8, 9] runs-on: ${{ matrix.os }} steps: @@ -123,4 +124,4 @@ jobs: uses: ./.github/actions/run-cypress-tests with: dashboards_config_file: opensearch_dashboards_multidatasources.yml - yarn_command: 'yarn cypress:run --browser chrome --headed --env LOGIN_AS_ADMIN=true --spec "test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js"' + yarn_command: 'yarn cypress:run --browser electron --headless --env LOGIN_AS_ADMIN=true --spec "test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js"' diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index e35ae5363..34619622f 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -6,7 +6,7 @@ env: TEST_BROWSER_HEADLESS: 1 CI: 1 PLUGIN_NAME: opensearch-security - OPENSEARCH_INITIAL_ADMIN_PASSWORD: myStrongPassword123! + OPENSEARCH_INITIAL_ADMIN_PASSWORD: admin jobs: tests: diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 4e21da685..c0eedc0a8 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,7 +8,6 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | ---------------- | ----------------------------------------------------- | ----------- | | Chang Liu | [cliu123](https://github.com/cliu123) | Amazon | | Darshit Chanpura | [DarshitChanpura](https://github.com/DarshitChanpura) | Amazon | -| Dave Lago | [davidlago](https://github.com/davidlago) | Amazon | | Peter Nied | [peternied](https://github.com/peternied) | Amazon | | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | @@ -22,3 +21,4 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Yan Zeng | [zengyan-amazon](https://github.com/zengyan-amazon) | Amazon | | Bandini Bhopi | [bandinib-amzn](https://github.com/bandinib-amzn) | Amazon | | Tianle Huang | [tianleh](https://github.com/tianleh) | Amazon | +| Dave Lago | [davidlago](https://github.com/davidlago) | Contributor | diff --git a/common/index.ts b/common/index.ts index b5e6a475d..1a0eb3ff5 100644 --- a/common/index.ts +++ b/common/index.ts @@ -34,6 +34,7 @@ export const OPENID_AUTH_LOGIN_WITH_FRAGMENT = '/auth/openid/captureUrlFragment' export const SAML_AUTH_LOGIN = '/auth/saml/login'; export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; +export const AUTH_TYPE_PARAM = 'auth_type'; export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/cypress.config.js b/cypress.config.js index e5a86728b..4ca1b8df5 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -21,6 +21,8 @@ module.exports = defineConfig({ defaultCommandTimeout: 60000, requestTimeout: 60000, responseTimeout: 60000, + experimentalMemoryManagement: true, + numTestsKeptInMemory: 0, e2e: { setupNodeEvents(on, config) {}, supportFile: 'test/cypress/support/e2e.js', diff --git a/public/apps/configuration/app-router.tsx b/public/apps/configuration/app-router.tsx index 491623ab3..8e23a3065 100644 --- a/public/apps/configuration/app-router.tsx +++ b/public/apps/configuration/app-router.tsx @@ -17,7 +17,7 @@ import { EuiBreadcrumb, EuiPage, EuiPageBody, EuiPageSideBar } from '@elastic/eu import { flow, partial } from 'lodash'; import React, { createContext, useState } from 'react'; import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; -import { DataSourceOption } from '../../../../../src/plugins/data_source_management/public/components/data_source_selector/data_source_selector'; +import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; import { AppDependencies } from '../types'; import { AuditLogging } from './panels/audit-logging/audit-logging'; import { AuditLoggingEditSettings } from './panels/audit-logging/audit-logging-edit-settings'; @@ -41,6 +41,7 @@ import { Action, RouteItem, SubAction } from './types'; import { ResourceType } from '../../../common'; import { buildHashUrl, buildUrl } from './utils/url-builder'; import { CrossPageToast } from './cross-page-toast'; +import { getDataSourceFromUrl } from '../../utils/datasource-utils'; const LANDING_PAGE_URL = '/getstarted'; @@ -155,7 +156,9 @@ export const DataSourceContext = createContext(nul export function AppRouter(props: AppDependencies) { const setGlobalBreadcrumbs = flow(getBreadcrumbs, props.coreStart.chrome.setBreadcrumbs); - const [dataSource, setDataSource] = useState(LocalCluster); + const dataSourceFromUrl = getDataSourceFromUrl(); + + const [dataSource, setDataSource] = useState(dataSourceFromUrl); return ( diff --git a/public/apps/configuration/panels/audit-logging/test/__snapshots__/audit-logging.test.tsx.snap b/public/apps/configuration/panels/audit-logging/test/__snapshots__/audit-logging.test.tsx.snap index 3e64fa6c9..9fdee791a 100644 --- a/public/apps/configuration/panels/audit-logging/test/__snapshots__/audit-logging.test.tsx.snap +++ b/public/apps/configuration/panels/audit-logging/test/__snapshots__/audit-logging.test.tsx.snap @@ -241,7 +241,7 @@ exports[`Audit logs render when AuditLoggingSettings.enabled is true 1`] = `
- (true); + const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { dataSource, setDataSource } = useContext(DataSourceContext)!; React.useEffect(() => { @@ -135,7 +136,13 @@ export function InternalUserEdit(props: InternalUserEditDeps) { setCrossPageToast(buildUrl(ResourceType.users), { id: 'updateUserSucceeded', color: 'success', - title: getSuccessToastMessage('User', props.action, userName), + title: `${getSuccessToastMessage( + 'User', + props.action, + userName, + dataSourceEnabled, + dataSource + )}`, }); // Redirect to user listing window.location.href = buildHashUrl(ResourceType.users); 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 8dcc630d8..c77b294bf 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 @@ -56,7 +56,7 @@ describe('Internal user edit', () => { sourceUserName={sampleUsername} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -77,7 +77,7 @@ describe('Internal user edit', () => { sourceUserName={sampleUsername} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -98,7 +98,7 @@ describe('Internal user edit', () => { sourceUserName={sampleUsername} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -118,7 +118,7 @@ describe('Internal user edit', () => { sourceUserName={sampleUsername} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -140,7 +140,7 @@ describe('Internal user edit', () => { sourceUserName={sampleUsername} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> diff --git a/public/apps/configuration/panels/permission-list/permission-list.tsx b/public/apps/configuration/panels/permission-list/permission-list.tsx index ec90dd7d5..1983fd017 100644 --- a/public/apps/configuration/panels/permission-list/permission-list.tsx +++ b/public/apps/configuration/panels/permission-list/permission-list.tsx @@ -192,6 +192,7 @@ export function PermissionList(props: AppDependencies) { const [selection, setSelection] = React.useState([]); const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); + const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { dataSource, setDataSource } = useContext(DataSourceContext)!; // Modal state @@ -305,7 +306,13 @@ export function PermissionList(props: AppDependencies) { fetchData(); addToast({ id: 'saveSucceeded', - title: getSuccessToastMessage('Action group', action, groupName), + title: `${getSuccessToastMessage( + 'Action group', + action, + groupName, + dataSourceEnabled, + dataSource + )}`, color: 'success', }); } catch (e) { diff --git a/public/apps/configuration/panels/permission-list/test/permission-list.test.tsx b/public/apps/configuration/panels/permission-list/test/permission-list.test.tsx index 856a3e8fb..a15d2eace 100644 --- a/public/apps/configuration/panels/permission-list/test/permission-list.test.tsx +++ b/public/apps/configuration/panels/permission-list/test/permission-list.test.tsx @@ -109,7 +109,7 @@ describe('Permission list page ', () => { const component = shallow( @@ -123,7 +123,7 @@ describe('Permission list page ', () => { shallow( @@ -145,7 +145,7 @@ describe('Permission list page ', () => { shallow( @@ -159,7 +159,7 @@ describe('Permission list page ', () => { const component = shallow( @@ -186,7 +186,7 @@ describe('Permission list page ', () => { const component = shallow( @@ -203,7 +203,7 @@ describe('Permission list page ', () => { shallow( @@ -223,7 +223,7 @@ describe('Permission list page ', () => { const component = shallow( diff --git a/public/apps/configuration/panels/role-edit/index-permission-panel.tsx b/public/apps/configuration/panels/role-edit/index-permission-panel.tsx index e52cb9128..a8a4284b6 100644 --- a/public/apps/configuration/panels/role-edit/index-permission-panel.tsx +++ b/public/apps/configuration/panels/role-edit/index-permission-panel.tsx @@ -23,7 +23,7 @@ import { EuiSuperSelect, EuiTextArea, } from '@elastic/eui'; -import React, { Dispatch, Fragment, SetStateAction } from 'react'; +import React, { Dispatch, Fragment, SetStateAction, useEffect } from 'react'; import { isEmpty } from 'lodash'; import { RoleIndexPermission } from '../../types'; import { ResourceType } from '../../../../../common'; @@ -320,9 +320,11 @@ export function IndexPermissionPanel(props: { }) { const { state, optionUniverse, setState } = props; // Show one empty row if there is no data. - if (isEmpty(state)) { - setState([getEmptyIndexPermission()]); - } + useEffect(() => { + if (isEmpty(state)) { + setState([getEmptyIndexPermission()]); + } + }, [state, setState]); return ( (true); + const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { dataSource, setDataSource } = useContext(DataSourceContext)!; React.useEffect(() => { @@ -174,7 +175,13 @@ export function RoleEdit(props: RoleEditDeps) { setCrossPageToast(buildUrl(ResourceType.roles, Action.view, roleName), { id: 'updateRoleSucceeded', color: 'success', - title: getSuccessToastMessage('Role', props.action, roleName), + title: `${getSuccessToastMessage( + 'Role', + props.action, + roleName, + dataSourceEnabled, + dataSource + )}`, }); // Redirect to role view window.location.href = buildHashUrl(ResourceType.roles, Action.view, roleName); diff --git a/public/apps/configuration/panels/role-edit/tenant-panel.tsx b/public/apps/configuration/panels/role-edit/tenant-panel.tsx index 2cce9e546..6183a27b7 100644 --- a/public/apps/configuration/panels/role-edit/tenant-panel.tsx +++ b/public/apps/configuration/panels/role-edit/tenant-panel.tsx @@ -14,7 +14,7 @@ */ import { EuiButton, EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiSuperSelect } from '@elastic/eui'; -import React, { Dispatch, Fragment, SetStateAction } from 'react'; +import React, { Dispatch, Fragment, SetStateAction, useEffect } from 'react'; import { isEmpty } from 'lodash'; import { RoleTenantPermission, TenantPermissionType, ComboBoxOptions } from '../../types'; import { @@ -129,9 +129,12 @@ export function TenantPanel(props: { }) { const { state, optionUniverse, setState } = props; // Show one empty row if there is no data. - if (isEmpty(state)) { - setState([getEmptyTenantPermission()]); - } + + useEffect(() => { + if (isEmpty(state)) { + setState([getEmptyTenantPermission()]); + } + }, [state, setState]); return ( { const state: RoleIndexPermissionStateClass[] = []; const optionUniverse: ComboBoxOptions = []; - shallow(); + render(); expect(setState).toHaveBeenCalledTimes(1); }); diff --git a/public/apps/configuration/panels/role-edit/test/role-edit.test.tsx b/public/apps/configuration/panels/role-edit/test/role-edit.test.tsx index 81ac845be..4bc01ebe2 100644 --- a/public/apps/configuration/panels/role-edit/test/role-edit.test.tsx +++ b/public/apps/configuration/panels/role-edit/test/role-edit.test.tsx @@ -63,7 +63,7 @@ describe('Role edit', () => { sourceRoleName={sampleSourceRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -89,7 +89,7 @@ describe('Role edit', () => { sourceRoleName={sampleSourceRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -112,7 +112,7 @@ describe('Role edit', () => { sourceRoleName={sampleSourceRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> diff --git a/public/apps/configuration/panels/role-edit/test/tenant-panel.test.tsx b/public/apps/configuration/panels/role-edit/test/tenant-panel.test.tsx index 6fd5adcdc..d7ad8f36c 100644 --- a/public/apps/configuration/panels/role-edit/test/tenant-panel.test.tsx +++ b/public/apps/configuration/panels/role-edit/test/tenant-panel.test.tsx @@ -24,6 +24,7 @@ import { import { shallow } from 'enzyme'; import React from 'react'; import { EuiComboBox, EuiButton, EuiSuperSelect } from '@elastic/eui'; +import { render } from '@testing-library/react'; jest.mock('../../../utils/array-state-utils'); // eslint-disable-next-line @@ -76,7 +77,7 @@ describe('Role edit - tenant panel', () => { const setState = jest.fn(); it('render an empty row if data is empty', () => { - shallow(); + render(); expect(setState).toHaveBeenCalledWith([ { diff --git a/public/apps/configuration/panels/role-mapping/role-edit-mapped-user.tsx b/public/apps/configuration/panels/role-mapping/role-edit-mapped-user.tsx index a2ae92c60..38dc5354f 100644 --- a/public/apps/configuration/panels/role-mapping/role-edit-mapped-user.tsx +++ b/public/apps/configuration/panels/role-mapping/role-edit-mapped-user.tsx @@ -45,7 +45,7 @@ import { setCrossPageToast } from '../../utils/storage-utils'; import { ExternalLink } from '../../utils/display-utils'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; import { DataSourceContext } from '../../app-router'; -import { createDataSourceQuery } from '../../../../utils/datasource-utils'; +import { createDataSourceQuery, getClusterInfoIfEnabled } from '../../../../utils/datasource-utils'; interface RoleEditMappedUserProps extends BreadcrumbsPageDependencies { roleName: string; @@ -63,6 +63,7 @@ export function RoleEditMappedUser(props: RoleEditMappedUserProps) { const [userNames, setUserNames] = useState([]); const [hosts, setHosts] = React.useState([]); const [toasts, addToast, removeToast] = useToastState(); + const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { dataSource, setDataSource } = useContext(DataSourceContext)!; React.useEffect(() => { @@ -130,7 +131,10 @@ export function RoleEditMappedUser(props: RoleEditMappedUserProps) { { id: 'updateRoleMappingSucceeded', color: 'success', - title: 'Role "' + props.roleName + '" successfully updated.', + title: `Role "${props.roleName}" successfully updated ${getClusterInfoIfEnabled( + dataSourceEnabled, + dataSource + )}`, } ); window.location.href = buildHashUrl( diff --git a/public/apps/configuration/panels/role-mapping/test/role-edit-mapped-user.test.tsx b/public/apps/configuration/panels/role-mapping/test/role-edit-mapped-user.test.tsx index 314ca803f..e78435817 100644 --- a/public/apps/configuration/panels/role-mapping/test/role-edit-mapped-user.test.tsx +++ b/public/apps/configuration/panels/role-mapping/test/role-edit-mapped-user.test.tsx @@ -56,7 +56,7 @@ describe('Role mapping edit', () => { roleName={sampleRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -81,7 +81,7 @@ describe('Role mapping edit', () => { roleName={sampleRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -102,7 +102,7 @@ describe('Role mapping edit', () => { roleName={sampleRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -135,7 +135,7 @@ describe('Role mapping edit', () => { roleName={sampleRole} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> diff --git a/public/apps/configuration/panels/role-view/role-view.tsx b/public/apps/configuration/panels/role-view/role-view.tsx index d82710486..5bcb2ae75 100644 --- a/public/apps/configuration/panels/role-view/role-view.tsx +++ b/public/apps/configuration/panels/role-view/role-view.tsx @@ -14,7 +14,6 @@ */ import React, { useState, useContext } from 'react'; - import { EuiButton, EuiPageContentHeader, @@ -72,7 +71,7 @@ import { requestDeleteRoles } from '../../utils/role-list-utils'; import { setCrossPageToast } from '../../utils/storage-utils'; import { DataSourceContext } from '../../app-router'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; -import { createDataSourceQuery } from '../../../../utils/datasource-utils'; +import { createDataSourceQuery, getClusterInfoIfEnabled } from '../../../../utils/datasource-utils'; interface RoleViewProps extends BreadcrumbsPageDependencies { roleName: string; @@ -111,6 +110,7 @@ export function RoleView(props: RoleViewProps) { const [toasts, addToast, removeToast] = useToastState(); const [isReserved, setIsReserved] = React.useState(false); const [loading, setLoading] = React.useState(false); + const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { dataSource, setDataSource } = useContext(DataSourceContext)!; const PERMISSIONS_TAB_INDEX = 0; @@ -378,7 +378,10 @@ export function RoleView(props: RoleViewProps) { setCrossPageToast(buildUrl(ResourceType.roles), { id: 'deleteRole', color: 'success', - title: props.roleName + ' deleted.', + title: `${props.roleName} deleted ${getClusterInfoIfEnabled( + dataSourceEnabled, + dataSource + )}`, }); window.location.href = buildHashUrl(ResourceType.roles); } catch (e) { diff --git a/public/apps/configuration/panels/role-view/test/__snapshots__/role-view.test.tsx.snap b/public/apps/configuration/panels/role-view/test/__snapshots__/role-view.test.tsx.snap index 576e409b2..c5095452f 100644 --- a/public/apps/configuration/panels/role-view/test/__snapshots__/role-view.test.tsx.snap +++ b/public/apps/configuration/panels/role-view/test/__snapshots__/role-view.test.tsx.snap @@ -2,7 +2,7 @@ exports[`Role view basic rendering when permission tab is selected 1`] = ` - - { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -123,7 +123,7 @@ describe('Role view', () => { prevAction={SubAction.mapuser} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -138,7 +138,7 @@ describe('Role view', () => { prevAction={SubAction.mapuser} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -159,7 +159,7 @@ describe('Role view', () => { prevAction={SubAction.mapuser} buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -178,7 +178,7 @@ describe('Role view', () => { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -207,7 +207,7 @@ describe('Role view', () => { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -223,7 +223,7 @@ describe('Role view', () => { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -249,7 +249,7 @@ describe('Role view', () => { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -271,7 +271,7 @@ describe('Role view', () => { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> @@ -291,7 +291,7 @@ describe('Role view', () => { prevAction="" buildBreadcrumbs={buildBreadcrumbs} coreStart={mockCoreStart as any} - navigation={{} as any} + depsStart={{} as any} params={{} as any} config={{} as any} /> diff --git a/public/apps/configuration/panels/tenant-list/manage_tab.tsx b/public/apps/configuration/panels/tenant-list/manage_tab.tsx index bc36114b4..f6689ac95 100644 --- a/public/apps/configuration/panels/tenant-list/manage_tab.tsx +++ b/public/apps/configuration/panels/tenant-list/manage_tab.tsx @@ -68,7 +68,7 @@ import { useContextMenuState } from '../../utils/context-menu'; import { generateResourceName } from '../../utils/resource-utils'; import { DocLinks } from '../../constants'; import { TenantList } from './tenant-list'; -import { getBreadcrumbs } from '../../app-router'; +import { LocalCluster, getBreadcrumbs } from '../../app-router'; import { buildUrl } from '../../utils/url-builder'; import { CrossPageToast } from '../../cross-page-toast'; import { getDashboardsInfo } from '../../../../utils/dashboards-info-utils'; @@ -90,6 +90,7 @@ export function ManageTab(props: AppDependencies) { const [isMultiTenancyEnabled, setIsMultiTenancyEnabled] = useState(false); const [isPrivateTenantEnabled, setIsPrivateTenantEnabled] = useState(false); const [dashboardsDefaultTenant, setDashboardsDefaultTenant] = useState(''); + const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled; const { http } = props.coreStart; @@ -465,7 +466,13 @@ export function ManageTab(props: AppDependencies) { fetchData(); addToast({ id: 'saveSucceeded', - title: getSuccessToastMessage('Tenant', action, tenantName), + title: getSuccessToastMessage( + 'Tenant', + action, + tenantName, + dataSourceEnabled, + LocalCluster + ), color: 'success', }); } catch (e) { diff --git a/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx b/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx index b92dd8806..a00a417d8 100644 --- a/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx +++ b/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx @@ -98,7 +98,7 @@ describe('Tenant list', () => { const component = shallow( @@ -126,7 +126,7 @@ describe('Tenant list', () => { const component = shallow( @@ -145,7 +145,7 @@ describe('Tenant list', () => { shallow( @@ -175,7 +175,7 @@ describe('Tenant list', () => { shallow( @@ -195,7 +195,7 @@ describe('Tenant list', () => { shallow( @@ -220,7 +220,7 @@ describe('Tenant list', () => { shallow( @@ -239,7 +239,7 @@ describe('Tenant list', () => { const component = shallow( @@ -261,7 +261,7 @@ describe('Tenant list', () => { const component = shallow( @@ -299,7 +299,7 @@ describe('Tenant list', () => { const component = shallow( @@ -315,7 +315,7 @@ describe('Tenant list', () => { const component = shallow( @@ -335,7 +335,7 @@ describe('Tenant list', () => { const component = shallow( @@ -364,7 +364,7 @@ describe('Tenant list', () => { component = shallow( diff --git a/public/apps/configuration/panels/test/__snapshots__/get-started.test.tsx.snap b/public/apps/configuration/panels/test/__snapshots__/get-started.test.tsx.snap index f615b622b..0b2126950 100644 --- a/public/apps/configuration/panels/test/__snapshots__/get-started.test.tsx.snap +++ b/public/apps/configuration/panels/test/__snapshots__/get-started.test.tsx.snap @@ -5,7 +5,7 @@ exports[`Get started (landing page) renders when backend configuration is disabl
- - { depsStart={securityPluginStartDepsMock} dataSourcePickerReadOnly={false} dataSourceManagement={dataSourceManagementMock} + selectedDataSource={{}} params={{}} /> ); diff --git a/public/apps/configuration/top-nav-menu.tsx b/public/apps/configuration/top-nav-menu.tsx index c582700e5..669a6d805 100644 --- a/public/apps/configuration/top-nav-menu.tsx +++ b/public/apps/configuration/top-nav-menu.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { DataSourceSelectableConfig } from 'src/plugins/data_source_management/public'; import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; import { AppDependencies } from '../types'; +import { setDataSourceInUrl } from '../../utils/datasource-utils'; export interface TopNavMenuProps extends AppDependencies { dataSourcePickerReadOnly: boolean; @@ -24,51 +25,43 @@ export interface TopNavMenuProps extends AppDependencies { selectedDataSource: DataSourceOption; } -const compatibleVersion = new Set([ - '2.1', - '2.2', - '2.3', - '2.4', - '2.5', - '2.6', - '2.7', - '2.8', - '2.9', - '2.10', - '2.11', - '2.12', -]); +export const SecurityPluginTopNavMenu = React.memo( + (props: TopNavMenuProps) => { + const { + coreStart, + depsStart, + params, + dataSourceManagement, + setDataSource, + selectedDataSource, + dataSourcePickerReadOnly, + } = props; + const { setHeaderActionMenu } = params; + const DataSourceMenu = dataSourceManagement?.ui.getDataSourceMenu(); -export const SecurityPluginTopNavMenu = (props: TopNavMenuProps) => { - const { - coreStart, - depsStart, - params, - dataSourceManagement, - setDataSource, - selectedDataSource, - dataSourcePickerReadOnly, - } = props; - const { setHeaderActionMenu } = params; - const DataSourceMenu = dataSourceManagement?.ui.getDataSourceMenu(); + const dataSourceEnabled = !!depsStart.dataSource?.dataSourceEnabled; - const dataSourceEnabled = !!depsStart.dataSource?.dataSourceEnabled; + const wrapSetDataSourceWithUpdateUrl = (dataSources: DataSourceOption[]) => { + setDataSourceInUrl(dataSources[0]); + setDataSource(dataSources[0]); + }; - return dataSourceEnabled ? ( - compatibleVersion.has(ds.attributes.version), - onSelectedDataSources: (dataSources) => { - // single select for now - setDataSource(dataSources[0]); - }, - fullWidth: true, - }} - /> - ) : null; -}; + return dataSourceEnabled ? ( + + ) : null; + }, + (prevProps, newProps) => + prevProps.selectedDataSource.id === newProps.selectedDataSource.id && + prevProps.dataSourcePickerReadOnly === newProps.dataSourcePickerReadOnly +); diff --git a/public/apps/configuration/utils/test/toast-utils.test.tsx b/public/apps/configuration/utils/test/toast-utils.test.tsx index 921751da5..82b6b8ad9 100644 --- a/public/apps/configuration/utils/test/toast-utils.test.tsx +++ b/public/apps/configuration/utils/test/toast-utils.test.tsx @@ -101,21 +101,30 @@ describe('Toast utils', () => { describe('getSuccessToastMessage', () => { it('should return successful create message', () => { - const result = getSuccessToastMessage('User', 'create', 'user1'); + const result = getSuccessToastMessage('User', 'create', 'user1', false, { id: '' }); - expect(result).toEqual('User "user1" successfully created'); + expect(result).toEqual('User "user1" successfully created '); }); it('should return successful update message', () => { - const result = getSuccessToastMessage('Role', 'edit', 'role1'); + const result = getSuccessToastMessage('Role', 'edit', 'role1', false, { id: '' }); - expect(result).toEqual('Role "role1" successfully updated'); + expect(result).toEqual('Role "role1" successfully updated '); }); it('should return empty message for unknown action', () => { - const result = getSuccessToastMessage('User', '', 'user1'); + const result = getSuccessToastMessage('User', '', 'user1', false, { id: '' }); expect(result).toEqual(''); }); + + it('should return successful update message for remote cluster', () => { + const result = getSuccessToastMessage('Role', 'edit', 'role1', true, { + id: '', + label: 'blah', + }); + + expect(result).toEqual('Role "role1" successfully updated for blah'); + }); }); }); diff --git a/public/apps/configuration/utils/toast-utils.tsx b/public/apps/configuration/utils/toast-utils.tsx index db6fea635..450c01be7 100644 --- a/public/apps/configuration/utils/toast-utils.tsx +++ b/public/apps/configuration/utils/toast-utils.tsx @@ -15,6 +15,8 @@ import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; import { useState, useCallback } from 'react'; +import { getClusterInfoIfEnabled } from '../../../utils/datasource-utils'; +import { DataSourceOption } from '../../../../../../src/plugins/data_source_management/public/components/data_source_menu/types'; export function createErrorToast(id: string, title: string, text: string): Toast { return { @@ -66,14 +68,22 @@ export function useToastState(): [Toast[], (toAdd: Toast) => void, (toDelete: To export function getSuccessToastMessage( resourceType: string, action: string, - userName: string + userName: string, + dataSourceEnabled: boolean, + dataSource: DataSourceOption ): string { switch (action) { case 'create': case 'duplicate': - return `${resourceType} "${userName}" successfully created`; + return `${resourceType} "${userName}" successfully created ${getClusterInfoIfEnabled( + dataSourceEnabled, + dataSource + )}`; case 'edit': - return `${resourceType} "${userName}" successfully updated`; + return `${resourceType} "${userName}" successfully updated ${getClusterInfoIfEnabled( + dataSourceEnabled, + dataSource + )}`; default: return ''; } diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index a22a36dc7..1e5f43dd8 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -231,14 +231,14 @@ export function LoginPage(props: LoginPageDeps) { ); - if (authOpts.length > 1) { - if (props.config.auth.anonymous_auth_enabled) { - const anonymousConfig = props.config.ui[AuthType.ANONYMOUS].login; - formBody.push( - renderLoginButton(AuthType.ANONYMOUS, ANONYMOUS_AUTH_LOGIN, anonymousConfig) - ); - } + if (props.config.auth.anonymous_auth_enabled) { + const anonymousConfig = props.config.ui[AuthType.ANONYMOUS].login; + formBody.push( + renderLoginButton(AuthType.ANONYMOUS, ANONYMOUS_AUTH_LOGIN, anonymousConfig) + ); + } + if (authOpts.length > 1) { formBody.push(); formBody.push(); formBody.push(); diff --git a/public/apps/login/test/__snapshots__/login-page.test.tsx.snap b/public/apps/login/test/__snapshots__/login-page.test.tsx.snap index b8a1e1182..de432dcca 100644 --- a/public/apps/login/test/__snapshots__/login-page.test.tsx.snap +++ b/public/apps/login/test/__snapshots__/login-page.test.tsx.snap @@ -153,6 +153,419 @@ exports[`Login page renders renders with config value for multiauth 1`] = ` `; +exports[`Login page renders renders with config value for multiauth with anonymous auth enabled 1`] = ` + + + + + Title1 + + + + SubTitle1 + + + + + + } + value="" + /> + + + + } + type="password" + value="" + /> + + + + Log in + + + + + + + + + + + Button1 + + + + + Button2 + + + + +`; + +exports[`Login page renders renders with config value with anonymous auth enabled: string 1`] = ` + + + + + Title1 + + + + SubTitle1 + + + + + + } + value="" + /> + + + + } + type="password" + value="" + /> + + + + Log in + + + + + + + +`; + +exports[`Login page renders renders with config value with anonymous auth enabled: string array 1`] = ` + + + + + Title1 + + + + SubTitle1 + + + + + + } + value="" + /> + + + + } + type="password" + value="" + /> + + + + Log in + + + + + + + +`; + exports[`Login page renders renders with config value: string 1`] = ` { expect(component).toMatchSnapshot(); }); + it('renders with config value with anonymous auth enabled: string array', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: [AuthType.BASIC], + logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + it('renders with config value: string', () => { const config: ClientConfigType = { ui: configUI, @@ -166,12 +190,42 @@ describe('Login page', () => { expect(component).toMatchSnapshot(); }); + it('renders with config value with anonymous auth enabled: string', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: AuthType.BASIC, + logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + it('renders with config value for multiauth', () => { const config: ClientConfigType = { ui: configUI, auth: { - type: [AuthType.BASIC, 'openid', AuthType.SAML], + type: [AuthType.BASIC, AuthType.OPEN_ID, AuthType.SAML], + logout_url: API_AUTH_LOGOUT, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + + it('renders with config value for multiauth with anonymous auth enabled', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: [AuthType.BASIC, AuthType.OPEN_ID, AuthType.SAML], logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, }, }; const component = shallow( diff --git a/public/utils/datasource-utils.ts b/public/utils/datasource-utils.ts index 79de76a2f..59db29aac 100644 --- a/public/utils/datasource-utils.ts +++ b/public/utils/datasource-utils.ts @@ -13,15 +13,27 @@ * permissions and limitations under the License. */ -import { DataSourceOption } from '../../../../src/plugins/data_source_management/public/components/data_source_selector/data_source_selector'; +import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; export function createDataSourceQuery(dataSourceId: string) { return { dataSourceId }; } +const DATASOURCEURLKEY = 'dataSource'; + export function getClusterInfoIfEnabled(dataSourceEnabled: boolean, cluster: DataSourceOption) { if (dataSourceEnabled) { return `for ${cluster.label || 'Local cluster'}`; } return ''; } + +export function getDataSourceFromUrl(): DataSourceOption { + return JSON.parse(new URLSearchParams(window.location.search).get(DATASOURCEURLKEY) || '{}'); +} + +export function setDataSourceInUrl(dataSource: DataSourceOption) { + const url = new URL(window.location.href); + url.searchParams.set(DATASOURCEURLKEY, JSON.stringify(dataSource)); + window.history.replaceState({}, '', url.toString()); +} diff --git a/public/utils/test/datasource-utils.test.ts b/public/utils/test/datasource-utils.test.ts index a34ddd951..9db4af61b 100644 --- a/public/utils/test/datasource-utils.test.ts +++ b/public/utils/test/datasource-utils.test.ts @@ -13,7 +13,12 @@ * permissions and limitations under the License. */ -import { createDataSourceQuery, getClusterInfoIfEnabled } from '../datasource-utils'; +import { + createDataSourceQuery, + getClusterInfoIfEnabled, + getDataSourceFromUrl, + setDataSourceInUrl, +} from '../datasource-utils'; describe('Tests datasource utils', () => { it('Tests the GetClusterDescription helper function', () => { @@ -25,4 +30,52 @@ describe('Tests datasource utils', () => { it('Tests the create DataSource query helper function', () => { expect(createDataSourceQuery('test')).toStrictEqual({ dataSourceId: 'test' }); }); + + it('Tests getting the datasource from the url', () => { + const mockSearchNoDataSourceId = '?foo=bar&baz=qux'; + Object.defineProperty(window, 'location', { + value: { search: mockSearchNoDataSourceId }, + writable: true, + }); + expect(getDataSourceFromUrl()).toEqual({}); + const mockSearchDataSourceIdNotfirst = + '?foo=bar&baz=qux&dataSource=%7B"id"%3A"94ffa650-f11a-11ee-a585-793f7b098e1a"%2C"label"%3A"9202"%7D'; + Object.defineProperty(window, 'location', { + value: { search: mockSearchDataSourceIdNotfirst }, + writable: true, + }); + expect(getDataSourceFromUrl()).toEqual({ + id: '94ffa650-f11a-11ee-a585-793f7b098e1a', + label: '9202', + }); + const mockSearchDataSourceIdFirst = + '?dataSource=%7B"id"%3A"94ffa650-f11a-11ee-a585-793f7b098e1a"%2C"label"%3A"9202"%7D'; + Object.defineProperty(window, 'location', { + value: { search: mockSearchDataSourceIdFirst }, + writable: true, + }); + expect(getDataSourceFromUrl()).toEqual({ + id: '94ffa650-f11a-11ee-a585-793f7b098e1a', + label: '9202', + }); + }); + + it('Tests setting the datasource in the url', () => { + const replaceState = jest.fn(); + const mockUrl = 'http://localhost:5601/app/security-dashboards-plugin#/auth'; + Object.defineProperty(window, 'location', { + value: { href: mockUrl }, + writable: true, + }); + Object.defineProperty(window, 'history', { + value: { replaceState }, + writable: true, + }); + setDataSourceInUrl({ id: '', label: 'Local cluster' }); + expect(replaceState).toBeCalledWith( + {}, + '', + 'http://localhost:5601/app/security-dashboards-plugin?dataSource=%7B%22id%22%3A%22%22%2C%22label%22%3A%22Local+cluster%22%7D#/auth' + ); + }); }); diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index f21f86827..a9cfedb6c 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -28,9 +28,14 @@ import { SecurityPluginConfigType } from '../../..'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { BasicAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; -import { LOGIN_PAGE_URI, ANONYMOUS_AUTH_LOGIN } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; -import { AUTH_HEADER_NAME, AuthType, OPENDISTRO_SECURITY_ANONYMOUS } from '../../../../common'; +import { + LOGIN_PAGE_URI, + ANONYMOUS_AUTH_LOGIN, + AUTH_HEADER_NAME, + AuthType, + OPENDISTRO_SECURITY_ANONYMOUS, +} from '../../../../common'; export class BasicAuthentication extends AuthenticationType { public readonly type: string = AuthType.BASIC; diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index b190d9d03..b00b3d154 100644 --- a/server/auth/types/multiple/multi_auth.ts +++ b/server/auth/types/multiple/multi_auth.ts @@ -25,7 +25,7 @@ import { import { OpenSearchDashboardsResponse } from '../../../../../../src/core/server/http/router'; import { SecurityPluginConfigType } from '../../..'; import { AuthenticationType } from '../authentication_type'; -import { ANONYMOUS_AUTH_LOGIN, AuthType, LOGIN_PAGE_URI } from '../../../../common'; +import { AuthType, LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { MultiAuthRoutes } from './routes'; import { SecuritySessionCookie } from '../../../session/security_cookie'; @@ -166,14 +166,6 @@ export class MultipleAuthentication extends AuthenticationType { this.coreSetup.http.basePath.serverBasePath ); - if (this.config.auth.anonymous_auth_enabled) { - const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${ANONYMOUS_AUTH_LOGIN}?${nextUrlParam}`; - return response.redirected({ - headers: { - location: `${redirectLocation}`, - }, - }); - } return response.redirected({ headers: { location: `${this.coreSetup.http.basePath.serverBasePath}${LOGIN_PAGE_URI}?${nextUrlParam}`, diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 87605d65e..d14d0711b 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -131,11 +131,12 @@ export class SamlAuthRoutes { } try { - const credentials = await this.securityClient.authToken( + const credentials = await this.securityClient.authToken({ requestId, - request.body.SAMLResponse, - undefined - ); + samlResponse: request.body.SAMLResponse, + acsEndpoint: undefined, + authRequestType: AuthType.SAML, + }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', @@ -208,11 +209,12 @@ export class SamlAuthRoutes { async (context, request, response) => { const acsEndpoint = `${this.coreSetup.http.basePath.serverBasePath}/_opendistro/_security/saml/acs/idpinitiated`; try { - const credentials = await this.securityClient.authToken( - undefined, - request.body.SAMLResponse, - acsEndpoint - ); + const credentials = await this.securityClient.authToken({ + requestId: undefined, + samlResponse: request.body.SAMLResponse, + acsEndpoint, + authRequestType: AuthType.SAML, + }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 71a65d205..6f2d3439f 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -16,6 +16,7 @@ import { ILegacyClusterClient, OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { User } from '../auth/user'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; +import { AUTH_TYPE_PARAM, AuthType } from '../../common'; export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -182,7 +183,9 @@ export class SecurityClient { public async getSamlHeader(request: OpenSearchDashboardsRequest) { try { // response is expected to be an error - await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo'); + await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo', { + [AUTH_TYPE_PARAM]: AuthType.SAML, + }); } catch (error: any) { // the error looks like // wwwAuthenticateDirective: @@ -217,11 +220,12 @@ export class SecurityClient { throw new Error(`Invalid SAML configuration.`); } - public async authToken( - requestId: string | undefined, - samlResponse: any, - acsEndpoint: any | undefined = undefined - ) { + public async authToken({ + requestId, + samlResponse, + acsEndpoint = undefined, + authRequestType, + }: AuthTokenParams) { const body = { RequestId: requestId, SAMLResponse: samlResponse, @@ -230,6 +234,7 @@ export class SecurityClient { try { return await this.esClient.asScoped().callAsCurrentUser('opensearch_security.authtoken', { body, + [AUTH_TYPE_PARAM]: authRequestType, }); } catch (error: any) { console.log(error); @@ -237,3 +242,10 @@ export class SecurityClient { } } } + +interface AuthTokenParams { + requestId?: string; + samlResponse: any; + acsEndpoint?: any; + authRequestType?: string; +} diff --git a/test/constant.ts b/test/constant.ts index d4ab9e3ae..5dcb387e2 100644 --- a/test/constant.ts +++ b/test/constant.ts @@ -13,14 +13,9 @@ * permissions and limitations under the License. */ -import { version, opensearchDashboards } from '../package.json'; - export const OPENSEARCH_DASHBOARDS_SERVER_USER: string = 'kibanaserver'; export const OPENSEARCH_DASHBOARDS_SERVER_PASSWORD: string = 'kibanaserver'; -export const ELASTICSEARCH_VERSION: string = opensearchDashboards.version; -export const SECURITY_ES_PLUGIN_VERSION: string = version; - export const ADMIN_USER: string = 'admin'; export const ADMIN_PASSWORD: string = process.env.ADMIN_PASSWORD || 'admin'; const ADMIN_USER_PASS: string = `${ADMIN_USER}:${ADMIN_PASSWORD}`; diff --git a/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js b/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js index 663c73e02..fc40197af 100644 --- a/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js +++ b/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js @@ -13,8 +13,9 @@ * permissions and limitations under the License. */ +const externalTitle = '9202'; const createDataSource = () => { - cy.request({ + return cy.request({ method: 'POST', url: `${Cypress.config('baseUrl')}/api/saved_objects/data-source`, headers: { @@ -22,7 +23,7 @@ const createDataSource = () => { }, body: { attributes: { - title: '9202', + title: externalTitle, endpoint: 'https://localhost:9202', auth: { type: 'username_password', @@ -36,15 +37,6 @@ const createDataSource = () => { }); }; -const closeToast = () => { - // remove browser incompatibiltiy toast causing flakyness (cause it has higher z-index than Create button making it invisible) - cy.get('body').then((body) => { - if (body.find('[data-test-subj="toastCloseButton"]').length > 0) { - cy.get('[data-test-subj="toastCloseButton"]').click(); - } - }); -}; - const deleteAllDataSources = () => { cy.request( 'GET', @@ -67,55 +59,64 @@ const deleteAllDataSources = () => { }); }; +const createUrlParam = (label, id) => { + const dataSourceObj = { label, id }; + + return `?dataSource=${JSON.stringify(dataSourceObj)}`; +}; + +let externalDataSourceId; +let externalDataSourceUrl; +let localDataSourceUrl; + describe('Multi-datasources enabled', () => { - before(() => { + beforeEach(() => { deleteAllDataSources(); localStorage.setItem('opendistro::security::tenant::saved', '""'); localStorage.setItem('home:newThemeModal:show', 'false'); - createDataSource(); + createDataSource().then((resp) => { + if (resp && resp.body) { + externalDataSourceId = resp.body.id; + } + externalDataSourceUrl = createUrlParam(externalTitle, externalDataSourceId); + localDataSourceUrl = createUrlParam('Local cluster', ''); + }); }); - after(() => { + afterEach(() => { + cy.clearCookies(); + cy.clearAllLocalStorage(); + cy.clearAllSessionStorage(); deleteAllDataSources(); - cy.clearLocalStorage(); }); it('Checks Get Started Tab', () => { - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/getstarted'); - // Local cluster purge cache - cy.get('[data-test-subj="purge-cache"]').click(); - cy.get('.euiToastHeader__title').should('contain', 'successful for Local cluster'); // Remote cluster purge cache - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="9202"]').click(); + cy.visit( + `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/getstarted` + ); + + cy.contains('h1', 'Get started'); + cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( + 'contain', + '9202' + ); + cy.get('[data-test-subj="purge-cache"]').click(); - cy.get('.euiToastHeader__title').should('contain', 'successful for 9202'); - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/auth'); - // Data source persisted across tabs - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').contains('9202'); + cy.get('[class="euiToast euiToast--success euiGlobalToastListItem"]') + .get('.euiToastHeader__title') + .should('contain', 'successful for 9202'); }); it('Checks Auth Tab', () => { - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/auth'); - // Local cluster auth - cy.get('.panel-header-count').first().invoke('text').should('contain', '(6)'); - // Remote cluster auth - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="9202"]').click(); - cy.get('.panel-header-count').first().invoke('text').should('contain', '(6)'); + cy.visit(`http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/auth`); + + cy.get('.panel-header-count').first().invoke('text').should('contain', '(2)'); }); it('Checks Users Tab', () => { - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/users'); - // Create an internal user in the remote cluster - cy.contains('h3', 'Internal users'); - cy.contains('a', 'admin'); - - closeToast(); - // select remote data source - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="9202"]').click(); + cy.visit(`http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/users`); // create a user on remote data source cy.get('[data-test-subj="create-user"]').click(); @@ -129,28 +130,16 @@ describe('Multi-datasources enabled', () => { 'contain', '9202' ); + cy.get('[data-test-subj="tableHeaderCell_username_0"]').click(); cy.get('[data-test-subj="checkboxSelectRow-9202-user"]').should('exist'); - - // Internal user doesn't exist on local cluster - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="Local cluster"]').click(); - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( - 'contain', - 'Local cluster' - ); - cy.get('[data-test-subj="checkboxSelectRow-9202-user"]').should('not.exist'); }); it('Checks Permissions Tab', () => { - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/permissions'); - // Create a permission in the remote cluster - cy.contains('h3', 'Permissions'); - - closeToast(); - // Select remote cluster - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="9202"]').click(); + cy.visit( + `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/permissions` + ); + cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( 'contain', '9202' @@ -161,123 +150,88 @@ describe('Multi-datasources enabled', () => { cy.get('[id="create-from-blank"]').click(); cy.get('[data-test-subj="name-text"]') .focus() - .type('test_permission_ag', { force: true }) - .should('have.value', 'test_permission_ag'); + .type('9202-permission', { force: true }) + .should('have.value', '9202-permission'); cy.get('[data-test-subj="comboBoxInput"]').focus().type('some_permission'); cy.get('[id="submit"]').click(); - closeToast(); // Permission exists on the remote data source - cy.get('[data-text="Customization"]').click(); - cy.get('[data-test-subj="filter-custom"]').click(); - cy.get('[data-test-subj="checkboxSelectRow-test_permission_ag"]').should('exist'); - - // Permission doesn't exist on local cluster - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="Local cluster"]').click(); - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( - 'contain', - 'Local cluster' - ); - cy.get('[data-test-subj="checkboxSelectRow-test_permission_ag"]').should('not.exist'); + cy.get('[data-test-subj="tableHeaderCell_name_0"]').click(); + cy.get('[data-test-subj="checkboxSelectRow-9202-permission"]').should('exist'); }); it('Checks Tenancy Tab', () => { // Datasource is locked to local cluster for tenancy tab - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/tenants'); - cy.contains('h1', 'Multi-tenancy'); - cy.get('[data-test-subj="dataSourceViewContextMenuHeaderLink"]').should( + cy.visit(`http://localhost:5601/app/security-dashboards-plugin${localDataSourceUrl}#/tenants`); + + cy.contains('h1', 'Dashboards multi-tenancy'); + cy.get('[data-test-subj="dataSourceViewButton"]').should( 'contain', 'Local cluster' ); - cy.get('[data-test-subj="dataSourceViewContextMenuHeaderLink"]').should('be.disabled'); + cy.get('[data-test-subj="dataSourceViewButton"]').should('be.disabled'); }); it('Checks Service Accounts Tab', () => { // Datasource is locked to local cluster for service accounts tab - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/serviceAccounts'); - cy.get('[data-test-subj="dataSourceViewContextMenuHeaderLink"]').should( + cy.visit( + `http://localhost:5601/app/security-dashboards-plugin${localDataSourceUrl}#/serviceAccounts` + ); + + cy.get('[data-test-subj="dataSourceViewButton"]').should( 'contain', 'Local cluster' ); - cy.get('[data-test-subj="dataSourceViewContextMenuHeaderLink"]').should('be.disabled'); + cy.get('[data-test-subj="dataSourceViewButton"]').should('be.disabled'); }); it('Checks Audit Logs Tab', () => { - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/auditLogging'); - cy.get('[data-test-subj="general-settings"]').should('exist'); - // Select remote cluster - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="9202"]').click(); + cy.visit( + `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/auditLogging` + ); + cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( 'contain', '9202' ); cy.get('[data-test-subj="general-settings-configure"]').click(); - cy.get('[data-test-subj="dataSourceViewContextMenuHeaderLink"]').should('contain', '9202'); + cy.get('[data-test-subj="dataSourceViewButton"]').should('contain', '9202'); - closeToast(); cy.get('[data-test-subj="comboBoxInput"]').last().type('blah'); cy.get('[data-test-subj="save"]').click(); - - cy.get('[data-test-subj="general-settings"]').should('contain', 'blah'); - - // Select local cluster - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="Local cluster"]').click(); cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( 'contain', - 'Local cluster' + '9202' ); - cy.get('[data-test-subj="general-settings"]').should('not.contain', 'blah'); + cy.get('[data-test-subj="general-settings"]').should('contain', 'blah'); }); - it('Checks Roles Tab', () => { + it.skip('Checks Roles Tab', () => { Cypress.on('uncaught:exception', (err) => !err.message.includes('ResizeObserver')); - - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/roles'); - cy.contains('h3', 'Roles'); - - closeToast(); - // select remote data source - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="9202"]').click(); + cy.visit(`http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/roles`); // create a role on remote data source cy.get('[data-test-subj="create-role"]').click(); cy.contains('h1', 'Create Role'); cy.get('[data-test-subj="name-text"]').focus().type('9202-role'); - cy.get('[data-test-subj="comboBoxToggleListButton"]').first().click(); - cy.get('button[title="manage_snapshots"]').should('be.visible'); - cy.get('button[title="manage_snapshots"]').click({ force: true }); - - cy.get('[data-test-subj="comboBoxInput"]').first().should('contain', 'manage_snapshots'); cy.get('[data-test-subj="create-or-update-role"]').click(); - cy.get('.euiToastHeader__title').should('contain', 'Role "9202-role" successfully created'); - closeToast(); + cy.get('[class="euiToast euiToast--success euiGlobalToastListItem"]') + .get('.euiToastHeader__title') + .should('contain', 'Role "9202-role" successfully created'); // role exists on the remote - cy.visit('http://localhost:5601/app/security-dashboards-plugin#/roles'); + cy.visit(`http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/roles`); + cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( 'contain', '9202' ); - cy.get('[data-text="Customization"]').click(); - cy.get('[data-test-subj="filter-custom"]').click(); + cy.get('[data-test-subj="tableHeaderCell_roleName_0"]').click(); cy.get('[data-test-subj="checkboxSelectRow-9202-role"]').should('exist'); - - // Role doesn't exist on local cluster - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').click(); - cy.get('[title="Local cluster"]').click(); - cy.get('[data-test-subj="dataSourceSelectableContextMenuHeaderLink"]').should( - 'contain', - 'Local cluster' - ); - cy.get('[data-test-subj="checkboxSelectRow-9202-role"]').should('not.exist'); }); }); diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index b4bbd3e55..cdb4a39d0 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -132,7 +132,6 @@ describe('start OpenSearch Dashboards server', () => { }); it('call authinfo API as admin', async () => { - const testUserCredentials = Buffer.from(ADMIN_CREDENTIALS); const response = await osdTestServer.request .get(root, '/api/v1/auth/authinfo') .set(AUTHORIZATION_HEADER_NAME, ADMIN_CREDENTIALS); @@ -142,7 +141,7 @@ describe('start OpenSearch Dashboards server', () => { it('call authinfo API without credentials', async () => { const response = await osdTestServer.request .get(root, '/api/v1/auth/authinfo') - .unset('Authorization'); + .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(401); }); @@ -209,7 +208,9 @@ describe('start OpenSearch Dashboards server', () => { }); it('enforce authentication on api/status route', async () => { - const response = await osdTestServer.request.get(root, '/api/status'); + const response = await osdTestServer.request + .get(root, '/api/status') + .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(401); });