diff --git a/.eslintrc.js b/.eslintrc.js index 5e176b8bb..1d8ac940b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,7 @@ module.exports = { }, plugins: [ 'cypress', + "unused-imports" ], rules: { // "@osd/eslint/require-license-header": "off" @@ -44,6 +45,9 @@ module.exports = { 'cypress/assertion-before-screenshot': 'warn', 'cypress/no-force': 'warn', 'cypress/no-async-tests': 'error', + // Unused imports and variables rules + "no-unused-vars": "off", + "unused-imports/no-unused-imports": "error", }, overrides: [ { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 547813578..468826191 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @cliu123 @cwperks @DarshitChanpura @davidlago @peternied @RyanL1997 @scrawfor99 +* @cliu123 @cwperks @DarshitChanpura @davidlago @derek-ho @peternied @RyanL1997 @scrawfor99 diff --git a/.github/actions/run-cypress-tests/action.yml b/.github/actions/run-cypress-tests/action.yml index 678634455..74e001ff7 100644 --- a/.github/actions/run-cypress-tests/action.yml +++ b/.github/actions/run-cypress-tests/action.yml @@ -56,7 +56,7 @@ runs: if: ${{ runner.os == 'Linux' }} run: | cd ./OpenSearch-Dashboards/plugins/security-dashboards-plugin - yarn pretest:jest_server + yarn runIdp shell: bash - name: Run OpenSearch Dashboards with provided configuration diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 2a15958df..e35ae5363 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: admin + OPENSEARCH_INITIAL_ADMIN_PASSWORD: myStrongPassword123! jobs: tests: @@ -98,7 +98,8 @@ jobs: run: | echo "check if opensearch is ready" curl -XGET https://localhost:9200 -u 'admin:${{ env.OPENSEARCH_INITIAL_ADMIN_PASSWORD }}' -k - yarn test:jest_server --coverage + ADMIN_PASSWORD=${{ env.OPENSEARCH_INITIAL_ADMIN_PASSWORD }} yarn test:jest_server --coverage + shell: bash working-directory: ${{ steps.install-dashboards.outputs.plugin-directory }} - name: Run integration tests on Windows @@ -106,5 +107,6 @@ jobs: run: | echo "check if opensearch is ready" curl -XGET https://localhost:9200 -u 'admin:${{ env.OPENSEARCH_INITIAL_ADMIN_PASSWORD }}' -k - node .\test\run_jest_tests.js --runInBand --detectOpenHandles --forceExit --config .\test\jest.config.server.js + export ADMIN_PASSWORD=${{ env.OPENSEARCH_INITIAL_ADMIN_PASSWORD }} && node ./test/run_jest_tests.js --runInBand --detectOpenHandles --forceExit --config ./test/jest.config.server.js + shell: bash working-directory: ${{ steps.install-dashboards.outputs.plugin-directory }} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..deae1a9ce --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn lint:es --fix \ No newline at end of file diff --git a/MAINTAINERS.md b/MAINTAINERS.md index f7d069cd1..4e21da685 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -13,6 +13,7 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | | Stephen Crawford | [scrawfor99](https://github.com/scrawfor99) | Amazon | +| Derek Ho | [derek-ho](https://github.com/derek-ho) | Amazon | ## Emeritus diff --git a/package.json b/package.json index e888182d9..acd56721e 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,10 @@ "lint:es": "node ../../scripts/eslint", "lint:style": "node ../../scripts/stylelint", "lint": "yarn run lint:es && yarn run lint:style", - "pretest:jest_server": "node ./test/jest_integration/runIdpServer.js &", - "test:jest_server": "node ./test/run_jest_tests.js --config ./test/jest.config.server.js", - "test:jest_ui": "node ./test/run_jest_tests.js --config ./test/jest.config.ui.js" + "runIdp": "node ./test/jest_integration/runIdpServer.js &", + "test:jest_server": "ADMIN_PASSWORD=$ADMIN_PASSWORD node ./test/run_jest_tests.js --config ./test/jest.config.server.js", + "test:jest_ui": "node ./test/run_jest_tests.js --config ./test/jest.config.ui.js", + "prepare": "husky install" }, "devDependencies": { "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", @@ -34,7 +35,9 @@ "saml-idp": "^1.2.1", "selfsigned": "^2.0.1", "typescript": "4.0.2", - "eslint-plugin-cypress": "^2.8.1" + "eslint-plugin-cypress": "^2.8.1", + "eslint-plugin-unused-imports": "3.1.0", + "husky": "^8.0.0" }, "dependencies": { "@hapi/cryptiles": "5.0.0", @@ -46,4 +49,4 @@ "glob-parent": "^5.1.2", "debug": "^4.3.4" } -} \ No newline at end of file +} diff --git a/public/apps/account/tenant-switch-panel.tsx b/public/apps/account/tenant-switch-panel.tsx index 2768d9eb0..103647ec8 100755 --- a/public/apps/account/tenant-switch-panel.tsx +++ b/public/apps/account/tenant-switch-panel.tsx @@ -40,7 +40,7 @@ import { } from '../configuration/utils/tenant-utils'; import { fetchAccountInfo } from './utils'; import { constructErrorMessageAndLog } from '../error-utils'; -import { getSavedTenant, setSavedTenant } from '../../utils/storage-utils'; +import { setSavedTenant } from '../../utils/storage-utils'; import { getDashboardsInfo } from '../../utils/dashboards-info-utils'; interface TenantSwitchPanelProps { diff --git a/public/apps/account/test/plugin.test.tsx b/public/apps/account/test/plugin.test.tsx index a792eea30..f3e7d4a76 100644 --- a/public/apps/account/test/plugin.test.tsx +++ b/public/apps/account/test/plugin.test.tsx @@ -13,15 +13,39 @@ * permissions and limitations under the License. */ +import { LOGIN_PAGE_URI } from '../../../../common'; import { interceptError } from '../../../utils/logout-utils'; import { setShouldShowTenantPopup } from '../../../utils/storage-utils'; -import { LOGIN_PAGE_URI } from '../../../../common'; jest.mock('../../../utils/storage-utils', () => ({ setShouldShowTenantPopup: jest.fn(), })); +interface LooseObject { + [key: string]: any; +} + +// Mock sessionStorage +const sessionStorageMock = (() => { + let store = {} as LooseObject; + return { + clear() { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, 'sessionStorage', { value: sessionStorageMock }); + describe('Intercept error handler', () => { + beforeEach(() => { + jest.spyOn(window.sessionStorage, 'clear'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + const fakeError401 = { response: { status: 401, @@ -34,15 +58,28 @@ describe('Intercept error handler', () => { }, }; - it('Intercept error handler Should call setShouldShowTenantPopup on session timeout', () => { + it('Intercept error handler should call setShouldShowTenantPopup on session timeout', () => { const sessionTimeoutFn = interceptError(LOGIN_PAGE_URI, window); sessionTimeoutFn(fakeError401, null); expect(setShouldShowTenantPopup).toBeCalledTimes(1); + expect(sessionStorage.clear).toBeCalledTimes(1); + }); + + it('Intercept error handler should clear the session', () => { + const sessionTimeoutFn = interceptError(LOGIN_PAGE_URI, window); + sessionTimeoutFn(fakeError401, null); + expect(sessionStorage.clear).toBeCalledTimes(1); }); - it('Intercept error handler Should not call setShouldShowTenantPopup on session timeout', () => { + it('Intercept error handler should not call setShouldShowTenantPopup on session timeout', () => { const sessionTimeoutFn = interceptError(LOGIN_PAGE_URI, window); sessionTimeoutFn(fakeError400, null); expect(setShouldShowTenantPopup).toBeCalledTimes(0); }); + + it('Intercept error handler should not clear the session', () => { + const sessionTimeoutFn = interceptError(LOGIN_PAGE_URI, window); + sessionTimeoutFn(fakeError400, null); + expect(sessionStorage.clear).toBeCalledTimes(0); + }); }); diff --git a/public/apps/account/utils.tsx b/public/apps/account/utils.tsx index e411895bc..2f9f25eee 100644 --- a/public/apps/account/utils.tsx +++ b/public/apps/account/utils.tsx @@ -14,16 +14,11 @@ */ import { HttpStart } from 'opensearch-dashboards/public'; -import { - API_AUTH_LOGOUT, - LOGIN_PAGE_URI, - OPENID_AUTH_LOGOUT, - SAML_AUTH_LOGOUT, -} from '../../../common'; +import { API_AUTH_LOGOUT } from '../../../common'; +import { setShouldShowTenantPopup } from '../../utils/storage-utils'; +import { httpGet, httpGetWithIgnores, httpPost } from '../configuration/utils/request-utils'; import { API_ENDPOINT_ACCOUNT_INFO } from './constants'; import { AccountInfo } from './types'; -import { httpGet, httpGetWithIgnores, httpPost } from '../configuration/utils/request-utils'; -import { setShouldShowTenantPopup } from '../../utils/storage-utils'; export function fetchAccountInfo(http: HttpStart): Promise { return httpGet({ http, url: API_ENDPOINT_ACCOUNT_INFO }); @@ -45,19 +40,6 @@ export async function logout(http: HttpStart, logoutUrl?: string): Promise logoutUrl || `${http.basePath.serverBasePath}/app/login?nextUrl=${nextUrl}`; } -export async function samlLogout(http: HttpStart): Promise { - // This will ensure tenancy is picked up from local storage in the next login. - setShouldShowTenantPopup(null); - window.location.href = `${http.basePath.serverBasePath}${SAML_AUTH_LOGOUT}`; -} - -export async function openidLogout(http: HttpStart): Promise { - // This will ensure tenancy is picked up from local storage in the next login. - setShouldShowTenantPopup(null); - sessionStorage.clear(); - window.location.href = `${http.basePath.serverBasePath}${OPENID_AUTH_LOGOUT}`; -} - export async function externalLogout(http: HttpStart, logoutEndpoint: string): Promise { // This will ensure tenancy is picked up from local storage in the next login. setShouldShowTenantPopup(null); diff --git a/public/apps/configuration/constants.tsx b/public/apps/configuration/constants.tsx index 9df5f8285..93b36aa96 100644 --- a/public/apps/configuration/constants.tsx +++ b/public/apps/configuration/constants.tsx @@ -145,24 +145,64 @@ export const CLUSTER_PERMISSIONS: string[] = [ 'cluster:admin/opensearch/ql/async_query/result', 'cluster:admin/opensearch/ql/async_query/delete', 'cluster:admin/opensearch/ppl', + 'cluster:admin/opensearch/ml/agents/delete', + 'cluster:admin/opensearch/ml/agents/get', + 'cluster:admin/opensearch/ml/agents/register', + 'cluster:admin/opensearch/ml/agents/search', + 'cluster:admin/opensearch/ml/config/get', + 'cluster:admin/opensearch/ml/create_connector', + 'cluster:admin/opensearch/ml/connectors/get', + 'cluster:admin/opensearch/ml/connectors/search', + 'cluster:admin/opensearch/ml/connectors/update', + 'cluster:admin/opensearch/ml/controllers/create', + 'cluster:admin/opensearch/ml/controllers/delete', + 'cluster:admin/opensearch/ml/controllers/deploy', + 'cluster:admin/opensearch/ml/controllers/get', + 'cluster:admin/opensearch/ml/controllers/undeploy', + 'cluster:admin/opensearch/ml/controllers/update', 'cluster:admin/opensearch/ml/create_model_meta', 'cluster:admin/opensearch/ml/execute', - 'cluster:admin/opensearch/ml/load_model', - 'cluster:admin/opensearch/ml/load_model_on_nodes', + 'cluster:admin/opensearch/ml/deploy_model', + 'cluster:admin/opensearch/ml/deploy_model_on_nodes', + 'cluster:admin/opensearch/ml/memory/conversation/get', + 'cluster:admin/opensearch/ml/memory/conversation/interaction/search', + 'cluster:admin/opensearch/ml/memory/conversation/delete', + 'cluster:admin/opensearch/ml/memory/conversation/list', + 'cluster:admin/opensearch/ml/memory/conversation/search', + 'cluster:admin/opensearch/ml/memory/conversation/create', + 'cluster:admin/opensearch/ml/memory/conversation/update', + 'cluster:admin/opensearch/ml/memory/interaction/create', + 'cluster:admin/opensearch/ml/memory/interaction/update', + 'cluster:admin/opensearch/ml/memory/interaction/get', + 'cluster:admin/opensearch/ml/memory/interaction/list', + 'cluster:admin/opensearch/ml/memory/trace/get', + 'cluster:admin/opensearch/ml/model_groups/delete', + 'cluster:admin/opensearch/ml/model_groups/get', + 'cluster:admin/opensearch/ml/model_groups/search', + 'cluster:admin/opensearch/ml/register_model_group', + 'cluster:admin/opensearch/ml/update_model_group', 'cluster:admin/opensearch/ml/models/delete', 'cluster:admin/opensearch/ml/models/get', 'cluster:admin/opensearch/ml/models/search', + 'cluster:admin/opensearch/ml/models/update', + 'cluster:admin/opensearch/ml/models/update_cache', 'cluster:admin/opensearch/ml/predict', 'cluster:admin/opensearch/ml/profile/nodes', + 'cluster:admin/opensearch/ml/register_model', + 'cluster:admin/opensearch/ml/register_model_meta', 'cluster:admin/opensearch/ml/stats/nodes', 'cluster:admin/opensearch/ml/tasks/delete', 'cluster:admin/opensearch/ml/tasks/get', 'cluster:admin/opensearch/ml/tasks/search', + 'cluster:admin/opensearch/ml/tools/get', + 'cluster:admin/opensearch/ml/tools/list', 'cluster:admin/opensearch/ml/train', 'cluster:admin/opensearch/ml/trainAndPredict', - 'cluster:admin/opensearch/ml/unload_model', + 'cluster:admin/opensearch/ml/undeploy_model', + 'cluster:admin/opensearch/ml/undeploy_models', 'cluster:admin/opensearch/ml/upload_model', 'cluster:admin/opensearch/ml/upload_model_chunk', + 'cluster:admin/opensearch/mlinternal/forward', 'cluster:admin/opensearch/observability/create', 'cluster:admin/opensearch/observability/delete', 'cluster:admin/opensearch/observability/get', diff --git a/public/apps/configuration/panels/get-started.tsx b/public/apps/configuration/panels/get-started.tsx index d55c8aefa..dd638f5ce 100644 --- a/public/apps/configuration/panels/get-started.tsx +++ b/public/apps/configuration/panels/get-started.tsx @@ -26,9 +26,8 @@ import { EuiTitle, EuiGlobalToastList, } from '@elastic/eui'; -import React, { useContext, useState } from 'react'; +import React, { useContext } from 'react'; import { FormattedMessage } from '@osd/i18n/react'; -import { DataSourceOption } from '../../../../../../src/plugins/data_source_management/public/components/data_source_selector/data_source_selector'; import { AppDependencies } from '../../types'; import { buildHashUrl } from '../utils/url-builder'; import { Action } from '../types'; diff --git a/public/apps/configuration/panels/internal-user-edit/test/backend-role-panel.test.tsx b/public/apps/configuration/panels/internal-user-edit/test/backend-role-panel.test.tsx index 268ca7759..d24f81d6e 100644 --- a/public/apps/configuration/panels/internal-user-edit/test/backend-role-panel.test.tsx +++ b/public/apps/configuration/panels/internal-user-edit/test/backend-role-panel.test.tsx @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -import { EuiFieldText, EuiFlexGroup, EuiFormRow } from '@elastic/eui'; -import { mount, shallow } from 'enzyme'; +import { EuiFieldText, EuiFlexGroup } from '@elastic/eui'; +import { shallow } from 'enzyme'; import React from 'react'; import { appendElementToArray, 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 7a082b237..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 @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { EuiButton, EuiFieldText } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import { shallow } from 'enzyme'; import React from 'react'; import { updateRole } from '../../../utils/role-detail-utils'; diff --git a/public/apps/configuration/panels/role-view/role-view.tsx b/public/apps/configuration/panels/role-view/role-view.tsx index b6761e5cd..9791f90bf 100644 --- a/public/apps/configuration/panels/role-view/role-view.tsx +++ b/public/apps/configuration/panels/role-view/role-view.tsx @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useContext } from 'react'; import { EuiButton, diff --git a/public/apps/configuration/panels/role-view/tenants-panel.tsx b/public/apps/configuration/panels/role-view/tenants-panel.tsx index 45d19bb2b..d0cde8cd9 100644 --- a/public/apps/configuration/panels/role-view/tenants-panel.tsx +++ b/public/apps/configuration/panels/role-view/tenants-panel.tsx @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { EuiInMemoryTable, EuiLink, diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx index c775f7589..2ebf47662 100644 --- a/public/apps/configuration/panels/service-account-list.tsx +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -29,7 +29,7 @@ import { EuiTitle, Query, } from '@elastic/eui'; -import { Dictionary, difference, isEmpty, map } from 'lodash'; +import { Dictionary, isEmpty, map } from 'lodash'; import React, { useState } from 'react'; import { getAuthInfo } from '../../../utils/auth-info-utils'; import { AppDependencies } from '../../types'; diff --git a/public/apps/configuration/panels/tenant-list/configure_tab1.tsx b/public/apps/configuration/panels/tenant-list/configure_tab1.tsx index dd8686664..dafd153d6 100644 --- a/public/apps/configuration/panels/tenant-list/configure_tab1.tsx +++ b/public/apps/configuration/panels/tenant-list/configure_tab1.tsx @@ -328,7 +328,7 @@ export function ConfigureTab1(props: AppDependencies) { -

Multi-tenancy

+

Dashboards multi-tenancy

@@ -360,7 +360,7 @@ export function ConfigureTab1(props: AppDependencies) { -

Tenants

+

Dashboards tenants

{' '} - Global tenant is shared amaong all Dashboards users and cannot be disabled.{' '} + Global tenant is shared among all Dashboards users and cannot be disabled.{' '} } className="described-form-group2" @@ -407,7 +407,7 @@ export function ConfigureTab1(props: AppDependencies) { -

Default tenant

+

Dashboards default tenant

- Tenants + Dashboards tenants {' '} ({Query.execute(query || '', tenantData).length}) diff --git a/public/apps/configuration/panels/tenant-list/tenant-list.tsx b/public/apps/configuration/panels/tenant-list/tenant-list.tsx index 3fd5b89da..0b97c6ae1 100644 --- a/public/apps/configuration/panels/tenant-list/tenant-list.tsx +++ b/public/apps/configuration/panels/tenant-list/tenant-list.tsx @@ -23,16 +23,15 @@ import { EuiButton, } from '@elastic/eui'; import { Route } from 'react-router-dom'; -import React, { useState, useMemo, useCallback, useContext } from 'react'; +import React, { useState, useMemo } from 'react'; import { ManageTab } from './manage_tab'; import { ConfigureTab1 } from './configure_tab1'; import { AppDependencies } from '../../../types'; import { ExternalLink } from '../../utils/display-utils'; -import { displayBoolean } from '../../utils/display-utils'; import { DocLinks } from '../../constants'; import { getDashboardsInfo } from '../../../../utils/dashboards-info-utils'; import { TenantInstructionView } from './tenant-instruction-view'; -import { DataSourceContext, LocalCluster } from '../../app-router'; +import { LocalCluster } from '../../app-router'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; interface TenantListProps extends AppDependencies { @@ -144,7 +143,7 @@ export function TenantList(props: TenantListProps) { /> -

Multi-tenancy

+

Dashboards multi-tenancy

diff --git a/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap b/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap index 64c1e1b64..c817212aa 100644 --- a/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap +++ b/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Tenant list Action menu click Duplicate click 1`] = ` size="s" >

- Tenants + Dashboards tenants @@ -198,7 +198,7 @@ exports[`Tenant list Action menu click Edit click 1`] = ` size="s" >

- Tenants + Dashboards tenants diff --git a/public/apps/configuration/panels/test/get-started.test.tsx b/public/apps/configuration/panels/test/get-started.test.tsx index 09e64578d..00e73a542 100644 --- a/public/apps/configuration/panels/test/get-started.test.tsx +++ b/public/apps/configuration/panels/test/get-started.test.tsx @@ -19,7 +19,7 @@ import { EuiSteps } from '@elastic/eui'; import { Action } from '../../types'; import { ResourceType } from '../../../../../common'; import { buildHashUrl } from '../../utils/url-builder'; -import { GetStarted, getClusterInfoIfEnabled } from '../get-started'; +import { GetStarted } from '../get-started'; import * as ToastUtils from '../../utils/toast-utils'; // Import all functions from toast-utils import * as RequestUtils from '../../utils/request-utils'; // Import all functions from request-utils diff --git a/public/apps/configuration/test/app-router.test.tsx b/public/apps/configuration/test/app-router.test.tsx index 990c448a3..f93c1014d 100644 --- a/public/apps/configuration/test/app-router.test.tsx +++ b/public/apps/configuration/test/app-router.test.tsx @@ -15,7 +15,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SecurityPluginTopNavMenu } from '../top-nav-menu'; import { AppRouter } from '../app-router'; describe('SecurityPluginTopNavMenu', () => { diff --git a/public/apps/configuration/top-nav-menu.tsx b/public/apps/configuration/top-nav-menu.tsx index 0de0efca8..669a6d805 100644 --- a/public/apps/configuration/top-nav-menu.tsx +++ b/public/apps/configuration/top-nav-menu.tsx @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import React, { useMemo } from 'react'; +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'; diff --git a/public/apps/configuration/utils/tenancy-config_util.tsx b/public/apps/configuration/utils/tenancy-config_util.tsx index 70ab84c63..adff9d165 100644 --- a/public/apps/configuration/utils/tenancy-config_util.tsx +++ b/public/apps/configuration/utils/tenancy-config_util.tsx @@ -15,7 +15,7 @@ import { HttpStart } from 'opensearch-dashboards/public'; import { API_ENDPOINT_TENANCY_CONFIGS } from '../constants'; -import { httpGet, httpPut, httpPost } from './request-utils'; +import { httpGet, httpPost } from './request-utils'; import { TenancyConfigSettings } from '../panels/tenancy-config/types'; export async function updateTenancyConfig(http: HttpStart, updateObject: TenancyConfigSettings) { diff --git a/public/apps/configuration/utils/tenant-utils.tsx b/public/apps/configuration/utils/tenant-utils.tsx index 329acbd44..c467c809e 100644 --- a/public/apps/configuration/utils/tenant-utils.tsx +++ b/public/apps/configuration/utils/tenant-utils.tsx @@ -39,7 +39,6 @@ import { import { httpDelete, httpGet, httpPost, httpPut } from './request-utils'; import { getResourceUrl } from './resource-utils'; import { - API_ENDPOINT_DASHBOARDSINFO, DEFAULT_TENANT, GLOBAL_TENANT_RENDERING_TEXT, GLOBAL_TENANT_SYMBOL, @@ -47,7 +46,6 @@ import { isGlobalTenant, isRenderingPrivateTenant, PRIVATE_TENANT_RENDERING_TEXT, - SAML_AUTH_LOGIN, } from '../../../../common'; import { TenancyConfigSettings } from '../panels/tenancy-config/types'; diff --git a/public/apps/configuration/utils/test/password-edit-panel.test.tsx b/public/apps/configuration/utils/test/password-edit-panel.test.tsx index 49ab94b65..247a88d0b 100644 --- a/public/apps/configuration/utils/test/password-edit-panel.test.tsx +++ b/public/apps/configuration/utils/test/password-edit-panel.test.tsx @@ -16,7 +16,6 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; import { PasswordEditPanel } from '../password-edit-panel'; -import { getDashboardsInfo } from '../../../../utils/dashboards-info-utils'; const mockDashboardsInfo = { multitenancy_enabled: true, diff --git a/public/apps/login/login-app.tsx b/public/apps/login/login-app.tsx index 340b45eec..33cb1ebe2 100644 --- a/public/apps/login/login-app.tsx +++ b/public/apps/login/login-app.tsx @@ -15,7 +15,7 @@ import './_index.scss'; // @ts-ignore : Component not used -import React, { Component } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { LoginPage } from './login-page'; diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index 70d894781..a22a36dc7 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -35,6 +35,7 @@ import { OPENID_AUTH_LOGIN_WITH_FRAGMENT, SAML_AUTH_LOGIN_WITH_FRAGMENT, } from '../../../common'; +import { getSavedTenant } from '../../utils/storage-utils'; interface LoginPageDeps { http: CoreStart['http']; @@ -49,8 +50,7 @@ interface LoginButtonConfig { buttonstyle: string; } -function redirect(serverBasePath: string) { - // navigate to nextUrl +export function getNextPath(serverBasePath: string) { const urlParams = new URLSearchParams(window.location.search); let nextUrl = urlParams.get('nextUrl'); if (!nextUrl || nextUrl.toLowerCase().includes('//')) { @@ -58,7 +58,26 @@ function redirect(serverBasePath: string) { // redirect to '/'. nextUrl = serverBasePath + '/'; } - window.location.href = nextUrl + window.location.hash; + const savedTenant = getSavedTenant(); + const url = new URL( + window.location.protocol + '//' + window.location.host + nextUrl + window.location.hash + ); + if ( + !!savedTenant && + !( + url.searchParams.has('security_tenant') || + url.searchParams.has('securitytenant') || + url.searchParams.has('securityTenant_') + ) + ) { + url.searchParams.append('security_tenant', savedTenant); + } + return url.pathname + url.search + url.hash; +} + +function redirect(serverBasePath: string) { + // navigate to nextUrl + window.location.href = getNextPath(serverBasePath); } export function extractNextUrlFromWindowLocation(): string { diff --git a/public/apps/login/test/login-page.test.tsx b/public/apps/login/test/login-page.test.tsx index f21a39e5a..8d1c76358 100644 --- a/public/apps/login/test/login-page.test.tsx +++ b/public/apps/login/test/login-page.test.tsx @@ -16,11 +16,12 @@ import { shallow } from 'enzyme'; import React from 'react'; import { ClientConfigType } from '../../../types'; -import { LoginPage, extractNextUrlFromWindowLocation } from '../login-page'; +import { LoginPage, extractNextUrlFromWindowLocation, getNextPath } from '../login-page'; import { validateCurrentPassword } from '../../../utils/login-utils'; import { API_AUTH_LOGOUT } from '../../../../common'; import { chromeServiceMock } from '../../../../../../src/core/public/mocks'; import { AuthType } from '../../../../common'; +import { setSavedTenant } from '../../../utils/storage-utils'; jest.mock('../../../utils/login-utils', () => ({ validateCurrentPassword: jest.fn(), @@ -85,6 +86,45 @@ describe('test extractNextUrlFromWindowLocation', () => { }); }); +describe('test redirect', () => { + test('extract redirect excludes security_tenant when no tenant in local storage', () => { + // Trick to mock window.location + const originalLocation = window.location; + delete window.location; + window.location = new URL('http://localhost:5601/app/login?nextUrl=%2Fapp%2Fdashboards') as any; + setSavedTenant(null); + const nextPath = getNextPath(''); + expect(nextPath).toEqual('/app/dashboards'); + window.location = originalLocation; + }); + + test('extract redirect includes security_tenant when tenant in local storage', () => { + const originalLocation = window.location; + delete window.location; + window.location = new URL('http://localhost:5601/app/login?nextUrl=%2Fapp%2Fdashboards'); + setSavedTenant('custom'); + const nextPath = getNextPath(''); + expect(nextPath).toEqual('/app/dashboards?security_tenant=custom'); + setSavedTenant(null); + window.location = originalLocation; + }); + + test('extract redirect includes security_tenant when tenant in local storage, existing url params and hash', () => { + const originalLocation = window.location; + delete window.location; + window.location = new URL( + "http://localhost:5601/app/login?nextUrl=%2Fapp%2Fdashboards?param1=value1#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?_g=(filters:!(),refreshInterval:(pause:!f,value:900000),time:(from:now-24h,to:now))&_a=(description:'Analyze%20mock%20flight%20data%20for%20OpenSearch-Air,%20Logstash%20Airways,%20OpenSearch%20Dashboards%20Airlines%20and%20BeatsWest',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),query:(language:kuery,query:''),timeRestore:!t,title:'%5BFlights%5D%20Global%20Flight%20Dashboard',viewMode:view)" + ); + setSavedTenant('custom'); + const nextPath = getNextPath(''); + expect(nextPath).toEqual( + "/app/dashboards?param1=value1&security_tenant=custom#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?_g=(filters:!(),refreshInterval:(pause:!f,value:900000),time:(from:now-24h,to:now))&_a=(description:'Analyze%20mock%20flight%20data%20for%20OpenSearch-Air,%20Logstash%20Airways,%20OpenSearch%20Dashboards%20Airlines%20and%20BeatsWest',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),query:(language:kuery,query:''),timeRestore:!t,title:'%5BFlights%5D%20Global%20Flight%20Dashboard',viewMode:view)" + ); + setSavedTenant(null); + window.location = originalLocation; + }); +}); + describe('Login page', () => { let chrome: ReturnType; const mockHttpStart = { diff --git a/public/utils/datasource-utils.ts b/public/utils/datasource-utils.ts index b73cfe770..59db29aac 100644 --- a/public/utils/datasource-utils.ts +++ b/public/utils/datasource-utils.ts @@ -14,7 +14,6 @@ */ import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; -import { LocalCluster } from '../apps/configuration/app-router'; export function createDataSourceQuery(dataSourceId: string) { return { dataSourceId }; diff --git a/public/utils/logout-utils.tsx b/public/utils/logout-utils.tsx index 90f5a75a0..d195cc79f 100644 --- a/public/utils/logout-utils.tsx +++ b/public/utils/logout-utils.tsx @@ -13,19 +13,21 @@ * permissions and limitations under the License. */ -import { setShouldShowTenantPopup } from './storage-utils'; import { HttpInterceptorResponseError, HttpStart, IHttpInterceptController, } from '../../../../src/core/public'; -import { CUSTOM_ERROR_PAGE_URI, LOGIN_PAGE_URI, API_ENDPOINT_AUTHTYPE } from '../../common'; +import { API_ENDPOINT_AUTHTYPE, CUSTOM_ERROR_PAGE_URI, LOGIN_PAGE_URI } from '../../common'; import { httpGet } from '../apps/configuration/utils/request-utils'; +import { setShouldShowTenantPopup } from './storage-utils'; export function interceptError(logoutUrl: string, thisWindow: Window): any { return (httpErrorResponse: HttpInterceptorResponseError, _: IHttpInterceptController) => { if (httpErrorResponse.response?.status === 401) { setShouldShowTenantPopup(null); + // Clear everything in the sessionStorage since they can contain sensitive information + sessionStorage.clear(); if ( !( thisWindow.location.pathname.toLowerCase().includes(LOGIN_PAGE_URI) || diff --git a/public/utils/test/datasource-utils.test.ts b/public/utils/test/datasource-utils.test.ts index 930a38cef..9db4af61b 100644 --- a/public/utils/test/datasource-utils.test.ts +++ b/public/utils/test/datasource-utils.test.ts @@ -17,8 +17,6 @@ import { createDataSourceQuery, getClusterInfoIfEnabled, getDataSourceFromUrl, - getDataSourceIdFromUrl, - setDataSourceIdInUrl, setDataSourceInUrl, } from '../datasource-utils'; diff --git a/release-notes/opensearch-security-dashboards-plugin.release-notes-2.13.0.0.md b/release-notes/opensearch-security-dashboards-plugin.release-notes-2.13.0.0.md new file mode 100644 index 000000000..4089ab028 --- /dev/null +++ b/release-notes/opensearch-security-dashboards-plugin.release-notes-2.13.0.0.md @@ -0,0 +1,12 @@ +## Version 2.13.0.0 + +Compatible with OpenSearch-Dashboards 2.13.0 + +### Enhancements +* Clear the contents of opensearch_dashboards prior to putting settings ([#1781](https://github.com/opensearch-project/security-dashboards-plugin/pull/1781)) +* Add loose flag to OSD bootstrap ([#1789](https://github.com/opensearch-project/security-dashboards-plugin/pull/1789)) +* Hide tenant when disabled in the account nav button popover ([#1792](https://github.com/opensearch-project/security-dashboards-plugin/pull/1792)) +* Use start-opensearch and setup-opensearch-dashboards actions ([#1808](https://github.com/opensearch-project/security-dashboards-plugin/pull/1808)) +* Fix cookie expiry issues from IDP/JWT auth methods, disables keepalive for JWT/IDP ([#1806](https://github.com/opensearch-project/security-dashboards-plugin/pull/1806)) +* Copy tenant with Short URL ([#1812](https://github.com/opensearch-project/security-dashboards-plugin/pull/1812)) +* Add toast handling for purge cache action ([#1827](https://github.com/opensearch-project/security-dashboards-plugin/pull/1827)) diff --git a/server/auth/types/jwt/jwt_auth.ts b/server/auth/types/jwt/jwt_auth.ts index da1c13dc6..cba89b79b 100644 --- a/server/auth/types/jwt/jwt_auth.ts +++ b/server/auth/types/jwt/jwt_auth.ts @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -import { ParsedUrlQuery } from 'querystring'; import { SessionStorageFactory, IRouter, diff --git a/server/auth/types/jwt/jwt_helper.test.ts b/server/auth/types/jwt/jwt_helper.test.ts index f82621a62..ae9012718 100644 --- a/server/auth/types/jwt/jwt_helper.test.ts +++ b/server/auth/types/jwt/jwt_helper.test.ts @@ -14,7 +14,7 @@ */ import { getAuthenticationHandler } from '../../auth_handler_factory'; -import { JWT_DEFAULT_EXTRA_STORAGE_OPTIONS, JwtAuthentication } from './jwt_auth'; +import { JWT_DEFAULT_EXTRA_STORAGE_OPTIONS } from './jwt_auth'; import { CoreSetup, ILegacyClusterClient, @@ -215,7 +215,7 @@ describe('test jwt auth library', () => { }); }); // re-import JWTAuth to change cookie splitter to a no-op -/* eslint-disable no-shadow, @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-var-requires */ describe('JWT Expiry Tests', () => { const setExtraAuthStorageMock = jest.fn(); jest.resetModules(); @@ -388,5 +388,5 @@ describe('JWT Expiry Tests', () => { jwtAuth.buildAuthHeaderFromCookie.mockRestore(); }); - /* eslint-enable no-shadow, @typescript-eslint/no-var-requires */ + /* eslint-enable @typescript-eslint/no-var-requires */ }); diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index 2bcc5e1ba..b190d9d03 100644 --- a/server/auth/types/multiple/multi_auth.ts +++ b/server/auth/types/multiple/multi_auth.ts @@ -24,7 +24,7 @@ import { } from 'opensearch-dashboards/server'; import { OpenSearchDashboardsResponse } from '../../../../../../src/core/server/http/router'; import { SecurityPluginConfigType } from '../../..'; -import { AuthenticationType, IAuthenticationType } from '../authentication_type'; +import { AuthenticationType } from '../authentication_type'; import { ANONYMOUS_AUTH_LOGIN, AuthType, LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { MultiAuthRoutes } from './routes'; diff --git a/server/auth/types/openid/openid_auth.ts b/server/auth/types/openid/openid_auth.ts index 61088dfd3..b67e174c8 100644 --- a/server/auth/types/openid/openid_auth.ts +++ b/server/auth/types/openid/openid_auth.ts @@ -39,10 +39,9 @@ import { import { OpenIdAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; import { callTokenEndpoint } from './helper'; -import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { getObjectProperties } from '../../../utils/object_properties_defined'; import { getExpirationDate } from './helper'; -import { AuthType, OPENID_AUTH_LOGIN } from '../../../../common'; +import { AuthType } from '../../../../common'; import { ExtraAuthStorageOptions, getExtraAuthStorageValue, diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index a8dc2e2b1..c23e26b1f 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -43,7 +43,6 @@ import { AUTH_GRANT_TYPE, AUTH_RESPONSE_TYPE, OPENID_AUTH_LOGOUT, - LOGIN_PAGE_URI, } from '../../../../common'; import { diff --git a/server/auth/types/proxy/proxy_auth.ts b/server/auth/types/proxy/proxy_auth.ts index b3a97d5ed..312c6dedf 100644 --- a/server/auth/types/proxy/proxy_auth.ts +++ b/server/auth/types/proxy/proxy_auth.ts @@ -30,7 +30,6 @@ import { SecurityPluginConfigType } from '../../..'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { ProxyAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; -import { isValidTenant } from '../../../multitenancy/tenant_resolver'; export class ProxyAuthentication extends AuthenticationType { private static readonly XFF: string = 'x-forwarded-for'; diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 7897444e4..71a65d205 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -15,7 +15,6 @@ import { ILegacyClusterClient, OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { User } from '../auth/user'; -import { getAuthInfo } from '../../public/utils/auth-info-utils'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; export class SecurityClient { diff --git a/test/constant.ts b/test/constant.ts index 713ea05de..d4ab9e3ae 100644 --- a/test/constant.ts +++ b/test/constant.ts @@ -22,7 +22,7 @@ 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 = 'admin'; +export const ADMIN_PASSWORD: string = process.env.ADMIN_PASSWORD || 'admin'; const ADMIN_USER_PASS: string = `${ADMIN_USER}:${ADMIN_PASSWORD}`; export const ADMIN_CREDENTIALS: string = `Basic ${Buffer.from(ADMIN_USER_PASS).toString('base64')}`; export const AUTHORIZATION_HEADER_NAME: string = 'Authorization'; 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 7ade8fb22..65318fbd1 100644 --- a/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js +++ b/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js @@ -39,17 +39,16 @@ 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('.euiToast') - .contains('Your browser does not meet the security requirements for OpenSearch Dashboards') - .then((toast) => { - if (toast.length > 0) { - cy.get('[data-test-subj="toastCloseButton"]').first().click(); - } - }); - } - }); + cy.get('.euiToastHeader__title') + .should( + 'contain', + 'Your browser does not meet the security requirements for OpenSearch Dashboards' + ) + .then((toast) => { + if (toast.length > 0) { + cy.get('[data-test-subj="toastCloseButton"]').first().click(); + } + }); }; const deleteAllDataSources = () => { diff --git a/test/jest_integration/security_entity_api.test.ts b/test/jest_integration/security_entity_api.test.ts index 38b101fcc..6e03da978 100644 --- a/test/jest_integration/security_entity_api.test.ts +++ b/test/jest_integration/security_entity_api.test.ts @@ -34,11 +34,7 @@ import { getEntityAsAdmin, getEntityAsAdminWithDataSource, } from '../helper/entity_operation'; -import { - testAuditLogDisabledSettings, - testAuditLogEnabledSettings, - testAuditLogSettings, -} from './constants'; +import { testAuditLogDisabledSettings, testAuditLogEnabledSettings } from './constants'; describe('start OpenSearch Dashboards server', () => { let root: Root; diff --git a/yarn.lock b/yarn.lock index 2c119c492..59cbbd085 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1729,6 +1729,18 @@ eslint-plugin-cypress@^2.8.1: dependencies: globals "^13.20.0" +eslint-plugin-unused-imports@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.1.0.tgz#db015b569d3774e17a482388c95c17bd303bc602" + integrity sha512-9l1YFCzXKkw1qtAru1RWUtG2EVDZY0a0eChKXcL+EZ5jitG7qxdctu4RnvhOJHv4xfmUf7h+JJPINlVpGhZMrw== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -2443,6 +2455,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +husky@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"