diff --git a/common/index.ts b/common/index.ts index 44227240e..028d9d376 100644 --- a/common/index.ts +++ b/common/index.ts @@ -42,6 +42,13 @@ export const AUTH_HEADER_NAME = 'authorization'; export const AUTH_GRANT_TYPE = 'authorization_code'; export const AUTH_RESPONSE_TYPE = 'code'; +export const GLOBAL_TENANT_SYMBOL = ''; +export const PRIVATE_TENANT_SYMBOL = '__user__'; +export const DEFAULT_TENANT = 'default'; +export const GLOBAL_TENANT_RENDERING_TEXT = 'Global'; +export const PRIVATE_TENANT_RENDERING_TEXT = 'Private'; +export const globalTenantName = 'global_tenant'; + export enum AuthType { BASIC = 'basicauth', OPEN_ID = 'openid', @@ -61,3 +68,15 @@ export function isValidResourceName(resourceName: string): boolean { const exp = new RegExp('[\\p{C}%]', 'u'); return !exp.test(resourceName) && resourceName.length > 0; } + +export function isPrivateTenant(selectedTenant: string | null) { + return selectedTenant !== null && selectedTenant === PRIVATE_TENANT_SYMBOL; +} + +export function isRenderingPrivateTenant(selectedTenant: string | null) { + return selectedTenant !== null && selectedTenant?.startsWith(PRIVATE_TENANT_SYMBOL); +} + +export function isGlobalTenant(selectedTenant: string | null) { + return selectedTenant !== null && selectedTenant === GLOBAL_TENANT_SYMBOL; +} diff --git a/public/apps/configuration/utils/tenant-utils.tsx b/public/apps/configuration/utils/tenant-utils.tsx index 8c50ffcda..7abf132f2 100644 --- a/public/apps/configuration/utils/tenant-utils.tsx +++ b/public/apps/configuration/utils/tenant-utils.tsx @@ -18,31 +18,34 @@ import { map } from 'lodash'; import React from 'react'; import { i18n } from '@osd/i18n'; import { - API_ENDPOINT_TENANTS, API_ENDPOINT_MULTITENANCY, + API_ENDPOINT_TENANTS, RoleViewTenantInvalidText, + TENANT_READ_PERMISSION, + TENANT_WRITE_PERMISSION, } from '../constants'; import { DataObject, ObjectsMessage, - Tenant, - TenantUpdate, - TenantSelect, - RoleTenantPermissionView, + RoleTenantPermission, RoleTenantPermissionDetail, + RoleTenantPermissionView, + Tenant, TenantPermissionType, - RoleTenantPermission, + TenantSelect, + TenantUpdate, } from '../types'; -import { TENANT_READ_PERMISSION, TENANT_WRITE_PERMISSION } from '../constants'; import { httpDelete, httpGet, httpPost } from './request-utils'; import { getResourceUrl } from './resource-utils'; - -export const globalTenantName = 'global_tenant'; -export const GLOBAL_TENANT_SYMBOL = ''; -export const PRIVATE_TENANT_SYMBOL = '__user__'; -export const DEFAULT_TENANT = 'default'; -export const GLOBAL_TENANT_RENDERING_TEXT = 'Global'; -export const PRIVATE_TENANT_RENDERING_TEXT = 'Private'; +import { + DEFAULT_TENANT, + GLOBAL_TENANT_RENDERING_TEXT, + GLOBAL_TENANT_SYMBOL, + globalTenantName, + isGlobalTenant, + isRenderingPrivateTenant, + PRIVATE_TENANT_RENDERING_TEXT, +} from '../../../../common'; export const GLOBAL_USER_DICT: { [key: string]: string } = { Label: 'Global', @@ -179,16 +182,31 @@ export function transformRoleTenantPermissions( })); } -export function isPrivateTenant(selectedTenant: string | null) { - return selectedTenant !== null && selectedTenant === PRIVATE_TENANT_SYMBOL; -} - -export function isRenderingPrivateTenant(selectedTenant: string | null) { - return selectedTenant !== null && selectedTenant?.startsWith(PRIVATE_TENANT_SYMBOL); -} - -export function isGlobalTenant(selectedTenant: string | null) { - return selectedTenant !== null && selectedTenant === GLOBAL_TENANT_SYMBOL; +export function getNamespacesToRegister(accountInfo: any) { + const tenants = accountInfo.tenants || {}; + const availableTenantNames = Object.keys(tenants!); + const namespacesToRegister = availableTenantNames.map((tenant) => { + if (tenant === globalTenantName) { + return { + id: GLOBAL_USER_DICT.Value, + name: GLOBAL_USER_DICT.Label, + }; + } else if (tenant === accountInfo.user_name) { + return { + id: `${PRIVATE_USER_DICT.Value}${accountInfo.user_name}`, + name: PRIVATE_USER_DICT.Label, + }; + } + return { + id: tenant, + name: tenant, + }; + }); + namespacesToRegister.push({ + id: DEFAULT_TENANT, + name: DEFAULT_TENANT, + }); + return namespacesToRegister; } export const tenantColumn = { diff --git a/public/apps/configuration/utils/test/tenant-utils.test.tsx b/public/apps/configuration/utils/test/tenant-utils.test.tsx index c079026b5..4d5c773d1 100644 --- a/public/apps/configuration/utils/test/tenant-utils.test.tsx +++ b/public/apps/configuration/utils/test/tenant-utils.test.tsx @@ -20,11 +20,11 @@ import { resolveTenantName, RESOLVED_GLOBAL_TENANT, RESOLVED_PRIVATE_TENANT, - globalTenantName, formatTenantName, transformRoleTenantPermissionData, getTenantPermissionType, transformRoleTenantPermissions, + getNamespacesToRegister, } from '../tenant-utils'; import { RoleViewTenantInvalidText, @@ -32,6 +32,7 @@ import { TENANT_WRITE_PERMISSION, } from '../../constants'; import { TenantPermissionType } from '../../types'; +import { globalTenantName } from '../../../../../common'; describe('Tenant list utils', () => { const expectedGlobalTenantListing = { @@ -282,4 +283,63 @@ describe('Tenant list utils', () => { expect(result[0]).toMatchObject(expectedRoleTenantPermissionView); }); }); + + describe('get list of namespaces to register', () => { + it('resolves to list of namespaces with a custom tenant', () => { + const authInfo = { + user_name: 'user1', + tenants: { + global_tenant: true, + user1_tenant: true, + user1: true, + }, + }; + const expectedNamespaces = [ + { + id: GLOBAL_USER_DICT.Value, + name: GLOBAL_USER_DICT.Label, + }, + { + id: 'user1_tenant', + name: 'user1_tenant', + }, + { + id: `${PRIVATE_USER_DICT.Value}user1`, + name: PRIVATE_USER_DICT.Label, + }, + { + id: 'default', + name: 'default', + }, + ]; + const result = getNamespacesToRegister(authInfo); + expect(result).toMatchObject(expectedNamespaces); + }); + + it('resolves to list of namespaces without a custom tenant', () => { + const authInfo = { + user_name: 'user1', + tenants: { + global_tenant: true, + user1: true, + }, + }; + const expectedNamespaces = [ + { + id: GLOBAL_USER_DICT.Value, + name: GLOBAL_USER_DICT.Label, + }, + { + id: `${PRIVATE_USER_DICT.Value}user1`, + name: PRIVATE_USER_DICT.Label, + }, + { + id: 'default', + name: 'default', + }, + ]; + const result = getNamespacesToRegister(authInfo); + expect(result).toMatchObject(expectedNamespaces); + }); + }); }); diff --git a/public/plugin.ts b/public/plugin.ts index 1020bb1aa..4fa66aaf4 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -46,7 +46,7 @@ import { } from './types'; import { addTenantToShareURL } from './services/shared-link'; import { interceptError } from './utils/logout-utils'; -import { tenantColumn } from './apps/configuration/utils/tenant-utils'; +import { tenantColumn, getNamespacesToRegister } from './apps/configuration/utils/tenant-utils'; async function hasApiPermission(core: CoreSetup): Promise { try { @@ -157,6 +157,13 @@ export class SecurityPlugin deps.savedObjectsManagement.columns.register( (tenantColumn as unknown) as SavedObjectsManagementColumn ); + if (!!accountInfo) { + const namespacesToRegister = getNamespacesToRegister(accountInfo); + deps.savedObjectsManagement.namespaces.registerAlias('Tenant'); + namespacesToRegister.forEach((ns) => { + deps.savedObjectsManagement.namespaces.register(ns as SavedObjectsManagementNamespace); + }); + } } // Return methods that should be available to other plugins diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 7c7c14154..b1ea1a208 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -31,7 +31,7 @@ import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; import { resolveTenant, isValidTenant } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; -import { GLOBAL_TENANT_SYMBOL } from '../../../public/apps/configuration/utils/tenant-utils'; +import { GLOBAL_TENANT_SYMBOL } from '../../../common'; export interface IAuthenticationType { type: string; diff --git a/server/multitenancy/tenant_resolver.ts b/server/multitenancy/tenant_resolver.ts index 2fa735013..9a2ad989d 100755 --- a/server/multitenancy/tenant_resolver.ts +++ b/server/multitenancy/tenant_resolver.ts @@ -17,10 +17,7 @@ import { isEmpty, findKey, cloneDeep } from 'lodash'; import { OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { SecuritySessionCookie } from '../session/security_cookie'; import { SecurityPluginConfigType } from '..'; -import { - GLOBAL_TENANT_SYMBOL, - PRIVATE_TENANT_SYMBOL, -} from '../../public/apps/configuration/utils/tenant-utils'; +import { GLOBAL_TENANT_SYMBOL, PRIVATE_TENANT_SYMBOL } from '../../common'; export const PRIVATE_TENANTS: string[] = [PRIVATE_TENANT_SYMBOL, 'private']; export const GLOBAL_TENANTS: string[] = ['global', GLOBAL_TENANT_SYMBOL]; diff --git a/server/saved_objects/saved_objects_wrapper.ts b/server/saved_objects/saved_objects_wrapper.ts index 0cf767ebe..5dae1fc5f 100644 --- a/server/saved_objects/saved_objects_wrapper.ts +++ b/server/saved_objects/saved_objects_wrapper.ts @@ -36,14 +36,14 @@ import { } from 'opensearch-dashboards/server'; import { Config } from 'packages/osd-config/target'; import { SecurityPluginConfigType } from '..'; +import { OpenSearchDashboardsAuthState } from '../auth/types/authentication_type'; import { DEFAULT_TENANT, - globalTenantName, GLOBAL_TENANT_SYMBOL, + globalTenantName, isPrivateTenant, PRIVATE_TENANT_SYMBOL, -} from '../../public/apps/configuration/utils/tenant-utils'; -import { OpenSearchDashboardsAuthState } from '../auth/types/authentication_type'; +} from '../../common'; export class SecuritySavedObjectsClientWrapper { public httpStart?: HttpServiceStart; @@ -104,24 +104,32 @@ export class SecuritySavedObjectsClientWrapper { availableTenantNames.splice(index, 1); } } - const typeToNamespacesMap: any = {}; if (isPrivateTenant(selectedTenant!)) { namespaceValue = selectedTenant! + username; } - const searchTypes = Array.isArray(options.type) ? options.type : [options.type]; - searchTypes.forEach((t) => { - if ('namespaces' in options) { - typeToNamespacesMap[t] = options.namespaces; - } else { - typeToNamespacesMap[t] = availableTenantNames; + if (!!options.namespaces) { + const namespacesToInclude = Array.isArray(options.namespaces) + ? options.namespaces + : [options.namespaces]; + const typeToNamespacesMap: any = {}; + const searchTypes = Array.isArray(options.type) ? options.type : [options.type]; + searchTypes.forEach((t) => { + typeToNamespacesMap[t] = namespacesToInclude; + }); + if (searchTypes.includes('config')) { + if (namespacesToInclude.includes(namespaceValue)) { + typeToNamespacesMap.config = [namespaceValue]; + } else { + delete typeToNamespacesMap.config; + } } - }); - if ('config' in typeToNamespacesMap) { - typeToNamespacesMap.config = [namespaceValue]; + + options.typeToNamespacesMap = new Map(Object.entries(typeToNamespacesMap)); + options.type = ''; + options.namespaces = []; + } else { + options.namespaces = [namespaceValue]; } - options.typeToNamespacesMap = new Map(Object.entries(typeToNamespacesMap)); - options.type = ''; - options.namespaces = []; return await wrapperOptions.client.find(options); };