From 1696500552d6225806c3038d352d3afe539060a0 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 31 May 2023 13:33:31 -0400 Subject: [PATCH 01/31] Skip flaky SAML test as it awaits a fix (#1453) Signed-off-by: Craig Perkins Signed-off-by: Sam --- test/jest_integration/saml_auth.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jest_integration/saml_auth.test.ts b/test/jest_integration/saml_auth.test.ts index 86d2e3497..7123baf15 100644 --- a/test/jest_integration/saml_auth.test.ts +++ b/test/jest_integration/saml_auth.test.ts @@ -284,7 +284,7 @@ describe('start OpenSearch Dashboards server', () => { await driver.quit(); }); - it('Tenancy persisted after Logout in SAML', async () => { + it.skip('Tenancy persisted after Logout in SAML', async () => { const driver = getDriver(browser, options).build(); await driver.get('http://localhost:5601/app/opensearch_dashboards_overview#/'); From 9afe45791ec29fc1ff87879b9f3a34439c530de8 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 3 Jul 2023 19:01:06 +0100 Subject: [PATCH 02/31] #2704 Adding Service Accounts page and link on side bar Signed-off-by: Sam --- public/apps/configuration/app-router.tsx | 13 ++ public/apps/configuration/constants.tsx | 1 + .../panels/service-account-list.tsx | 220 ++++++++++++++++++ .../apps/configuration/panels/user-list.tsx | 2 +- public/apps/configuration/types.ts | 1 + .../utils/internal-user-list-utils.tsx | 21 +- ...pensearch_security_configuration_plugin.ts | 12 + server/routes/index.ts | 13 +- 8 files changed, 271 insertions(+), 12 deletions(-) create mode 100644 public/apps/configuration/panels/service-account-list.tsx diff --git a/public/apps/configuration/app-router.tsx b/public/apps/configuration/app-router.tsx index 6cb97ec0e..8b4ce1f93 100644 --- a/public/apps/configuration/app-router.tsx +++ b/public/apps/configuration/app-router.tsx @@ -35,6 +35,7 @@ import { RoleEditMappedUser } from './panels/role-mapping/role-edit-mapped-user' import { RoleView } from './panels/role-view/role-view'; import { TenantList } from './panels/tenant-list/tenant-list'; import { UserList } from './panels/user-list'; +import { ServiceAccountList } from "./panels/service-account-list"; import { Action, ResourceType, RouteItem, SubAction } from './types'; import { buildHashUrl, buildUrl } from './utils/url-builder'; import { CrossPageToast } from './cross-page-toast'; @@ -54,6 +55,10 @@ const ROUTE_MAP: { [key: string]: RouteItem } = { name: 'Internal users', href: buildUrl(ResourceType.users), }, + [ResourceType.serviceAccounts]: { + name: 'Service Accounts', + href: buildUrl(ResourceType.serviceAccounts), + }, [ResourceType.permissions]: { name: 'Permissions', href: buildUrl(ResourceType.permissions), @@ -85,6 +90,7 @@ const ROUTE_LIST = [ ROUTE_MAP[ResourceType.auth], ROUTE_MAP[ResourceType.roles], ROUTE_MAP[ResourceType.users], + ROUTE_MAP[ResourceType.serviceAccounts], ROUTE_MAP[ResourceType.permissions], ROUTE_MAP[ResourceType.tenants], ROUTE_MAP[ResourceType.auditLogging], @@ -209,6 +215,13 @@ export function AppRouter(props: AppDependencies) { return ; }} /> + { + setGlobalBreadcrumbs(ResourceType.serviceAccounts); + return ; + }} + /> { diff --git a/public/apps/configuration/constants.tsx b/public/apps/configuration/constants.tsx index 1a5435d8c..798444441 100644 --- a/public/apps/configuration/constants.tsx +++ b/public/apps/configuration/constants.tsx @@ -25,6 +25,7 @@ export const API_ENDPOINT_MULTITENANCY = API_PREFIX + '/multitenancy/tenant'; export const API_ENDPOINT_TENANCY_CONFIGS = API_ENDPOINT + '/tenancy/config'; export const API_ENDPOINT_SECURITYCONFIG = API_ENDPOINT + '/securityconfig'; export const API_ENDPOINT_INTERNALUSERS = API_ENDPOINT + '/internalusers'; +export const API_ENDPOINT_SERVICEACCOUNTS = API_ENDPOINT + '/serviceaccounts'; export const API_ENDPOINT_AUDITLOGGING = API_ENDPOINT + '/audit'; export const API_ENDPOINT_AUDITLOGGING_UPDATE = API_ENDPOINT_AUDITLOGGING + '/config'; export const API_ENDPOINT_PERMISSIONS_INFO = API_PREFIX + '/restapiinfo'; diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx new file mode 100644 index 000000000..86e8afad8 --- /dev/null +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -0,0 +1,220 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { + EuiBadge, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiLink, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiText, + EuiTitle, + Query, +} from '@elastic/eui'; +import { Dictionary, difference, isEmpty, map } from 'lodash'; +import React, { useState } from 'react'; +import { getAuthInfo } from '../../../utils/auth-info-utils'; +import { AppDependencies } from '../../types'; +import { API_ENDPOINT_SERVICEACCOUNTS, DocLinks } from '../constants'; +import { Action, ResourceType } from '../types'; +import { EMPTY_FIELD_VALUE } from '../ui-constants'; +import { useContextMenuState } from '../utils/context-menu'; +import { ExternalLink, tableItemsUIProps, truncatedListView } from '../utils/display-utils'; +import { + getUserList, + InternalUsersListing, +} from '../utils/internal-user-list-utils'; +import { showTableStatusMessage } from '../utils/loading-spinner-utils'; +import { buildHashUrl } from '../utils/url-builder'; + +export function dictView(items: Dictionary) { + if (isEmpty(items)) { + return EMPTY_FIELD_VALUE; + } + return ( + + {map(items, (v, k) => ( + + {k}: {`"${v}"`} + + ))} + + ); +} + +export function getColumns(currentUsername: string) { + return [ + { + field: 'username', + name: 'Username', + render: (username: string) => ( + <> + {username} + {username === currentUsername && ( + <> +   + Current + + )} + + ), + sortable: true, + }, + { + field: 'backend_roles', + name: 'Backend roles', + render: truncatedListView(tableItemsUIProps), + }, + { + field: 'attributes', + name: 'Attributes', + render: dictView, + truncateText: true, + }, + ]; +} + +export function ServiceAccountList(props: AppDependencies) { + const [userData, setUserData] = React.useState([]); + const [errorFlag, setErrorFlag] = React.useState(false); + const [selection, setSelection] = React.useState([]); + const [currentUsername, setCurrentUsername] = useState(''); + const [loading, setLoading] = useState(false); + const [query, setQuery] = useState(null); + + React.useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const userDataPromise = getUserList(props.coreStart.http, ResourceType.serviceAccounts); + setCurrentUsername((await getAuthInfo(props.coreStart.http)).user_name); + setUserData(await userDataPromise); + } catch (e) { + console.log(e); + setErrorFlag(true); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [props.coreStart.http]); + + const actionsMenuItems = [ + { + window.location.href = buildHashUrl(ResourceType.users, Action.edit, selection[0].username); + }} + disabled={selection.length !== 1} + > + Edit + , + { + window.location.href = buildHashUrl( + ResourceType.users, + Action.duplicate, + selection[0].username + ); + }} + disabled={selection.length !== 1} + > + Duplicate + , + + Export JSON + , + ]; + + const [actionsMenu, closeActionsMenu] = useContextMenuState('Actions', {}, actionsMenuItems); + + return ( + <> + + +

Service accounts

+
+
+ + + + +

+ Service accounts + + {' '} + ({Query.execute(query || '', userData).length}) + +

+
+ + Here you list the users authenticated via an external authentication system such as LDAP server or Active + Directory. You can map an user to a role from{' '} + Roles + “Manage mapping” + +
+ + + {actionsMenu} + + +
+ + { + setQuery(arg.query); + return true; + }, + }} + // @ts-ignore + selection={{ onSelectionChange: setSelection }} + sorting + error={errorFlag ? 'Load data failed, please check console log for more detail.' : ''} + message={showTableStatusMessage(loading, userData)} + /> + +
+ + ); +} diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index 8cb627f8b..2ab636497 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -107,7 +107,7 @@ export function UserList(props: AppDependencies) { const fetchData = async () => { try { setLoading(true); - const userDataPromise = getUserList(props.coreStart.http); + const userDataPromise = getUserList(props.coreStart.http, ResourceType.users); setCurrentUsername((await getAuthInfo(props.coreStart.http)).user_name); setUserData(await userDataPromise); } catch (e) { diff --git a/public/apps/configuration/types.ts b/public/apps/configuration/types.ts index 06ef44369..fd64e1095 100644 --- a/public/apps/configuration/types.ts +++ b/public/apps/configuration/types.ts @@ -22,6 +22,7 @@ export type FieldLevelSecurityMethod = 'exclude' | 'include'; export enum ResourceType { roles = 'roles', users = 'users', + serviceAccounts = 'serviceAccounts', permissions = 'permissions', tenants = 'tenants', tenantsManageTab = 'tenantsManageTab', diff --git a/public/apps/configuration/utils/internal-user-list-utils.tsx b/public/apps/configuration/utils/internal-user-list-utils.tsx index 9a893eeb9..880c7a6b6 100644 --- a/public/apps/configuration/utils/internal-user-list-utils.tsx +++ b/public/apps/configuration/utils/internal-user-list-utils.tsx @@ -15,8 +15,8 @@ import { map } from 'lodash'; import { HttpStart } from '../../../../../../src/core/public'; -import { API_ENDPOINT_INTERNALUSERS } from '../constants'; -import { DataObject, InternalUser, ObjectsMessage } from '../types'; +import {API_ENDPOINT_INTERNALUSERS, API_ENDPOINT_SERVICEACCOUNTS} from '../constants'; +import {DataObject, InternalUser, ObjectsMessage, ResourceType} from '../types'; import { httpDelete, httpGet } from './request-utils'; import { getResourceUrl } from './resource-utils'; @@ -38,15 +38,20 @@ export async function requestDeleteUsers(http: HttpStart, users: string[]) { } } -async function getUserListRaw(http: HttpStart): Promise> { - return await httpGet>(http, API_ENDPOINT_INTERNALUSERS); +async function getUserListRaw(http: HttpStart, userType: string ): Promise> { + let ENDPOINT = API_ENDPOINT_INTERNALUSERS; + if (userType === ResourceType.serviceAccounts) { + ENDPOINT = API_ENDPOINT_SERVICEACCOUNTS; + } + + return await httpGet>(http, ENDPOINT); } -export async function getUserList(http: HttpStart): Promise { - const rawData = await getUserListRaw(http); +export async function getUserList(http: HttpStart, userType: string ): Promise { + const rawData = await getUserListRaw(http, userType); return transformUserData(rawData.data); } -export async function fetchUserNameList(http: HttpStart): Promise { - return Object.keys((await getUserListRaw(http)).data); +export async function fetchUserNameList(http: HttpStart, userType: string): Promise { + return Object.keys((await getUserListRaw(http, userType)).data); } diff --git a/server/backend/opensearch_security_configuration_plugin.ts b/server/backend/opensearch_security_configuration_plugin.ts index ff0ccfe21..2333fc94e 100644 --- a/server/backend/opensearch_security_configuration_plugin.ts +++ b/server/backend/opensearch_security_configuration_plugin.ts @@ -59,6 +59,18 @@ export default function (Client: any, config: any, components: any) { }, }); + Client.prototype.opensearch_security.prototype.listUserResource = ca({ + url: { + fmt: '/_plugins/_security/api/internalusers/<%=resourceName%>', + req: { + resourceName: { + type: 'string', + required: true, + }, + }, + }, + }); + /** * Creates a Security resource instance. * diff --git a/server/routes/index.ts b/server/routes/index.ts index 8331c1200..6f02cf5f2 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -21,6 +21,7 @@ import { OpenSearchDashboardsResponseFactory, } from 'opensearch-dashboards/server'; import { API_PREFIX, CONFIGURATION_API_PREFIX, isValidResourceName } from '../../common'; +import { ResourceType } from '../../public/apps/configuration/types'; // TODO: consider to extract entity CRUD operations and put it into a client class export function defineRoutes(router: IRouter) { @@ -248,9 +249,15 @@ export function defineRoutes(router: IRouter) { const client = context.security_plugin.esClient.asScoped(request); let esResp; try { - esResp = await client.callAsCurrentUser('opensearch_security.listResource', { - resourceName: request.params.resourceName, - }); + if(request.params.resourceName == ResourceType.serviceAccounts.toLowerCase() || request.params.resourceName == ResourceType.users.toLowerCase()){ + esResp = await client.callAsCurrentUser('opensearch_security.listUserResource', { + resourceName: request.params.resourceName, + });} + else{ + esResp = await client.callAsCurrentUser('opensearch_security.listResource', { + resourceName: request.params.resourceName, + }); + } return response.ok({ body: { total: Object.keys(esResp).length, From d1bd4f5232f42acf011fd5c380cc789e3d963a7f Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 6 Jul 2023 11:20:10 +0100 Subject: [PATCH 03/31] #2704 Renaming 'internal users' to 'internal accounts' on dashboards frontend front end to Signed-off-by: Sam --- public/apps/configuration/app-router.tsx | 2 +- public/apps/configuration/constants.tsx | 1 + public/apps/configuration/panels/user-list.tsx | 10 +++++----- .../utils/internal-user-list-utils.tsx | 6 +++--- .../opensearch_security_configuration_plugin.ts | 17 ++++++++--------- server/routes/index.ts | 11 +++++++---- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/public/apps/configuration/app-router.tsx b/public/apps/configuration/app-router.tsx index 8b4ce1f93..1c457627b 100644 --- a/public/apps/configuration/app-router.tsx +++ b/public/apps/configuration/app-router.tsx @@ -52,7 +52,7 @@ const ROUTE_MAP: { [key: string]: RouteItem } = { href: buildUrl(ResourceType.roles), }, [ResourceType.users]: { - name: 'Internal users', + name: 'Internal Accounts', href: buildUrl(ResourceType.users), }, [ResourceType.serviceAccounts]: { diff --git a/public/apps/configuration/constants.tsx b/public/apps/configuration/constants.tsx index 798444441..6623f1a53 100644 --- a/public/apps/configuration/constants.tsx +++ b/public/apps/configuration/constants.tsx @@ -25,6 +25,7 @@ export const API_ENDPOINT_MULTITENANCY = API_PREFIX + '/multitenancy/tenant'; export const API_ENDPOINT_TENANCY_CONFIGS = API_ENDPOINT + '/tenancy/config'; export const API_ENDPOINT_SECURITYCONFIG = API_ENDPOINT + '/securityconfig'; export const API_ENDPOINT_INTERNALUSERS = API_ENDPOINT + '/internalusers'; +export const API_ENDPOINT_INTERNALACCOUNTS = API_ENDPOINT + '/internalaccounts'; export const API_ENDPOINT_SERVICEACCOUNTS = API_ENDPOINT + '/serviceaccounts'; export const API_ENDPOINT_AUDITLOGGING = API_ENDPOINT + '/audit'; export const API_ENDPOINT_AUDITLOGGING_UPDATE = API_ENDPOINT_AUDITLOGGING + '/config'; diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index 2ab636497..664d8b9ae 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -195,7 +195,7 @@ export function UserList(props: AppDependencies) { <> -

Internal users

+

Internal accounts

@@ -203,7 +203,7 @@ export function UserList(props: AppDependencies) {

- Internal users + Internal accounts {' '} ({Query.execute(query || '', userData).length}) @@ -211,9 +211,9 @@ export function UserList(props: AppDependencies) {

- The Security plugin includes an internal user database. Use this database in place of, + The Security plugin includes an internal account database. Use this database in place of, or in addition to, an external authentication system such as LDAP server or Active - Directory. You can map an internal user to a role from{' '} + Directory. You can map an internal account to a role from{' '} Roles . First, click into the detail page of the role. Then, under “Mapped users”, click “Manage mapping” @@ -224,7 +224,7 @@ export function UserList(props: AppDependencies) { {actionsMenu} - Create internal user + Create internal account diff --git a/public/apps/configuration/utils/internal-user-list-utils.tsx b/public/apps/configuration/utils/internal-user-list-utils.tsx index 880c7a6b6..ad0b459f0 100644 --- a/public/apps/configuration/utils/internal-user-list-utils.tsx +++ b/public/apps/configuration/utils/internal-user-list-utils.tsx @@ -15,7 +15,7 @@ import { map } from 'lodash'; import { HttpStart } from '../../../../../../src/core/public'; -import {API_ENDPOINT_INTERNALUSERS, API_ENDPOINT_SERVICEACCOUNTS} from '../constants'; +import { API_ENDPOINT_INTERNALACCOUNTS, API_ENDPOINT_INTERNALUSERS, API_ENDPOINT_SERVICEACCOUNTS } from '../constants'; import {DataObject, InternalUser, ObjectsMessage, ResourceType} from '../types'; import { httpDelete, httpGet } from './request-utils'; import { getResourceUrl } from './resource-utils'; @@ -34,12 +34,12 @@ export function transformUserData(rawData: DataObject): InternalUs export async function requestDeleteUsers(http: HttpStart, users: string[]) { for (const user of users) { - await httpDelete(http, getResourceUrl(API_ENDPOINT_INTERNALUSERS, user)); + await httpDelete(http, getResourceUrl( API_ENDPOINT_INTERNALUSERS, user)); } } async function getUserListRaw(http: HttpStart, userType: string ): Promise> { - let ENDPOINT = API_ENDPOINT_INTERNALUSERS; + let ENDPOINT = API_ENDPOINT_INTERNALACCOUNTS; if (userType === ResourceType.serviceAccounts) { ENDPOINT = API_ENDPOINT_SERVICEACCOUNTS; } diff --git a/server/backend/opensearch_security_configuration_plugin.ts b/server/backend/opensearch_security_configuration_plugin.ts index 2333fc94e..199992d46 100644 --- a/server/backend/opensearch_security_configuration_plugin.ts +++ b/server/backend/opensearch_security_configuration_plugin.ts @@ -59,16 +59,15 @@ export default function (Client: any, config: any, components: any) { }, }); - Client.prototype.opensearch_security.prototype.listUserResource = ca({ + Client.prototype.opensearch_security.prototype.listInternalAccounts = ca({ url: { - fmt: '/_plugins/_security/api/internalusers/<%=resourceName%>', - req: { - resourceName: { - type: 'string', - required: true, - }, - }, - }, + fmt: '/_plugins/_security/api/internalusers/internalaccounts' + } + }); + Client.prototype.opensearch_security.prototype.listServiceAccounts = ca({ + url: { + fmt: '/_plugins/_security/api/internalusers/serviceaccounts' + } }); /** diff --git a/server/routes/index.ts b/server/routes/index.ts index 6f02cf5f2..8b6bce2c9 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -249,10 +249,13 @@ export function defineRoutes(router: IRouter) { const client = context.security_plugin.esClient.asScoped(request); let esResp; try { - if(request.params.resourceName == ResourceType.serviceAccounts.toLowerCase() || request.params.resourceName == ResourceType.users.toLowerCase()){ - esResp = await client.callAsCurrentUser('opensearch_security.listUserResource', { - resourceName: request.params.resourceName, - });} + console.log(request.params.resourceName) + if(request.params.resourceName == ResourceType.serviceAccounts.toLowerCase()){ + esResp = await client.callAsCurrentUser('opensearch_security.listServiceAccounts'); + } + else if(request.params.resourceName == 'internalaccounts'){ + esResp = await client.callAsCurrentUser('opensearch_security.listInternalAccounts'); + } else{ esResp = await client.callAsCurrentUser('opensearch_security.listResource', { resourceName: request.params.resourceName, From b5253bfe2b70dfe4e276520ddc1be1b77dca91f0 Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:23:29 -0700 Subject: [PATCH 04/31] Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Sam --- .github/workflows/prerequisite-test.yml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/workflows/prerequisite-test.yml diff --git a/.github/workflows/prerequisite-test.yml b/.github/workflows/prerequisite-test.yml deleted file mode 100644 index afcccb53b..000000000 --- a/.github/workflows/prerequisite-test.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Prerequisite Checks - -on: [push, pull_request] - -jobs: - tests: - name: Run prerequisite checks - runs-on: ubuntu-latest - steps: - - name: Check for the existence of the OpenSearch Security Plugin artifact - env: - opensearch_version: 3.0.0 - opensearch_security_plugin_version: 3.0.0.0 - run: wget -S --spider https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${opensearch_version}/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-${opensearch_security_plugin_version}.zip || (echo "Please make sure security plugin has been bumped to the same version and added to manifest." && exit 1) From a3f2a4b72469da872d39fa691a4ec3adcaaa6017 Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:23:54 -0700 Subject: [PATCH 05/31] Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Sam From 9755c5b69a7d7a2fae8094bdb7675219838be5ab Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:25:03 -0700 Subject: [PATCH 06/31] Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: Sam From c384d6b07a263d0b157fbc503ac3b6bfeb912c16 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 1 Jun 2023 12:18:34 -0400 Subject: [PATCH 07/31] Use version from package.json for integration tests (#1463) * Use version from package.json for integration tests Signed-off-by: Craig Perkins Signed-off-by: Sam --- .github/workflows/cypress-test-tenancy-disabled.yml | 10 ++++++++-- .github/workflows/cypress-test.yml | 10 ++++++++-- .github/workflows/integration-test.yml | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cypress-test-tenancy-disabled.yml b/.github/workflows/cypress-test-tenancy-disabled.yml index c494f1390..23494d1c3 100644 --- a/.github/workflows/cypress-test-tenancy-disabled.yml +++ b/.github/workflows/cypress-test-tenancy-disabled.yml @@ -9,9 +9,7 @@ env: START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --opensearch_security.multitenancy.enable_aggregation_view=true' OPENSEARCH_SNAPSHOT_CMD: 'node ../scripts/opensearch snapshot' SPEC: 'cypress/integration/plugins/security-dashboards-plugin/aggregation_view.js,' - OPENSEARCH_VERSION: 3.0.0 PLUGIN_NAME: opensearch-security - PLUGIN_VERSION: 3.0.0.0 jobs: tests: @@ -31,6 +29,14 @@ jobs: - name: Checkout Branch uses: actions/checkout@v3 + - name: Set env + run: | + opensearch_version=$(node -p "require('./package.json').opensearchDashboards.version") + plugin_version=$(node -p "require('./package.json').version") + echo "OPENSEARCH_VERSION=$opensearch_version" >> $GITHUB_ENV + echo "PLUGIN_VERSION=$plugin_version" >> $GITHUB_ENV + shell: bash + - name: Download security plugin and create setup scripts uses: ./.github/actions/download-plugin with: diff --git a/.github/workflows/cypress-test.yml b/.github/workflows/cypress-test.yml index e61149cf6..c5e5370c1 100644 --- a/.github/workflows/cypress-test.yml +++ b/.github/workflows/cypress-test.yml @@ -9,9 +9,7 @@ env: START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --opensearch_security.multitenancy.enable_aggregation_view=true' OPENSEARCH_SNAPSHOT_CMD: 'node ../scripts/opensearch snapshot' SPEC: 'cypress/integration/plugins/security-dashboards-plugin/aggregation_view.js,' - OPENSEARCH_VERSION: 3.0.0 PLUGIN_NAME: opensearch-security - PLUGIN_VERSION: 3.0.0.0 jobs: tests: @@ -31,6 +29,14 @@ jobs: - name: Checkout Branch uses: actions/checkout@v3 + - name: Set env + run: | + opensearch_version=$(node -p "require('./package.json').opensearchDashboards.version") + plugin_version=$(node -p "require('./package.json').version") + echo "OPENSEARCH_VERSION=$opensearch_version" >> $GITHUB_ENV + echo "PLUGIN_VERSION=$plugin_version" >> $GITHUB_ENV + shell: bash + - name: Download security plugin and create setup scripts uses: ./.github/actions/download-plugin with: diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index e445fa0af..b97526175 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -5,9 +5,7 @@ on: [push, pull_request] env: TEST_BROWSER_HEADLESS: 1 CI: 1 - OPENSEARCH_VERSION: 3.0.0 PLUGIN_NAME: opensearch-security - PLUGIN_VERSION: 3.0.0.0 jobs: tests: @@ -27,6 +25,14 @@ jobs: with: java-version: 11 + - name: Set env + run: | + opensearch_version=$(node -p "require('./package.json').opensearchDashboards.version") + plugin_version=$(node -p "require('./package.json').version") + echo "OPENSEARCH_VERSION=$opensearch_version" >> $GITHUB_ENV + echo "PLUGIN_VERSION=$plugin_version" >> $GITHUB_ENV + shell: bash + - uses: browser-actions/setup-geckodriver@v0.0.0 - run: geckodriver --version From 0a626c652573daf830d1a48347c4ca8a31c9a042 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Thu, 1 Jun 2023 13:13:48 -0400 Subject: [PATCH 08/31] Adds 2.8 release notes (#1464) Signed-off-by: Darshit Chanpura Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: Sam --- ...urity-dashboards-plugin.release-notes-2.8.0.0.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 release-notes/opensearch-security-dashboards-plugin.release-notes-2.8.0.0.md diff --git a/release-notes/opensearch-security-dashboards-plugin.release-notes-2.8.0.0.md b/release-notes/opensearch-security-dashboards-plugin.release-notes-2.8.0.0.md new file mode 100644 index 000000000..1d9e25acf --- /dev/null +++ b/release-notes/opensearch-security-dashboards-plugin.release-notes-2.8.0.0.md @@ -0,0 +1,13 @@ +## 2023-06-06 Version 2.8.0.0 + +Compatible with Opensearch-Dashboards 2.8.0 + +### Bug Fixes + +* Fix the test cases to adapt the security password validation feature ([#1437](https://github.com/opensearch-project/security-dashboards-plugin/pull/1437)) +* Fixing dynamic tenancy changes for issues 1412 (#1419) ([#1459](https://github.com/opensearch-project/security-dashboards-plugin/pull/1459)) + +### Enhancements + +* Add new cluster permissions constants for long-running operation notification feature in Index-Management repo ([#1444](https://github.com/opensearch-project/security-dashboards-plugin/pull/1444)) +* Adds the newly created admin api permissions to the static dropdown list ([#1426](https://github.com/opensearch-project/security-dashboards-plugin/pull/1426)) \ No newline at end of file From 3fdb80fb727ff99954dcd9e091dc33b48d3de020 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:03:35 +0100 Subject: [PATCH 09/31] Switch to new tenant after loading a copied long URL (#1450) * Replace legacy template with index template (#1359) Signed-off-by: Chang Liu Signed-off-by: leanneeliatra * added loginEndPointWithPath (#1358) * added loginEndPointWithPath Signed-off-by: Mattijs Vanhaverbeke Signed-off-by: leanneeliatra * Add release notes for 1.3.9 (#1379) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * No blank backend role before adding a new one in Create User page (#1384) * Add last backend role empty check Signed-off-by: nursaadat Signed-off-by: Saadat Nursultan * Add backend role empty check Signed-off-by: nursaadat Signed-off-by: Saadat Nursultan * Add strict comparison Signed-off-by: nursaadat Signed-off-by: Saadat Nursultan * Fix lint errors Signed-off-by: nursaadat Signed-off-by: Saadat Nursultan * Add tests for backend role panel Signed-off-by: Saadat Nursultan * Fix lint errors Signed-off-by: Saadat Nursultan --------- Signed-off-by: nursaadat Signed-off-by: Saadat Nursultan Co-authored-by: nursaadat Co-authored-by: Saadat Nursultan Signed-off-by: leanneeliatra * Fix script for Windows (#1393) * Fix script for Windows Signed-off-by: nurbqq Signed-off-by: nurbqq <106753054+nurbq@users.noreply.github.com> Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Signed-off-by: leanneeliatra * Adding new actions for ppl and datasource apis (#1395) * Adding new actions for ppl and datasource apis Signed-off-by: vamsi-amazon Signed-off-by: leanneeliatra * Fix "Get started" image is not adaptive to the browser window size. (#1396) * Fixed get-started page image not adapting to the browser window size Signed-off-by: Sirazh Gabdullin * Update fix by applying suggested changes Signed-off-by: Sirazh Gabdullin * Update unit tests snapshot Signed-off-by: Sirazh Gabdullin --------- Signed-off-by: Sirazh Gabdullin Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Signed-off-by: leanneeliatra * Split up a value into multiple cookie payloads (#1352) * PoC for splitting up a value into multiple cookie payloads Signed-off-by: Jochen Kressin * Cookie splitting for OpenId and SAML Signed-off-by: Jochen Kressin * Changes after review comments Signed-off-by: Jochen Kressin * WIP: First unit tests Signed-off-by: Jochen Kressin * More unit tests Signed-off-by: Jochen Kressin * Fix for multi auth, request argument was missing Signed-off-by: Jochen Kressin * Fixed linting errors Signed-off-by: Jochen Kressin * Added one additional cookie for the SAML integration tests Signed-off-by: Jochen Kressin --------- Signed-off-by: Jochen Kressin Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Signed-off-by: leanneeliatra * Dynamic tenancy configurations (#1394) * Dynamic multitenancy feature. Signed-off-by: Abhi Kalra * Dynamic multitenancy feature -PR feedback Signed-off-by: Abhi Kalra --------- Signed-off-by: Abhi Kalra Co-authored-by: Abhi Kalra Signed-off-by: leanneeliatra * Add release notes for 2.7.0 (#1407) * Add release notes for 2.7.0 Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removes tiny.amazon.com links (#1420) Signed-off-by: Darshit Chanpura Signed-off-by: leanneeliatra * Fixing dynamic tenancy changes for issues 1412 (#1419) * Fixing dynamic tenancy changes for opensearchdasbhoard.yaml Signed-off-by: Abhi Kalra Co-authored-by: Abhi Kalra Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Signed-off-by: leanneeliatra * Change the testuser's password in some integration test cases into a stronger password (#1428) * Change the testuser's password into a stronger password Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Increment version to 3.0.0.0 (#1414) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot Signed-off-by: leanneeliatra * Adds the newly created admin api permissions to the static dropdown list (#1426) * Adds the newly created admin api permissions to the static dropdown of permissions displayed when creating/modifying a role --------- Signed-off-by: Darshit Chanpura Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Update account-nav-button.tsx Fix added to set the window.location to the pathname, rather than just reload & clear lastURL as it would be from the previous tenant. Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * Update account-nav-button.tsx Adding comments to explain changes Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * add new cluster permissions constants for lron (#1444) Signed-off-by: zhichao-aws Signed-off-by: leanneeliatra * removing whitespace due to linting fix Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * Adding tests for account-nav-button wip Signed-off-by: leanneeliatra * put commented code to original state Signed-off-by: leanneeliatra * Skip flaky SAML test as it awaits a fix (#1453) Signed-off-by: Craig Perkins Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Use version from package.json for integration tests (#1463) * Use version from package.json for integration tests Signed-off-by: Craig Perkins Signed-off-by: leanneeliatra * Adds 2.8 release notes (#1464) Signed-off-by: Darshit Chanpura Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Adding tests to jest test for tenant switch. Putting test in correct folder and renaming function. Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra * handle switch calling correct function Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * checking for session storage Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra * adding window to make sessionStorage more explicit Signed-off-by: leanneeliatra * Moved the test into account-nav-button.test.tsx Signed-off-by: leanneeliatra * Removing additional files. Signed-off-by: leanneeliatra * Declared session storage as a constant Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Update account-nav-button.tsx Fix added to set the window.location to the pathname, rather than just reload & clear lastURL as it would be from the previous tenant. Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * Update account-nav-button.tsx Adding comments to explain changes Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * removing whitespace due to linting fix Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * Adding tests for account-nav-button wip Signed-off-by: leanneeliatra * put commented code to original state Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Adding tests to jest test for tenant switch. Putting test in correct folder and renaming function. Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra * handle switch calling correct function Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * Removing additional files. Signed-off-by: leanneeliatra * Fix unwanted changes Signed-off-by: Darshit Chanpura Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Update account-nav-button.tsx Fix added to set the window.location to the pathname, rather than just reload & clear lastURL as it would be from the previous tenant. Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * Adding tests for account-nav-button wip Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Adding tests to jest test for tenant switch. Putting test in correct folder and renaming function. Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra * checking for session storage Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra * Declared session storage as a constant Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Adding tests for account-nav-button wip Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Adding tests to jest test for tenant switch. Putting test in correct folder and renaming function. Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra * Moved the test into account-nav-button.test.tsx Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Dynamic tenancy configurations (#1394) * Dynamic multitenancy feature. Signed-off-by: Abhi Kalra * Dynamic multitenancy feature -PR feedback Signed-off-by: Abhi Kalra --------- Signed-off-by: Abhi Kalra Co-authored-by: Abhi Kalra Signed-off-by: leanneeliatra * Fixing dynamic tenancy changes for issues 1412 (#1419) * Fixing dynamic tenancy changes for opensearchdasbhoard.yaml Signed-off-by: Abhi Kalra Co-authored-by: Abhi Kalra Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Use version from package.json for integration tests (#1463) * Use version from package.json for integration tests Signed-off-by: Craig Perkins Signed-off-by: leanneeliatra * Removing unneded file Signed-off-by: leanneeliatra --------- Signed-off-by: Chang Liu Signed-off-by: leanneeliatra Signed-off-by: Ryan Liang Signed-off-by: nursaadat Signed-off-by: Saadat Nursultan Signed-off-by: nurbqq Signed-off-by: nurbqq <106753054+nurbq@users.noreply.github.com> Signed-off-by: vamsi-amazon Signed-off-by: Sirazh Gabdullin Signed-off-by: Jochen Kressin Signed-off-by: Abhi Kalra Signed-off-by: Darshit Chanpura Signed-off-by: opensearch-ci-bot Signed-off-by: Leanne Lacey-Byrne Signed-off-by: zhichao-aws Signed-off-by: Craig Perkins Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: Craig Perkins Co-authored-by: Chang Liu Co-authored-by: mattieserver <3049868+mattieserver@users.noreply.github.com> Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Co-authored-by: Saadat Nursultan <39532643+nurSaadat@users.noreply.github.com> Co-authored-by: nursaadat Co-authored-by: Saadat Nursultan Co-authored-by: Nurbakhyt Sembayev <106753054+nurbq@users.noreply.github.com> Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Co-authored-by: Vamsi Manohar Co-authored-by: Sirazh Gabdullin Co-authored-by: Jochen Kressin <126353411+jochen-kressin@users.noreply.github.com> Co-authored-by: Abhi Kalra <99718513+abhivka7@users.noreply.github.com> Co-authored-by: Abhi Kalra Co-authored-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Co-authored-by: opensearch-trigger-bot[bot] <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Co-authored-by: opensearch-ci-bot Co-authored-by: zhichao-aws Co-authored-by: Craig Perkins Co-authored-by: Darshit Chanpura Signed-off-by: Sam --- public/apps/account/account-nav-button.tsx | 23 +++++- .../account/test/account-nav-button.test.tsx | 59 ++++++++++++++- .../apps/account/test/switch-tenants.test.tsx | 73 +++++++++++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 public/apps/account/test/switch-tenants.test.tsx diff --git a/public/apps/account/account-nav-button.tsx b/public/apps/account/account-nav-button.tsx index 8f67c3328..02e04f3af 100644 --- a/public/apps/account/account-nav-button.tsx +++ b/public/apps/account/account-nav-button.tsx @@ -62,7 +62,7 @@ export function AccountNavButton(props: { }} handleSwitchAndClose={() => { setModal(null); - window.location.reload(); + reloadAfterTenantSwitch(); }} tenant={props.tenant!} /> @@ -186,3 +186,24 @@ export function AccountNavButton(props: { ); } + +export function reloadAfterTenantSwitch(): void { + // the below portion is to clear URLs starting with 'lastUrl' + // when switching tenants, the last URLs will be from the old tenancy therefore we need to remove these from sessionStorage. + const lastUrls = []; + const { sessionStorage } = window; + + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i); + if (key?.startsWith('lastUrl')) { + lastUrls.push(key); + } + } + for (let i = 0; i < lastUrls.length; i++) { + sessionStorage.removeItem(lastUrls[i]); + } + + // rather than just reload when we switch tenants, we set the URL to the pathname. i.e. the portion like: '/app/dashboards' + // therefore, the copied URL will now allow tenancy changes. + window.location.href = window.location.pathname; +} diff --git a/public/apps/account/test/account-nav-button.test.tsx b/public/apps/account/test/account-nav-button.test.tsx index 0bf24798e..ff20b0f25 100644 --- a/public/apps/account/test/account-nav-button.test.tsx +++ b/public/apps/account/test/account-nav-button.test.tsx @@ -15,7 +15,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { AccountNavButton } from '../account-nav-button'; +import { AccountNavButton, reloadAfterTenantSwitch } from '../account-nav-button'; import { getShouldShowTenantPopup, setShouldShowTenantPopup } from '../../../utils/storage-utils'; import { getDashboardsInfo } from '../../../utils/dashboards-info-utils'; @@ -174,3 +174,60 @@ describe('Account navigation button, multitenancy disabled', () => { expect(setState).toBeCalledTimes(0); }); }); + +describe('Reload window after tenant switch', () => { + const originalLocation = window.location; + const mockSetWindowHref = jest.fn(); + let pathname: string = ''; + beforeAll(() => { + pathname = '/app/myapp'; + Object.defineProperty(window, 'location', { + value: { + get pathname() { + return pathname; + }, + get href() { + return '/app/dashboards?security_tenant=admin_tenant'; + }, + set href(value: string) { + mockSetWindowHref(value); + }, + }, + }); + }); + + afterAll(() => { + window.location = originalLocation; + }); + + it('should remove the tenant query parameter before reloading', () => { + pathname = '/app/pathname-only'; + reloadAfterTenantSwitch(); + expect(mockSetWindowHref).toHaveBeenCalledWith(pathname); + }); +}); + +describe('Clear lastUrls after tenant switch', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should clear out keys with a lastUrl prefix', () => { + window.sessionStorage.setItem('lastUrl:dashboard', '/dashboard1'); + window.sessionStorage.setItem('lastUrl:otherApp', '/otherApp'); + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:dashboard'); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:otherApp'); + expect(mockRemoveItem).toHaveBeenCalledTimes(2); + }); + + it('should not clear out keys without a lastUrl prefix', () => { + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledTimes(0); + }); +}); diff --git a/public/apps/account/test/switch-tenants.test.tsx b/public/apps/account/test/switch-tenants.test.tsx new file mode 100644 index 000000000..1eb798fe0 --- /dev/null +++ b/public/apps/account/test/switch-tenants.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { reloadAfterTenantSwitch } from '../account-nav-button'; + +describe('Reload window after tenant switch', () => { + const originalLocation = window.location; + const mockSetWindowHref = jest.fn(); + let pathname: string = ''; + beforeAll(() => { + pathname = '/app/myapp'; + Object.defineProperty(window, 'location', { + value: { + get pathname() { + return pathname; + }, + get href() { + return '/app/dashboards?security_tenant=admin_tenant'; + }, + set href(value: string) { + mockSetWindowHref(value); + }, + }, + }); + }); + + afterAll(() => { + window.location = originalLocation; + }); + + it('should remove the tenant query parameter before reloading', () => { + pathname = '/app/pathname-only'; + reloadAfterTenantSwitch(); + expect(mockSetWindowHref).toHaveBeenCalledWith(pathname); + }); +}); + +describe('Clear lastUrls after tenant switch', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should clear out keys with a lastUrl prefix', () => { + window.sessionStorage.setItem('lastUrl:dashboard', '/dashboard1'); + window.sessionStorage.setItem('lastUrl:otherApp', '/otherApp'); + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:dashboard'); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:otherApp'); + expect(mockRemoveItem).toHaveBeenCalledTimes(2); + }); + + it('should not clear out keys without a lastUrl prefix', () => { + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledTimes(0); + }); +}); From 8f2b8554b92b6c08db57d27118758ba6003ae2ec Mon Sep 17 00:00:00 2001 From: Derek Ho Date: Wed, 21 Jun 2023 21:56:23 -0400 Subject: [PATCH 10/31] update permissions according to backend (#1480) Signed-off-by: Derek Ho Signed-off-by: Sam --- public/apps/configuration/constants.tsx | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/public/apps/configuration/constants.tsx b/public/apps/configuration/constants.tsx index 6623f1a53..ff347054f 100644 --- a/public/apps/configuration/constants.tsx +++ b/public/apps/configuration/constants.tsx @@ -215,6 +215,25 @@ export const CLUSTER_PERMISSIONS: string[] = [ 'restapi:admin/ssl/certs/info', 'restapi:admin/ssl/certs/reload', 'restapi:admin/tenants', + 'indices:admin/template/delete', + 'indices:admin/template/get', + 'indices:admin/template/put', + 'indices:admin/index_template/delete', + 'indices:admin/index_template/get', + 'indices:admin/index_template/put', + 'indices:admin/index_template/simulate', + 'indices:admin/index_template/simulate_index', + 'indices:data/read/scroll', + 'indices:data/read/scroll/clear', + 'indices:data/write/bulk', + 'indices:data/write/bulk*', + 'indices:data/read/mget', + 'indices:data/read/mget*', + 'indices:data/read/msearch', + 'indices:data/read/msearch/template', + 'indices:data/read/mtv', + 'indices:data/read/mtv*', + 'indices:data/write/reindex', ]; export function includeClusterPermissions(clusterPermissionsToInclude: string[]) { @@ -243,11 +262,6 @@ export const INDEX_PERMISSIONS: string[] = [ 'indices:admin/flush*', 'indices:admin/forcemerge', 'indices:admin/get', - 'indices:admin/index_template/delete', - 'indices:admin/index_template/get', - 'indices:admin/index_template/put', - 'indices:admin/index_template/simulate', - 'indices:admin/index_template/simulate_index', 'indices:admin/mapping/auto_put', 'indices:admin/mapping/put', 'indices:admin/mappings/fields/get', @@ -267,9 +281,6 @@ export const INDEX_PERMISSIONS: string[] = [ 'indices:admin/shards/search_shards', 'indices:admin/shrink', 'indices:admin/synced_flush', - 'indices:admin/template/delete', - 'indices:admin/template/get', - 'indices:admin/template/put', 'indices:admin/types/exists', 'indices:admin/upgrade', 'indices:admin/validate/query', @@ -277,27 +288,16 @@ export const INDEX_PERMISSIONS: string[] = [ 'indices:data/read/field_caps', 'indices:data/read/field_caps*', 'indices:data/read/get', - 'indices:data/read/mget', - 'indices:data/read/mget*', - 'indices:data/read/msearch', - 'indices:data/read/msearch/template', - 'indices:data/read/mtv', - 'indices:data/read/mtv*', 'indices:data/read/point_in_time/create', 'indices:data/read/point_in_time/delete', 'indices:data/read/point_in_time/readall', - 'indices:data/read/scroll', - 'indices:data/read/scroll/clear', 'indices:data/read/search', 'indices:data/read/search*', 'indices:data/read/search/template', 'indices:data/read/tv', - 'indices:data/write/bulk', - 'indices:data/write/bulk*', 'indices:data/write/delete', 'indices:data/write/delete/byquery', 'indices:data/write/index', - 'indices:data/write/reindex', 'indices:data/write/update', 'indices:data/write/update/byquery', 'indices:monitor/data_stream/stats', From 2fd888128c0713d2b155c0612f7da509f5106116 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Thu, 22 Jun 2023 20:11:25 +0100 Subject: [PATCH 11/31] Add the tenant into the short URL once the short URL is resolved (#1462) * More information added Signed-off-by: leanneeliatra * More information added Signed-off-by: leanneeliatra * fixed linting errors Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Extracting function to tenant_resolver and adding more appropriate comments. Signed-off-by: leanneeliatra * lint errors fixed Signed-off-by: leanneeliatra * Use version from package.json for integration tests (#1463) * Use version from package.json for integration tests Signed-off-by: Craig Perkins Signed-off-by: leanneeliatra * Adds 2.8 release notes (#1464) Signed-off-by: Darshit Chanpura Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Cleaning up comments Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra * linting issues resolved Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: leanneeliatra * Removing Prerequisite Checks Workflow (#1456) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: leanneeliatra * Update server/multitenancy/tenant_resolver.ts Co-authored-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Signed-off-by: leanneeliatra * comments addressed & linting amended Signed-off-by: leanneeliatra * integration test fix following rebase Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Signed-off-by: leanneeliatra --------- Signed-off-by: leanneeliatra Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: Craig Perkins Signed-off-by: Darshit Chanpura Signed-off-by: Leanne Lacey-Byrne Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Co-authored-by: Craig Perkins Co-authored-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Signed-off-by: Sam --- server/multitenancy/tenant_resolver.test.ts | 88 +++++++++++++++++++++ server/multitenancy/tenant_resolver.ts | 31 ++++++++ server/plugin.ts | 10 +++ 3 files changed, 129 insertions(+) create mode 100644 server/multitenancy/tenant_resolver.test.ts diff --git a/server/multitenancy/tenant_resolver.test.ts b/server/multitenancy/tenant_resolver.test.ts new file mode 100644 index 000000000..166c3a91d --- /dev/null +++ b/server/multitenancy/tenant_resolver.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { httpServerMock } from '../../../../src/core/server/mocks'; +import { OpenSearchDashboardsRequest } from '../../../../src/core/server'; +import { addTenantParameterToResolvedShortLink } from './tenant_resolver'; +import { Request, ResponseObject } from '@hapi/hapi'; + +describe('Preserve the tenant parameter in short urls', () => { + it(`adds the tenant as a query parameter for goto short links`, async () => { + const resolvedUrl = '/url/resolved'; + const rawRequest = httpServerMock.createRawRequest({ + url: { + pathname: '/goto/123', + }, + headers: { + securitytenant: 'dummy_tenant', + }, + response: { + headers: { + location: resolvedUrl, + }, + }, + }) as Request; + + const osRequest = OpenSearchDashboardsRequest.from(rawRequest); + addTenantParameterToResolvedShortLink(osRequest); + + expect((rawRequest.response as ResponseObject).headers.location).toEqual( + resolvedUrl + '?security_tenant=dummy_tenant' + ); + }); + + it(`ignores links not starting with /goto`, async () => { + const resolvedUrl = '/url/resolved'; + const rawRequest = httpServerMock.createRawRequest({ + url: { + pathname: '/dontgoto/123', + }, + headers: { + securitytenant: 'dummy_tenant', + }, + response: { + headers: { + location: resolvedUrl, + }, + }, + }) as Request; + + const osRequest = OpenSearchDashboardsRequest.from(rawRequest); + addTenantParameterToResolvedShortLink(osRequest); + + expect((rawRequest.response as ResponseObject).headers.location).toEqual(resolvedUrl); + }); + + it(`checks that a redirect location is present before applying the query parameter`, async () => { + const rawRequest = httpServerMock.createRawRequest({ + url: { + pathname: '/goto/123', + }, + headers: { + securitytenant: 'dummy_tenant', + }, + response: { + headers: { + someotherheader: 'abc', + }, + }, + }) as Request; + + const osRequest = OpenSearchDashboardsRequest.from(rawRequest); + addTenantParameterToResolvedShortLink(osRequest); + + expect((rawRequest.response as ResponseObject).headers.location).toBeFalsy(); + }); +}); diff --git a/server/multitenancy/tenant_resolver.ts b/server/multitenancy/tenant_resolver.ts index c4ca9d5d4..a8b8b4e5e 100755 --- a/server/multitenancy/tenant_resolver.ts +++ b/server/multitenancy/tenant_resolver.ts @@ -14,9 +14,14 @@ */ import { isEmpty, findKey, cloneDeep } from 'lodash'; +import { OpenSearchDashboardsRequest } from 'opensearch-dashboards/server'; +import { ResponseObject } from '@hapi/hapi'; import { SecuritySessionCookie } from '../session/security_cookie'; import { SecurityPluginConfigType } from '..'; import { GLOBAL_TENANT_SYMBOL, PRIVATE_TENANT_SYMBOL, globalTenantName } from '../../common'; +import { modifyUrl } from '../../../../packages/osd-std'; +import { ensureRawRequest } from '../../../../src/core/server/http/router'; +import { GOTO_PREFIX } from '../../../../src/plugins/share/common/short_url_routes'; export const PRIVATE_TENANTS: string[] = [PRIVATE_TENANT_SYMBOL, 'private']; export const GLOBAL_TENANTS: string[] = ['global', GLOBAL_TENANT_SYMBOL, 'Global']; @@ -194,3 +199,29 @@ export function resolve( export function isValidTenant(tenant: string | undefined | null): boolean { return tenant !== undefined && tenant !== null; } + +/** + * If multitenancy is enabled & the URL entered starts with /goto, + * We will modify the rawResponse to add a new parameter to the URL, the security_tenant (or value for tenant when in multitenancy) + * With the security_tenant added, the resolved short URL now contains the security_tenant information (so the short URL retains the tenant information). + **/ + +export function addTenantParameterToResolvedShortLink(request: OpenSearchDashboardsRequest) { + if (request.url.pathname.startsWith(`${GOTO_PREFIX}/`)) { + const rawRequest = ensureRawRequest(request); + const rawResponse = rawRequest.response as ResponseObject; + + // Make sure the request really should redirect + if (rawResponse.headers.location) { + const modifiedUrl = modifyUrl(rawResponse.headers.location as string, (parts) => { + if (parts.query.security_tenant === undefined) { + parts.query.security_tenant = request.headers.securitytenant as string; + } + // Mutating the headers toolkit.next({headers: ...}) logs a warning about headers being overwritten + }); + rawResponse.headers.location = modifiedUrl; + } + } + + return request; +} diff --git a/server/plugin.ts b/server/plugin.ts index 84d94e4f1..c6aec6e58 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -15,6 +15,7 @@ import { first } from 'rxjs/operators'; import { Observable } from 'rxjs'; +import { ResponseObject } from '@hapi/hapi'; import { PluginInitializerContext, CoreSetup, @@ -47,6 +48,7 @@ import { setupMultitenantRoutes } from './multitenancy/routes'; import { defineAuthTypeRoutes } from './routes/auth_type_routes'; import { createMigrationOpenSearchClient } from '../../../src/core/server/saved_objects/migrations/core'; import { SecuritySavedObjectsClientWrapper } from './saved_objects/saved_objects_wrapper'; +import { addTenantParameterToResolvedShortLink } from './multitenancy/tenant_resolver'; export interface SecurityPluginRequestContext { logger: Logger; @@ -125,6 +127,14 @@ export class SecurityPlugin implements Plugin { + addTenantParameterToResolvedShortLink(request); + return toolkit.next(); + }); + } + // Register server side APIs defineRoutes(router); defineAuthTypeRoutes(router, config); From 605b37ba16214cb3e54475a417eb1c1a6d28d47e Mon Sep 17 00:00:00 2001 From: Hailong Cui Date: Thu, 29 Jun 2023 21:45:56 +0800 Subject: [PATCH 12/31] Move security plugin from Plugin to Management section on left navigation menu (#1474) * move security management section Signed-off-by: Hailong Cui * Fix eslint Signed-off-by: Hailong Cui * Remove plugins pages for management overview registration Signed-off-by: Hailong Cui --------- Signed-off-by: Hailong Cui Co-authored-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Co-authored-by: Craig Perkins Signed-off-by: Sam --- opensearch_dashboards.json | 5 ++++- public/plugin.ts | 24 +++++++++++++++++------- public/types.ts | 2 ++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 42e148cb1..c5d7cac43 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -9,6 +9,9 @@ "navigation", "savedObjectsManagement" ], + "optionalPlugins": [ + "managementOverview" + ], "server": true, "ui": true -} \ No newline at end of file +} diff --git a/public/plugin.ts b/public/plugin.ts index 991c00834..7ac039fb1 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -15,17 +15,19 @@ import { BehaviorSubject } from 'rxjs'; import { SavedObjectsManagementColumn } from 'src/plugins/saved_objects_management/public'; +import { i18n } from '@osd/i18n'; import { AppMountParameters, AppStatus, AppUpdater, CoreSetup, CoreStart, + DEFAULT_APP_CATEGORIES, Plugin, PluginInitializerContext, } from '../../../src/core/public'; import { APP_ID_LOGIN, CUSTOM_ERROR_PAGE_URI, LOGIN_PAGE_URI, PLUGIN_NAME } from '../common'; -import { APP_ID_CUSTOMERROR } from '../common/index'; +import { APP_ID_CUSTOMERROR } from '../common'; import { setupTopNavButton } from './apps/account/account-app'; import { fetchAccountInfoSafe } from './apps/account/utils'; import { @@ -98,7 +100,7 @@ export class SecurityPlugin core.application.register({ id: PLUGIN_NAME, title: 'Security', - order: 8000, + order: 9050, mount: async (params: AppMountParameters) => { const { renderApp } = await import('./apps/configuration/configuration-app'); const [coreStart, depsStart] = await core.getStartServices(); @@ -112,12 +114,20 @@ export class SecurityPlugin return renderApp(coreStart, depsStart as SecurityPluginStartDependencies, params, config); }, - category: { - id: 'opensearch', - label: 'OpenSearch Plugins', - order: 2000, - }, + category: DEFAULT_APP_CATEGORIES.management, }); + + if (deps.managementOverview) { + deps.managementOverview.register({ + id: PLUGIN_NAME, + title: 'Security', + order: 9050, + description: i18n.translate('security.securityDescription', { + defaultMessage: + 'Configure how users access data in OpenSearch with authentication, access control and audit logging.', + }), + }); + } } core.application.register({ diff --git a/public/types.ts b/public/types.ts index 5e5a904f5..7169b893a 100644 --- a/public/types.ts +++ b/public/types.ts @@ -18,6 +18,7 @@ import { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart, } from '../../../src/plugins/saved_objects_management/public'; +import { ManagementOverViewPluginSetup } from '../../../src/plugins/management_overview/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SecurityPluginSetup {} @@ -26,6 +27,7 @@ export interface SecurityPluginStart {} export interface SecurityPluginSetupDependencies { savedObjectsManagement: SavedObjectsManagementPluginSetup; + managementOverview?: ManagementOverViewPluginSetup; } export interface SecurityPluginStartDependencies { From 70779e66acbbca1ec9dc72cadb00ea89d4bd88b1 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 12:41:37 -0400 Subject: [PATCH 13/31] Increment version to 3.0.0.0 (#1496) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot Signed-off-by: Sam --- opensearch_dashboards.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index c5d7cac43..994096906 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -14,4 +14,4 @@ ], "server": true, "ui": true -} +} \ No newline at end of file From fb2bab001113b46c352fd4263b12ec0f0d59ead9 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 30 Jun 2023 12:52:50 -0500 Subject: [PATCH 14/31] Fix a bad import path (#1498) * Fix a bad import path - Resolves #1487 Signed-off-by: Peter Nied Signed-off-by: Peter Nied Co-authored-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Signed-off-by: Sam --- server/multitenancy/tenant_resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/multitenancy/tenant_resolver.ts b/server/multitenancy/tenant_resolver.ts index a8b8b4e5e..58fa241d1 100755 --- a/server/multitenancy/tenant_resolver.ts +++ b/server/multitenancy/tenant_resolver.ts @@ -16,10 +16,10 @@ import { isEmpty, findKey, cloneDeep } from 'lodash'; import { OpenSearchDashboardsRequest } from 'opensearch-dashboards/server'; import { ResponseObject } from '@hapi/hapi'; +import { modifyUrl } from '@osd/std'; import { SecuritySessionCookie } from '../session/security_cookie'; import { SecurityPluginConfigType } from '..'; import { GLOBAL_TENANT_SYMBOL, PRIVATE_TENANT_SYMBOL, globalTenantName } from '../../common'; -import { modifyUrl } from '../../../../packages/osd-std'; import { ensureRawRequest } from '../../../../src/core/server/http/router'; import { GOTO_PREFIX } from '../../../../src/plugins/share/common/short_url_routes'; From 0bb077eb8e4d7c64b8b002b918603edf24bae74e Mon Sep 17 00:00:00 2001 From: Derek Ho Date: Tue, 11 Jul 2023 14:53:21 -0400 Subject: [PATCH 15/31] filter high level groups and action groups by cluster and index (#1482) * filter high level groups and action groups by cluster and index Signed-off-by: Derek Ho * remove unecessary console Signed-off-by: Derek Ho * add semicolon back Signed-off-by: Derek Ho * use map instead of flat map Signed-off-by: Derek Ho * fix lint Signed-off-by: Derek Ho * fix tests Signed-off-by: Derek Ho * revert file Signed-off-by: Derek Ho * fix up tests Signed-off-by: Derek Ho * lint Signed-off-by: Derek Ho --------- Signed-off-by: Derek Ho Signed-off-by: Sam --- .../panels/role-edit/role-edit.tsx | 23 ++- .../test/role-edit-filtering.test.tsx | 161 ++++++++++++++++++ 2 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 public/apps/configuration/panels/role-edit/test/role-edit-filtering.test.tsx diff --git a/public/apps/configuration/panels/role-edit/role-edit.tsx b/public/apps/configuration/panels/role-edit/role-edit.tsx index 53ec39d30..142cba1b3 100644 --- a/public/apps/configuration/panels/role-edit/role-edit.tsx +++ b/public/apps/configuration/panels/role-edit/role-edit.tsx @@ -46,7 +46,7 @@ import { } from './tenant-panel'; import { RoleIndexPermissionStateClass, RoleTenantPermissionStateClass } from './types'; import { buildHashUrl, buildUrl } from '../../utils/url-builder'; -import { ComboBoxOptions, ResourceType, Action } from '../../types'; +import { ComboBoxOptions, ResourceType, Action, ActionGroupItem } from '../../types'; import { useToastState, createUnknownErrorToast, @@ -106,12 +106,12 @@ export function RoleEdit(props: RoleEditDeps) { } }, [addToast, props.action, props.coreStart.http, props.sourceRoleName]); - const [actionGroups, setActionGroups] = useState([]); + const [actionGroups, setActionGroups] = useState>([]); React.useEffect(() => { const fetchActionGroupNames = async () => { try { const actionGroupsObject = await fetchActionGroups(props.coreStart.http); - setActionGroups(Object.keys(actionGroupsObject)); + setActionGroups(Object.entries(actionGroupsObject)); } catch (e) { addToast(createUnknownErrorToast('actionGroup', 'load data')); console.error(e); @@ -167,12 +167,25 @@ export function RoleEdit(props: RoleEditDeps) { const clusterWisePermissionOptions = [ { label: 'Permission groups', - options: actionGroups.map(stringToComboBoxOption), + options: actionGroups + .filter((actionGroup) => actionGroup[1].type === 'cluster') + .map((actionGroup) => actionGroup[0]) + .map(stringToComboBoxOption), }, { label: 'Cluster permissions', options: CLUSTER_PERMISSIONS.map(stringToComboBoxOption), }, + ]; + + const indexWisePermissionOptions = [ + { + label: 'Permission groups', + options: actionGroups + .filter((actionGroup) => actionGroup[1].type === 'index') + .map((actionGroup) => actionGroup[0]) + .map(stringToComboBoxOption), + }, { label: 'Index permissions', options: INDEX_PERMISSIONS.map(stringToComboBoxOption), @@ -219,7 +232,7 @@ export function RoleEdit(props: RoleEditDeps) { ({ + getRoleDetail: jest.fn().mockReturnValue({ + cluster_permissions: [], + index_permissions: [], + tenant_permissions: [], + reserved: false, + }), + updateRole: jest.fn(), +})); +jest.mock('../../../utils/action-groups-utils'); + +jest.mock('../cluster-permission-panel', () => ({ + ClusterPermissionPanel: jest.fn(() => null) as jest.Mock, +})); + +jest.mock('../index-permission-panel', () => ({ + IndexPermissionPanel: jest.fn(() => null) as jest.Mock, +})); + +describe('Role edit filtering', () => { + const sampleSourceRole = 'role'; + const mockCoreStart = { + http: 1, + }; + + (fetchActionGroups as jest.Mock).mockResolvedValue({ + data_access: { + reserved: true, + hidden: false, + allowed_actions: ['indices:data/*', 'crud'], + type: 'index', + description: 'Allow all read/write operations on data', + static: true, + }, + cluster_manage_pipelines: { + reserved: true, + hidden: false, + allowed_actions: ['cluster:admin/ingest/pipeline/*'], + type: 'cluster', + description: 'Manage pipelines', + static: true, + }, + }); + + it('basic cluster permission panel rendering', async () => { + const action = 'create'; + const buildBreadcrumbs = jest.fn(); + + render( + + ); + + await act(async () => { + await waitFor(() => { + expect(ClusterPermissionPanel).toHaveBeenCalled(); + }); + }); + + const lastCallArgs = + ClusterPermissionPanel.mock.calls[ClusterPermissionPanel.mock.calls.length - 1]; + const [props] = lastCallArgs; + + // Cluster Permission Panel props is filtered to action groups with type cluster, and only the cluster permission constants + expect(props.optionUniverse).toEqual([ + { + label: 'Permission groups', + options: [ + { + label: 'cluster_manage_pipelines', + }, + ], + }, + { + label: 'Cluster permissions', + options: CLUSTER_PERMISSIONS.map((x) => { + return { label: x }; + }), + }, + ]); + }); + + it('basic index permission panel rendering', async () => { + const action = 'create'; + const buildBreadcrumbs = jest.fn(); + + render( + + ); + + await act(async () => { + await waitFor(() => { + expect(IndexPermissionPanel).toHaveBeenCalled(); + }); + }); + + const lastCallArgs = + IndexPermissionPanel.mock.calls[IndexPermissionPanel.mock.calls.length - 1]; + const [props] = lastCallArgs; + + // Index Permission Panel props is filtered to action groups with type index, and only the index permission constants + expect(props.optionUniverse).toEqual([ + { + label: 'Permission groups', + options: [ + { + label: 'data_access', + }, + ], + }, + { + label: 'Index permissions', + options: INDEX_PERMISSIONS.map((x) => { + return { label: x }; + }), + }, + ]); + }); +}); From 261a4c402f396d8d7d95ca3ee247628e422974b4 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 11 Jul 2023 16:41:45 -0400 Subject: [PATCH 16/31] Use password message from /dashboardsinfo (#1503) * Use password message from /dashboardsinfo Signed-off-by: Craig Perkins * Run eslint --fix Signed-off-by: Craig Perkins * Remove unused attribute Signed-off-by: Craig Perkins * Clean up test Signed-off-by: Craig Perkins * Remove redundant mock Signed-off-by: Craig Perkins * Removed TODO messages Signed-off-by: Craig Perkins --------- Signed-off-by: Craig Perkins Signed-off-by: Sam --- public/apps/account/password-reset-panel.tsx | 19 +++++- .../internal-user-edit/internal-user-edit.tsx | 6 +- .../utils/password-edit-panel.tsx | 20 ++++++- .../utils/test/password-edit-panel.test.tsx | 58 ++++++++++++++++--- public/types.ts | 1 + 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/public/apps/account/password-reset-panel.tsx b/public/apps/account/password-reset-panel.tsx index 6119a3966..97d45400e 100644 --- a/public/apps/account/password-reset-panel.tsx +++ b/public/apps/account/password-reset-panel.tsx @@ -32,6 +32,7 @@ import { logout, updateNewPassword } from './utils'; import { PASSWORD_INSTRUCTION } from '../apps-constants'; import { constructErrorMessageAndLog } from '../error-utils'; import { validateCurrentPassword } from '../../utils/login-utils'; +import { getDashboardsInfo } from '../../utils/dashboards-info-utils'; interface PasswordResetPanelProps { coreStart: CoreStart; @@ -57,6 +58,22 @@ export function PasswordResetPanel(props: PasswordResetPanelProps) { const [errorCallOut, setErrorCallOut] = React.useState(''); + const [passwordHelpText, setPasswordHelpText] = React.useState(PASSWORD_INSTRUCTION); + + React.useEffect(() => { + const fetchData = async () => { + try { + setPasswordHelpText( + (await getDashboardsInfo(props.coreStart.http)).password_validation_error_message + ); + } catch (e) { + console.error(e); + } + }; + + fetchData(); + }, [props.coreStart.http]); + const handleReset = async () => { const http = props.coreStart.http; // validate the current password @@ -107,7 +124,7 @@ export function PasswordResetPanel(props: PasswordResetPanelProps) { - + diff --git a/public/apps/configuration/utils/password-edit-panel.tsx b/public/apps/configuration/utils/password-edit-panel.tsx index 7f7b7f558..0a52347ae 100644 --- a/public/apps/configuration/utils/password-edit-panel.tsx +++ b/public/apps/configuration/utils/password-edit-panel.tsx @@ -14,17 +14,35 @@ */ import React from 'react'; +import { CoreStart } from 'opensearch-dashboards/public'; import { EuiFieldText, EuiIcon } from '@elastic/eui'; import { FormRow } from './form-row'; import { PASSWORD_INSTRUCTION } from '../../apps-constants'; +import { getDashboardsInfo } from '../../../utils/dashboards-info-utils'; export function PasswordEditPanel(props: { + coreStart: CoreStart; updatePassword: (p: string) => void; updateIsInvalid: (v: boolean) => void; }) { const [password, setPassword] = React.useState(''); const [repeatPassword, setRepeatPassword] = React.useState(''); const [isRepeatPasswordInvalid, setIsRepeatPasswordInvalid] = React.useState(false); + const [passwordHelpText, setPasswordHelpText] = React.useState(PASSWORD_INSTRUCTION); + + React.useEffect(() => { + const fetchData = async () => { + try { + setPasswordHelpText( + (await getDashboardsInfo(props.coreStart.http)).password_validation_error_message + ); + } catch (e) { + console.error(e); + } + }; + + fetchData(); + }, [props.coreStart.http]); React.useEffect(() => { props.updatePassword(password); @@ -43,7 +61,7 @@ export function PasswordEditPanel(props: { return ( <> - + } 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 e44b91268..49ab94b65 100644 --- a/public/apps/configuration/utils/test/password-edit-panel.test.tsx +++ b/public/apps/configuration/utils/test/password-edit-panel.test.tsx @@ -13,9 +13,24 @@ * permissions and limitations under the License. */ -import { shallow } from 'enzyme'; +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, + private_tenant_enabled: true, + default_tenant: '', + password_validation_error_message: + 'Password must be minimum 5 characters long and must contain at least one uppercase letter, one lowercase letter, one digit, and one special character.', +}; + +jest.mock('../../../../utils/dashboards-info-utils', () => ({ + getDashboardsInfo: jest.fn().mockImplementation(() => { + return mockDashboardsInfo; + }), +})); describe('Password edit panel', () => { let component; @@ -24,21 +39,43 @@ describe('Password edit panel', () => { const updateIsInvalid = jest.fn(); const useState = jest.spyOn(React, 'useState'); const useEffect = jest.spyOn(React, 'useEffect'); + const mockCoreStart = { + http: 1, + }; beforeEach(() => { useEffect.mockImplementationOnce((f) => f()); useState.mockImplementation((initialValue) => [initialValue, setState]); - component = shallow( - - ); }); - it('renders', () => { - expect(updatePassword).toHaveBeenCalledTimes(1); - expect(updateIsInvalid).toHaveBeenCalledTimes(1); + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders', (done) => { + mount( + + ); + process.nextTick(() => { + expect(updatePassword).toHaveBeenCalledTimes(1); + expect(updateIsInvalid).toHaveBeenCalledTimes(1); + expect(setState).toBeCalledWith(mockDashboardsInfo.password_validation_error_message); + done(); + }); }); it('password field update', () => { + component = shallow( + + ); const event = { target: { value: 'dummy' }, } as React.ChangeEvent; @@ -47,6 +84,13 @@ describe('Password edit panel', () => { }); it('repeat password field update', () => { + component = shallow( + + ); const event = { target: { value: 'dummy' }, } as React.ChangeEvent; diff --git a/public/types.ts b/public/types.ts index 7169b893a..4acfc442f 100644 --- a/public/types.ts +++ b/public/types.ts @@ -46,6 +46,7 @@ export interface DashboardsInfo { multitenancy_enabled?: boolean; private_tenant_enabled?: boolean; default_tenant: string; + password_validation_error_message: string; } export interface ClientConfigType { From a40f8f08551e4373deb118d618813bbb3c674f16 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 12 Jul 2023 16:31:57 -0400 Subject: [PATCH 17/31] Add release notes for 2.9.0.0 (#1510) Signed-off-by: Craig Perkins Signed-off-by: Sam --- ...dashboards-plugin.release-notes-2.9.0.0.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 release-notes/opensearch-security-dashboards-plugin.release-notes-2.9.0.0.md diff --git a/release-notes/opensearch-security-dashboards-plugin.release-notes-2.9.0.0.md b/release-notes/opensearch-security-dashboards-plugin.release-notes-2.9.0.0.md new file mode 100644 index 000000000..599f49548 --- /dev/null +++ b/release-notes/opensearch-security-dashboards-plugin.release-notes-2.9.0.0.md @@ -0,0 +1,20 @@ +## 2023-07-18 Version 2.9.0.0 + +Compatible with Opensearch-Dashboards 2.9.0 + +### Enhancements + +* Add RESTAPI permissions and ISM control center permissions to dropdown ([#1473](https://github.com/opensearch-project/security-dashboards-plugin/pull/1473)) +* Move security plugin from Plugin to Management section on left navigation menu ([#1495](https://github.com/opensearch-project/security-dashboards-plugin/pull/1495)) +* Filter high level groups and action groups by cluster and index ([#1508](https://github.com/opensearch-project/security-dashboards-plugin/pull/1508)) +* Use password message from /dashboardsinfo ([#1509](https://github.com/opensearch-project/security-dashboards-plugin/pull/1509)) + +### Bug Fixes + +* Switch to new tenant after loading a copied long URL ([#1450](https://github.com/opensearch-project/security-dashboards-plugin/pull/1450)) ([#1477](https://github.com/opensearch-project/security-dashboards-plugin/pull/1477)) +* Fix a bad import path ([#1498](https://github.com/opensearch-project/security-dashboards-plugin/pull/1498)) ([#1499](https://github.com/opensearch-project/security-dashboards-plugin/pull/1499)) + +### Maintenance + +* Use version from package.json for integration tests ([#1465](https://github.com/opensearch-project/security-dashboards-plugin/pull/1465)) +* Update permissions according to backend ([#1480](https://github.com/opensearch-project/security-dashboards-plugin/pull/1480)) ([#1484](https://github.com/opensearch-project/security-dashboards-plugin/pull/1484)) From 5034f93ca9716d10b3b69a5d56cf42be32670cc9 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 19 Jul 2023 09:03:38 -0400 Subject: [PATCH 18/31] Add eslint rule to forbid imports from path containing packages/ from root dir of OSD (#1500) * Add lint rule to forbid imports from packages Signed-off-by: Craig Perkins * Add eslint rule to forbid imports from path containing packages/ from root dir of OSD Signed-off-by: Craig Perkins --------- Signed-off-by: Craig Perkins Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Co-authored-by: Peter Nied Signed-off-by: Sam --- .eslintrc.js | 12 ++++++++++++ server/saved_objects/saved_objects_wrapper.ts | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 03e09f2b0..2b5f80b11 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,18 @@ module.exports = { extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], rules: { // "@osd/eslint/require-license-header": "off" + '@osd/eslint/no-restricted-paths': [ + 'error', + { + basePath: __dirname, + zones: [ + { + target: ['(public|server)/**/*'], + from: ['../../packages/**/*','packages/**/*'] + }, + ] + } + ] }, overrides: [ { diff --git a/server/saved_objects/saved_objects_wrapper.ts b/server/saved_objects/saved_objects_wrapper.ts index 5dae1fc5f..983356adb 100644 --- a/server/saved_objects/saved_objects_wrapper.ts +++ b/server/saved_objects/saved_objects_wrapper.ts @@ -34,7 +34,6 @@ import { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, } from 'opensearch-dashboards/server'; -import { Config } from 'packages/osd-config/target'; import { SecurityPluginConfigType } from '..'; import { OpenSearchDashboardsAuthState } from '../auth/types/authentication_type'; import { From 0a6520680b4b6a91debf1ee5ad464ba13ec1db2e Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 3 Aug 2023 13:49:38 -0400 Subject: [PATCH 19/31] Force resolution of selenium-webdriver to 4.10.0 (#1541) Signed-off-by: Craig Perkins Signed-off-by: Sam --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 220979072..6ae13f2c1 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "gulp-rename": "2.0.0", "jose": "^4.11.2", "saml-idp": "^1.2.1", - "selenium-webdriver": "^4.0.0-alpha.7", "selfsigned": "^2.0.1", "typescript": "4.0.2" }, @@ -36,5 +35,8 @@ "@hapi/cryptiles": "5.0.0", "@hapi/wreck": "^17.1.0", "html-entities": "1.3.1" + }, + "resolutions": { + "selenium-webdriver": "4.10.0" } } \ No newline at end of file From b188209bca1f6f921393411c5dacfe868e430ba4 Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:58:14 -0700 Subject: [PATCH 20/31] Change the version parsing command's regex to handle the case of double digits of minor versions (#1537) Signed-off-by: Ryan Liang Co-authored-by: Craig Perkins Signed-off-by: Sam --- .github/actions/install-dashboards/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/install-dashboards/action.yml b/.github/actions/install-dashboards/action.yml index d314c40f7..eea6eaf63 100644 --- a/.github/actions/install-dashboards/action.yml +++ b/.github/actions/install-dashboards/action.yml @@ -40,15 +40,15 @@ runs: - id: osd-version run: | - echo "::set-output name=osd-version::$(cat package.json | jq '.opensearchDashboards.version' | cut -c 2-4)" - echo "::set-output name=osd-x-version::$(cat package.json | jq '.opensearchDashboards.version' | cut -c 2-3)" + echo "::set-output name=osd-version::$(jq -r '.opensearchDashboards.version | split(".") | .[:2] | join(".")' package.json)" + echo "::set-output name=osd-x-version::$(jq -r '.opensearchDashboards.version | split(".") | .[0]' package.json).x" working-directory: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} shell: bash - id: branch-switch-if-possible continue-on-error: true # Defaults onto main if the branch switch doesn't work if: ${{ steps.osd-version.outputs.osd-version }} - run: git checkout ${{ steps.osd-version.outputs.osd-version }} || git checkout ${{ steps.osd-version.outputs.osd-x-version }}x + run: git checkout ${{ steps.osd-version.outputs.osd-version }} || git checkout ${{ steps.osd-version.outputs.osd-x-version }} working-directory: ./OpenSearch-Dashboards shell: bash From 695fbd52b6e989918149b435c6b5009d12e4a8bb Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 4 Aug 2023 18:50:54 -0400 Subject: [PATCH 21/31] Add release notes for 1.3.12.0 (#1543) Signed-off-by: Craig Perkins Signed-off-by: Sam --- ...h-security-dashboards-plugin.release-notes-1.3.12.0.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 release-notes/opensearch-security-dashboards-plugin.release-notes-1.3.12.0.md diff --git a/release-notes/opensearch-security-dashboards-plugin.release-notes-1.3.12.0.md b/release-notes/opensearch-security-dashboards-plugin.release-notes-1.3.12.0.md new file mode 100644 index 000000000..ac8c68b4f --- /dev/null +++ b/release-notes/opensearch-security-dashboards-plugin.release-notes-1.3.12.0.md @@ -0,0 +1,8 @@ +## 2023-08-10 Version 1.3.12.0 + +Compatible with Opensearch-Dashboards 1.3.12 + +### Bug Fixes + +* Switch to new tenant after loading a copied long URL ([#1450](https://github.com/opensearch-project/security-dashboards-plugin/pull/1450)) +* Add the tenant into the short URL once the short URL is resolved ([#1462](https://github.com/opensearch-project/security-dashboards-plugin/pull/1462)) [#1516](https://github.com/opensearch-project/security-dashboards-plugin/pull/1516) From 772a81f059b41ee817676809d656207414fb85c0 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 10 Aug 2023 17:16:27 +0100 Subject: [PATCH 22/31] Modifying to use query params instead of new endpoints Signed-off-by: Sam --- server/backend/opensearch_security_configuration_plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/backend/opensearch_security_configuration_plugin.ts b/server/backend/opensearch_security_configuration_plugin.ts index 199992d46..5d7436b1e 100644 --- a/server/backend/opensearch_security_configuration_plugin.ts +++ b/server/backend/opensearch_security_configuration_plugin.ts @@ -61,12 +61,12 @@ export default function (Client: any, config: any, components: any) { Client.prototype.opensearch_security.prototype.listInternalAccounts = ca({ url: { - fmt: '/_plugins/_security/api/internalusers/internalaccounts' + fmt: '/_plugins/_security/api/internalusers?filterBy=internal' } }); Client.prototype.opensearch_security.prototype.listServiceAccounts = ca({ url: { - fmt: '/_plugins/_security/api/internalusers/serviceaccounts' + fmt: '/_plugins/_security/api/internalusers?filterBy=service' } }); From a3be5ef687873a31349535b02356997269591f3c Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 11 Aug 2023 00:29:40 +0100 Subject: [PATCH 23/31] yarn lint fixes Signed-off-by: Sam --- public/apps/configuration/app-router.tsx | 2 +- .../panels/service-account-list.tsx | 14 ++++++------- .../apps/configuration/panels/user-list.tsx | 4 ++-- .../utils/internal-user-list-utils.tsx | 20 ++++++++++++++----- ...pensearch_security_configuration_plugin.ts | 8 ++++---- server/routes/index.ts | 9 +++------ 6 files changed, 31 insertions(+), 26 deletions(-) diff --git a/public/apps/configuration/app-router.tsx b/public/apps/configuration/app-router.tsx index 1c457627b..17e3085b4 100644 --- a/public/apps/configuration/app-router.tsx +++ b/public/apps/configuration/app-router.tsx @@ -35,7 +35,7 @@ import { RoleEditMappedUser } from './panels/role-mapping/role-edit-mapped-user' import { RoleView } from './panels/role-view/role-view'; import { TenantList } from './panels/tenant-list/tenant-list'; import { UserList } from './panels/user-list'; -import { ServiceAccountList } from "./panels/service-account-list"; +import { ServiceAccountList } from './panels/service-account-list'; import { Action, ResourceType, RouteItem, SubAction } from './types'; import { buildHashUrl, buildUrl } from './utils/url-builder'; import { CrossPageToast } from './cross-page-toast'; diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx index 86e8afad8..6e1551580 100644 --- a/public/apps/configuration/panels/service-account-list.tsx +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -38,10 +38,7 @@ import { Action, ResourceType } from '../types'; import { EMPTY_FIELD_VALUE } from '../ui-constants'; import { useContextMenuState } from '../utils/context-menu'; import { ExternalLink, tableItemsUIProps, truncatedListView } from '../utils/display-utils'; -import { - getUserList, - InternalUsersListing, -} from '../utils/internal-user-list-utils'; +import { getUserList, InternalUsersListing } from '../utils/internal-user-list-utils'; import { showTableStatusMessage } from '../utils/loading-spinner-utils'; import { buildHashUrl } from '../utils/url-builder'; @@ -178,11 +175,12 @@ export function ServiceAccountList(props: AppDependencies) { - - Here you list the users authenticated via an external authentication system such as LDAP server or Active - Directory. You can map an user to a role from{' '} + + Here you list the users authenticated via an external authentication system such as + LDAP server or Active Directory. You can map an user to a role from{' '} Roles - “Manage mapping” + “Manage mapping” +
diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index 664d8b9ae..605b1e730 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -211,8 +211,8 @@ export function UserList(props: AppDependencies) { - The Security plugin includes an internal account database. Use this database in place of, - or in addition to, an external authentication system such as LDAP server or Active + The Security plugin includes an internal account database. Use this database in place + of, or in addition to, an external authentication system such as LDAP server or Active Directory. You can map an internal account to a role from{' '} Roles . First, click into the detail page of the role. Then, under “Mapped users”, click diff --git a/public/apps/configuration/utils/internal-user-list-utils.tsx b/public/apps/configuration/utils/internal-user-list-utils.tsx index ad0b459f0..02042e928 100644 --- a/public/apps/configuration/utils/internal-user-list-utils.tsx +++ b/public/apps/configuration/utils/internal-user-list-utils.tsx @@ -15,8 +15,12 @@ import { map } from 'lodash'; import { HttpStart } from '../../../../../../src/core/public'; -import { API_ENDPOINT_INTERNALACCOUNTS, API_ENDPOINT_INTERNALUSERS, API_ENDPOINT_SERVICEACCOUNTS } from '../constants'; -import {DataObject, InternalUser, ObjectsMessage, ResourceType} from '../types'; +import { + API_ENDPOINT_INTERNALACCOUNTS, + API_ENDPOINT_INTERNALUSERS, + API_ENDPOINT_SERVICEACCOUNTS, +} from '../constants'; +import { DataObject, InternalUser, ObjectsMessage, ResourceType } from '../types'; import { httpDelete, httpGet } from './request-utils'; import { getResourceUrl } from './resource-utils'; @@ -34,11 +38,14 @@ export function transformUserData(rawData: DataObject): InternalUs export async function requestDeleteUsers(http: HttpStart, users: string[]) { for (const user of users) { - await httpDelete(http, getResourceUrl( API_ENDPOINT_INTERNALUSERS, user)); + await httpDelete(http, getResourceUrl(API_ENDPOINT_INTERNALUSERS, user)); } } -async function getUserListRaw(http: HttpStart, userType: string ): Promise> { +async function getUserListRaw( + http: HttpStart, + userType: string +): Promise> { let ENDPOINT = API_ENDPOINT_INTERNALACCOUNTS; if (userType === ResourceType.serviceAccounts) { ENDPOINT = API_ENDPOINT_SERVICEACCOUNTS; @@ -47,7 +54,10 @@ async function getUserListRaw(http: HttpStart, userType: string ): Promise>(http, ENDPOINT); } -export async function getUserList(http: HttpStart, userType: string ): Promise { +export async function getUserList( + http: HttpStart, + userType: string +): Promise { const rawData = await getUserListRaw(http, userType); return transformUserData(rawData.data); } diff --git a/server/backend/opensearch_security_configuration_plugin.ts b/server/backend/opensearch_security_configuration_plugin.ts index 5d7436b1e..3b87863b8 100644 --- a/server/backend/opensearch_security_configuration_plugin.ts +++ b/server/backend/opensearch_security_configuration_plugin.ts @@ -61,13 +61,13 @@ export default function (Client: any, config: any, components: any) { Client.prototype.opensearch_security.prototype.listInternalAccounts = ca({ url: { - fmt: '/_plugins/_security/api/internalusers?filterBy=internal' - } + fmt: '/_plugins/_security/api/internalusers?filterBy=internal', + }, }); Client.prototype.opensearch_security.prototype.listServiceAccounts = ca({ url: { - fmt: '/_plugins/_security/api/internalusers?filterBy=service' - } + fmt: '/_plugins/_security/api/internalusers?filterBy=service', + }, }); /** diff --git a/server/routes/index.ts b/server/routes/index.ts index 8b6bce2c9..ad3dfbd58 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -249,14 +249,11 @@ export function defineRoutes(router: IRouter) { const client = context.security_plugin.esClient.asScoped(request); let esResp; try { - console.log(request.params.resourceName) - if(request.params.resourceName == ResourceType.serviceAccounts.toLowerCase()){ + if (request.params.resourceName === ResourceType.serviceAccounts.toLowerCase()) { esResp = await client.callAsCurrentUser('opensearch_security.listServiceAccounts'); - } - else if(request.params.resourceName == 'internalaccounts'){ + } else if (request.params.resourceName === 'internalaccounts') { esResp = await client.callAsCurrentUser('opensearch_security.listInternalAccounts'); - } - else{ + } else { esResp = await client.callAsCurrentUser('opensearch_security.listResource', { resourceName: request.params.resourceName, }); From f8f82f8a81e4cd4be9c1fcc0ee0069abc8af4852 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 18 Aug 2023 19:21:47 +0100 Subject: [PATCH 24/31] service-account-list.test.tsx added. Minor change resource name change Signed-off-by: Sam --- public/apps/configuration/app-router.tsx | 2 +- .../panels/test/service-account-list.test.tsx | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 public/apps/configuration/panels/test/service-account-list.test.tsx diff --git a/public/apps/configuration/app-router.tsx b/public/apps/configuration/app-router.tsx index 17e3085b4..a10cec08e 100644 --- a/public/apps/configuration/app-router.tsx +++ b/public/apps/configuration/app-router.tsx @@ -52,7 +52,7 @@ const ROUTE_MAP: { [key: string]: RouteItem } = { href: buildUrl(ResourceType.roles), }, [ResourceType.users]: { - name: 'Internal Accounts', + name: 'Internal users', href: buildUrl(ResourceType.users), }, [ResourceType.serviceAccounts]: { diff --git a/public/apps/configuration/panels/test/service-account-list.test.tsx b/public/apps/configuration/panels/test/service-account-list.test.tsx new file mode 100644 index 000000000..2ffe595e8 --- /dev/null +++ b/public/apps/configuration/panels/test/service-account-list.test.tsx @@ -0,0 +1,173 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { EuiBadge, EuiText, EuiInMemoryTable } from '@elastic/eui'; +import { shallow } from 'enzyme'; +import React from 'react'; +import { EMPTY_FIELD_VALUE } from '../../ui-constants'; +import { getUserList, InternalUsersListing } from '../../utils/internal-user-list-utils'; +import { dictView, getColumns, ServiceAccountList } from '../service-account-list'; + +jest.mock('../../utils/internal-user-list-utils'); +jest.mock('../../../../utils/auth-info-utils', () => ({ + getAuthInfo: jest.fn().mockReturnValue({ user_name: 'user' }), +})); +jest.mock('../../utils/context-menu', () => ({ + useContextMenuState: jest + .fn() + .mockImplementation((buttonText, buttonProps, children) => [children, jest.fn()]), +})); + +import { getAuthInfo } from '../../../../utils/auth-info-utils'; +import { buildHashUrl } from '../../utils/url-builder'; +import { ResourceType, Action } from '../../types'; + +describe('User list', () => { + describe('dictView', () => { + it('- empty', () => { + const result = dictView({}); + + expect(result).toEqual(EMPTY_FIELD_VALUE); + }); + + it('dictView - non-empty', () => { + const attr1 = 'attr1'; + const attr2 = 'attr2'; + const value1 = 'value1'; + const value2 = 'value2'; + const result = shallow(dictView({ [attr1]: value1, [attr2]: value2 })); + + expect(result.find(EuiText).at(0).prop('children')).toEqual([attr1, ': ', `"${value1}"`]); + expect(result.find(EuiText).at(1).prop('children')).toEqual([attr2, ': ', `"${value2}"`]); + }); + }); + + describe('getColumns', () => { + it('current user', () => { + const columns = getColumns('user1'); + const usernameRenderer = columns[0].render as (usename: string) => JSX.Element; + const Container = (props: { username: string }) => usernameRenderer(props.username); + const result = shallow(); + + expect(result.find(EuiBadge).length).toBe(1); + }); + + it('not current user', () => { + const columns = getColumns('user1'); + const usernameRenderer = columns[0].render as (usename: string) => JSX.Element; + const Container = (props: { username: string }) => usernameRenderer(props.username); + const result = shallow(); + + expect(result.find(EuiBadge).length).toBe(0); + }); + }); + + describe('ServiceAccountList', () => { + const mockCoreStart = { + http: 1, + }; + const setState = jest.fn(); + jest.spyOn(React, 'useState').mockImplementation((initValue) => [initValue, setState]); + + it('render empty', () => { + const component = shallow( + + ); + + expect(component.find(EuiInMemoryTable).prop('items')).toEqual([]); + }); + + it('fetch data', () => { + jest.spyOn(React, 'useEffect').mockImplementationOnce((f) => f()); + shallow( + + ); + + expect(getUserList).toBeCalled(); + expect(getAuthInfo).toBeCalled(); + }); + + it('fetch data error', () => { + jest.spyOn(React, 'useEffect').mockImplementationOnce((f) => f()); + getUserList.mockImplementationOnce(() => { + throw new Error(); + }); + // Hide the error message + jest.spyOn(console, 'log').mockImplementationOnce(() => {}); + shallow( + + ); + + // Expect error flag set to true + expect(setState).toBeCalledWith(true); + }); + }); + + describe('Action menu click', () => { + const mockCoreStart = { + http: { + basePath: { + serverBasePath: '', + }, + }, + }; + let component; + const mockUserListingData: InternalUsersListing = { + username: 'user_1', + attributes: { service: 'true' }, + backend_roles: ['backend_role1'], + }; + beforeEach(() => { + jest.spyOn(React, 'useState').mockImplementation(() => [[mockUserListingData], jest.fn()]); + component = shallow( + + ); + }); + + it('Edit click', () => { + component.find('[data-test-subj="edit"]').simulate('click'); + expect(window.location.hash).toBe( + buildHashUrl(ResourceType.users, Action.edit, mockUserListingData.username) + ); + }); + + it('Duplicate click', () => { + component.find('[data-test-subj="duplicate"]').simulate('click'); + expect(window.location.hash).toBe( + buildHashUrl(ResourceType.users, Action.duplicate, mockUserListingData.username) + ); + }); + }); +}); From 952c883598533759be4f7323ed753f7491fca524 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 18 Aug 2023 19:25:05 +0100 Subject: [PATCH 25/31] reverting accidental change to developer guide Signed-off-by: Sam --- DEVELOPER_GUIDE.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 2331b3a20..d82fdd5a4 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -92,17 +92,9 @@ Next, go to the base directory (`cd ../..`) and run `yarn osd bootstrap` to inst From the base directory, run `yarn start`. This should start dashboard UI successfully. `Cmd+click` the url in the console output (It should look something like `http://0:5601/omf`). Once the page loads, you should be able to log in with user `admin` and password `admin`. -## Testing +## Integration Tests -The security-dashboards-plugin project uses Jest, Cypress and Selenium and makes use of the [OpenSearch Dashboards Functional Test]( https://github.com/opensearch-project/opensearch-dashboards-functional-test) project. - -Make sure you have the OpenSearch and OpenSearch Dashboards running with the Security Plugin and that you can log in to it using a web browser. - -Clone [OpenSearch Dashboards Functional Test]( https://github.com/opensearch-project/opensearch-dashboards-functional-test) in your local machine and follow the instructions in its DEVELOPER_GUIDE.md - -### Integration Tests - -To run selenium based integration tests, download and export the firefox web-driver to your PATH. Also, run `node scripts/build_opensearch_dashboards_platform_plugins.js` or `yarn start` before running the tests. This is essential to generate the bundles. +To run selenium based integration tests, download and export the firefox web-driver to your PATH. Also, run `node scripts/build_opensearch_dashboards_platform_plugins.js` or `yarn start` before running the tests. This is essential to generate the bundles. ## Submitting Changes From bbcdf2902ab99fe88521a8602c2d1b8b14b9ab7c Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 19 Aug 2023 02:03:24 +0100 Subject: [PATCH 26/31] hardcoded collor CI error Signed-off-by: Sam --- public/apps/configuration/_index.scss | 2 +- public/apps/configuration/panels/audit-logging/_index.scss | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/public/apps/configuration/_index.scss b/public/apps/configuration/_index.scss index 4914998cb..f3b245b36 100644 --- a/public/apps/configuration/_index.scss +++ b/public/apps/configuration/_index.scss @@ -14,7 +14,7 @@ */ .panel-header-count { - color: #687078; + color: $euiTextSubduedColor; font-weight: normal; } diff --git a/public/apps/configuration/panels/audit-logging/_index.scss b/public/apps/configuration/panels/audit-logging/_index.scss index 22d30dbc2..eb20f361b 100644 --- a/public/apps/configuration/panels/audit-logging/_index.scss +++ b/public/apps/configuration/panels/audit-logging/_index.scss @@ -5,7 +5,3 @@ .form-row { max-width: 800px; } - -pre code { - color: #666; -} From d8de3d5467335d46363bbe11d1cec0c889da3644 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Fri, 1 Sep 2023 09:43:45 -0400 Subject: [PATCH 27/31] Update public/apps/configuration/panels/service-account-list.tsx Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- public/apps/configuration/panels/service-account-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx index 6e1551580..599774899 100644 --- a/public/apps/configuration/panels/service-account-list.tsx +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -208,7 +208,7 @@ export function ServiceAccountList(props: AppDependencies) { // @ts-ignore selection={{ onSelectionChange: setSelection }} sorting - error={errorFlag ? 'Load data failed, please check console log for more detail.' : ''} + error={errorFlag ? 'Load data failed, please check the console log for more details.' : ''} message={showTableStatusMessage(loading, userData)} /> From 10551f7d25db0e7dbe73367f471dbdecf9284a2c Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 7 Sep 2023 14:53:41 +0100 Subject: [PATCH 28/31] Service account description Signed-off-by: Sam --- public/apps/configuration/panels/service-account-list.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx index 599774899..608f6c247 100644 --- a/public/apps/configuration/panels/service-account-list.tsx +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -176,8 +176,8 @@ export function ServiceAccountList(props: AppDependencies) { - Here you list the users authenticated via an external authentication system such as - LDAP server or Active Directory. You can map an user to a role from{' '} + Here you have a list of special accounts that represent services like extensions, + plugins or other third party applications. You can map an account to a role from{' '} Roles “Manage mapping” @@ -208,7 +208,9 @@ export function ServiceAccountList(props: AppDependencies) { // @ts-ignore selection={{ onSelectionChange: setSelection }} sorting - error={errorFlag ? 'Load data failed, please check the console log for more details.' : ''} + error={ + errorFlag ? 'Load data failed, please check the console log for more details.' : '' + } message={showTableStatusMessage(loading, userData)} /> From 6e3c85b3f01e1c6c69316a71b85547e2272545c7 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 13 Oct 2023 02:24:45 +0100 Subject: [PATCH 29/31] space removal Signed-off-by: Sam --- public/apps/configuration/panels/service-account-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx index 608f6c247..61c21a9e5 100644 --- a/public/apps/configuration/panels/service-account-list.tsx +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -177,7 +177,7 @@ export function ServiceAccountList(props: AppDependencies) { Here you have a list of special accounts that represent services like extensions, - plugins or other third party applications. You can map an account to a role from{' '} + plugins or other third party applications. You can map an account to a role from Roles “Manage mapping” From 388328d11cc0de53b84a6a05c70efce901cac41e Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 7 Nov 2023 15:26:40 +0000 Subject: [PATCH 30/31] "Internal User" Signed-off-by: Sam --- public/apps/configuration/panels/user-list.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index 605b1e730..7b49be68f 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -195,7 +195,7 @@ export function UserList(props: AppDependencies) { <> -

Internal accounts

+

Internal users

@@ -203,7 +203,7 @@ export function UserList(props: AppDependencies) {

- Internal accounts + Internal users {' '} ({Query.execute(query || '', userData).length}) @@ -211,9 +211,9 @@ export function UserList(props: AppDependencies) {

- The Security plugin includes an internal account database. Use this database in place + The Security plugin includes an internal user database. Use this database in place of, or in addition to, an external authentication system such as LDAP server or Active - Directory. You can map an internal account to a role from{' '} + Directory. You can map an user account to a role from{' '} Roles . First, click into the detail page of the role. Then, under “Mapped users”, click “Manage mapping” @@ -224,7 +224,7 @@ export function UserList(props: AppDependencies) { {actionsMenu} - Create internal account + Create user account From cf231bda8a8aad188d91819fec3d08a3bf66c33a Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 9 Nov 2023 13:21:55 +0000 Subject: [PATCH 31/31] Lint Signed-off-by: Sam --- public/apps/configuration/panels/user-list.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index 7b49be68f..eb369f7ad 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -211,8 +211,8 @@ export function UserList(props: AppDependencies) { - The Security plugin includes an internal user database. Use this database in place - of, or in addition to, an external authentication system such as LDAP server or Active + The Security plugin includes an internal user database. Use this database in place of, + or in addition to, an external authentication system such as LDAP server or Active Directory. You can map an user account to a role from{' '} Roles . First, click into the detail page of the role. Then, under “Mapped users”, click