diff --git a/public/apps/account/account-app.tsx b/public/apps/account/account-app.tsx index 2379275e2..d6b4af397 100644 --- a/public/apps/account/account-app.tsx +++ b/public/apps/account/account-app.tsx @@ -37,7 +37,6 @@ function tenantSpecifiedInUrl() { export async function setupTopNavButton(coreStart: CoreStart, config: ClientConfigType) { const accountInfo = (await fetchAccountInfoSafe(coreStart.http))?.data; - const currentTenant = await fetchCurrentTenant(coreStart.http); if (accountInfo) { // Missing role error if (accountInfo.roles.length === 0 && !window.location.href.includes(CUSTOM_ERROR_PAGE_URI)) { @@ -46,11 +45,12 @@ export async function setupTopNavButton(coreStart: CoreStart, config: ClientConf } let tenant: string | undefined; - if (config.multitenancy.enable_aggregation_view) { - tenant = currentTenant; + if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) { + tenant = await fetchCurrentTenant(coreStart.http); } else { tenant = accountInfo.user_requested_tenant; } + let shouldShowTenantPopup = true; if (tenantSpecifiedInUrl() || getShouldShowTenantPopup() === false) { diff --git a/public/apps/account/tenant-switch-panel.tsx b/public/apps/account/tenant-switch-panel.tsx index 6f5c07f88..d825dc20f 100755 --- a/public/apps/account/tenant-switch-panel.tsx +++ b/public/apps/account/tenant-switch-panel.tsx @@ -91,9 +91,11 @@ export function TenantSwitchPanel(props: TenantSwitchPanelProps) { const currentUserName = accountInfo.data.user_name; setUsername(currentUserName); - // @ts-ignore let currentRawTenantName: string | undefined; - if (props.config.multitenancy.enable_aggregation_view) { + if ( + props.config.multitenancy.enabled && + props.config.multitenancy.enable_aggregation_view + ) { currentRawTenantName = props.tenant; } else { currentRawTenantName = accountInfo.data.user_requested_tenant; @@ -106,7 +108,7 @@ export function TenantSwitchPanel(props: TenantSwitchPanelProps) { }; fetchData(); - }, [props.coreStart.http, props.tenant, props.config.multitenancy.enable_aggregation_view]); + }, [props.coreStart.http, props.tenant, props.config.multitenancy]); // Custom tenant super select related. const onCustomTenantChange = (selectedOption: EuiComboBoxOptionOption[]) => { diff --git a/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx b/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx index 9c6068d35..e80d0d0e1 100644 --- a/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx +++ b/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx @@ -49,7 +49,7 @@ export function ClusterPermissionPanel(props: { options={optionUniverse} selectedOptions={state} onChange={setState} - id="cluster-permission-box" + id="roles-cluster-permission-box" /> {/* TODO: 'Browse and select' button with a pop-up modal for selection */} diff --git a/public/apps/configuration/panels/role-edit/index-permission-panel.tsx b/public/apps/configuration/panels/role-edit/index-permission-panel.tsx index e9272597c..75e2856e4 100644 --- a/public/apps/configuration/panels/role-edit/index-permission-panel.tsx +++ b/public/apps/configuration/panels/role-edit/index-permission-panel.tsx @@ -153,7 +153,7 @@ export function IndexPermissionRow(props: { options={props.permisionOptionsSet} selectedOptions={props.value} onChange={props.onChangeHandler} - id="index-permission-box" + id="roles-index-permission-box" /> {/* TODO: 'Browse and select' button with a pop-up modal for selection */} diff --git a/public/apps/configuration/panels/role-edit/tenant-panel.tsx b/public/apps/configuration/panels/role-edit/tenant-panel.tsx index 78a0dbd1c..2cce9e546 100644 --- a/public/apps/configuration/panels/role-edit/tenant-panel.tsx +++ b/public/apps/configuration/panels/role-edit/tenant-panel.tsx @@ -91,7 +91,7 @@ function generateTenantPermissionPanels( onChange={onValueChangeHandler('tenantPatterns')} onCreateOption={onCreateOptionHandler('tenantPatterns')} options={permisionOptionsSet} - id="tenant-permission-box" + id="roles-tenant-permission-box" /> diff --git a/public/apps/configuration/utils/tenant-utils.tsx b/public/apps/configuration/utils/tenant-utils.tsx index 080a93f1c..590fb22a7 100644 --- a/public/apps/configuration/utils/tenant-utils.tsx +++ b/public/apps/configuration/utils/tenant-utils.tsx @@ -36,9 +36,12 @@ import { httpDelete, httpGet, httpPost } from './request-utils'; import { getResourceUrl } from './resource-utils'; export const globalTenantName = 'global_tenant'; +export const GLOBAL_TENANT = ''; +export const PRIVATE_TENANT = '__user__'; +export const DEFAULT_TENANT = 'default'; export const GLOBAL_USER_DICT: { [key: string]: string } = { Label: 'Global', - Value: '', + Value: GLOBAL_TENANT, Description: 'Everyone can see it', }; @@ -62,10 +65,10 @@ export function transformTenantData( ): Tenant[] { // @ts-ignore const tenantList: Tenant[] = map(rawTenantData, (v: Tenant, k?: string) => ({ - tenant: k === globalTenantName ? GLOBAL_USER_DICT.Label : k || '', + tenant: k === globalTenantName ? GLOBAL_USER_DICT.Label : k || GLOBAL_TENANT, reserved: v.reserved, description: k === globalTenantName ? GLOBAL_USER_DICT.Description : v.description, - tenantValue: k === globalTenantName ? GLOBAL_USER_DICT.Value : k || '', + tenantValue: k === globalTenantName ? GLOBAL_USER_DICT.Value : k || GLOBAL_TENANT, })); if (isPrivateEnabled) { // Insert Private Tenant in List @@ -170,3 +173,11 @@ export function transformRoleTenantPermissions( permissionType: getTenantPermissionType(tenantPermission.allowed_actions), })); } + +export function isPrivateTenant(selectedTenant: string | null) { + return selectedTenant !== null && selectedTenant?.startsWith(PRIVATE_TENANT); +} + +export function isGlobalTenant(selectedTenant: string | null) { + return selectedTenant !== null && selectedTenant === GLOBAL_TENANT; +} diff --git a/public/plugin.tsx b/public/plugin.tsx index ca1d47388..89f837179 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -14,12 +14,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { - SavedObjectsManagementColumn, - SavedObjectsManagementRecord, -} from 'src/plugins/saved_objects_management/public'; -import { EuiTableFieldDataColumnType } from '@elastic/eui'; -import { string } from 'joi'; +import { SavedObjectsManagementColumn } from 'src/plugins/saved_objects_management/public'; import React from 'react'; import { i18n } from '@osd/i18n'; import { @@ -53,6 +48,7 @@ import { } from './types'; import { addTenantToShareURL } from './services/shared-link'; import { interceptError } from './utils/logout-utils'; +import { isGlobalTenant, isPrivateTenant } from './apps/configuration/utils/tenant-utils'; async function hasApiPermission(core: CoreSetup): Promise { try { @@ -73,8 +69,6 @@ const APP_ID_OPENSEARCH_DASHBOARDS = 'kibana'; const APP_LIST_FOR_READONLY_ROLE = [APP_ID_HOME, APP_ID_DASHBOARDS, APP_ID_OPENSEARCH_DASHBOARDS]; const GLOBAL_TENANT_RENDERING_TEXT = 'Global'; const PRIVATE_TENANT_RENDERING_TEXT = 'Private'; -const GLOBAL_TENANT = ''; -const PRIVATE_TENANT = '__user__'; export class SecurityPlugin implements @@ -161,7 +155,7 @@ export class SecurityPlugin }) ); - if (config.multitenancy.enable_aggregation_view) { + if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) { deps.savedObjectsManagement.columns.register(({ id: 'tenant_column', euiColumn: { @@ -169,7 +163,7 @@ export class SecurityPlugin name:
Tenant
, dataType: 'string', render: (value: any[][]) => { - let text = value[0][0]; + let text = value.flat()[0]; if (isGlobalTenant(text)) { text = GLOBAL_TENANT_RENDERING_TEXT; } else if (isPrivateTenant(text)) { @@ -204,21 +198,8 @@ export class SecurityPlugin if (config.multitenancy.enabled) { addTenantToShareURL(core); } - - if (config.multitenancy.enable_aggregation_view) { - const columns = deps.savedObjectsManagement.columns.getAll(); - } - return {}; } public stop() {} } - -function isPrivateTenant(selectedTenant: string) { - return selectedTenant.startsWith('__user__'); -} - -function isGlobalTenant(selectedTenant: string) { - return selectedTenant === null || selectedTenant === GLOBAL_TENANT; -} diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 7234dcb5e..f566c112b 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -26,7 +26,6 @@ import { IOpenSearchDashboardsResponse, AuthResult, } from 'opensearch-dashboards/server'; -import { any } from 'joi'; import { SecurityPluginConfigType } from '../..'; import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; @@ -36,6 +35,7 @@ import { isValidTenant, } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; +import { GLOBAL_TENANT } from '../../../public/apps/configuration/utils/tenant-utils'; export interface IAuthenticationType { type: string; @@ -102,7 +102,6 @@ export abstract class AuthenticationType implements IAuthenticationType { } const authState: OpenSearchDashboardsAuthState = {}; - const globalTenant = ''; // if browser request, auth logic is: // 1. check if request includes auth header or paramter(e.g. jwt in url params) is present, if so, authenticate with auth header. @@ -184,9 +183,9 @@ export abstract class AuthenticationType implements IAuthenticationType { authState.selectedTenant = tenant; // set tenant in header - if (this.config.multitenancy.enable_aggregation_view) { + if (this.config.multitenancy.enabled && this.config.multitenancy.enable_aggregation_view) { // Store all saved objects in a single kibana index. - Object.assign(authHeaders, { securitytenant: globalTenant }); + Object.assign(authHeaders, { securitytenant: GLOBAL_TENANT }); } else { Object.assign(authHeaders, { securitytenant: tenant }); } @@ -245,7 +244,7 @@ export abstract class AuthenticationType implements IAuthenticationType { if (!authInfo) { try { authInfo = await this.securityClient.authinfo(request, authHeader); - } catch (error) { + } catch (error: any) { throw new UnauthenticatedError(error); } } diff --git a/server/plugin.ts b/server/plugin.ts index e5fa06fe9..b7c9e0ce8 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -15,7 +15,6 @@ import { first } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import _ from 'lodash'; import { PluginInitializerContext, CoreSetup, @@ -134,9 +133,9 @@ export class SecurityPlugin implements Plugin(); const config = await config$.pipe(first()).toPromise(); + + this.savedObjectClientWrapper.httpStart = core.http; + this.savedObjectClientWrapper.config = config; + if (config.multitenancy?.enabled) { const globalConfig$: Observable = this.initializerContext.config.legacy .globalConfig$; diff --git a/server/saved_objects/saved_objects_wrapper.ts b/server/saved_objects/saved_objects_wrapper.ts index 53c4d0dc6..fb80175c1 100644 --- a/server/saved_objects/saved_objects_wrapper.ts +++ b/server/saved_objects/saved_objects_wrapper.ts @@ -34,10 +34,19 @@ import { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, } from 'opensearch-dashboards/server'; +import { Config } from 'packages/osd-config/target'; +import { SecurityPluginConfigType } from '..'; +import { + DEFAULT_TENANT, + GLOBAL_TENANT, + isPrivateTenant, + PRIVATE_TENANT, +} from '../../public/apps/configuration/utils/tenant-utils'; import { OpenSearchDashboardsAuthState } from '../auth/types/authentication_type'; export class SecuritySavedObjectsClientWrapper { public httpStart?: HttpServiceStart; + public config?: SecurityPluginConfigType; constructor() {} @@ -48,9 +57,8 @@ export class SecuritySavedObjectsClientWrapper { const selectedTenant = state.selectedTenant; const username = state.authInfo?.user_name; - const globalTenant = ''; - const defaultTenant = 'default'; - const privateTenant = '__user__'; + const isGlobalEnabled = this.config!.multitenancy.tenants.enable_global; + const isPrivateEnabled = this.config!.multitenancy.tenants.enable_private; let namespaceValue = selectedTenant; @@ -59,7 +67,7 @@ export class SecuritySavedObjectsClientWrapper { attributes: T, options?: SavedObjectsCreateOptions ) => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -70,7 +78,7 @@ export class SecuritySavedObjectsClientWrapper { objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise> => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -82,9 +90,13 @@ export class SecuritySavedObjectsClientWrapper { ): Promise> => { const tenants = state.authInfo?.tenants; const availableTenantNames = Object.keys(tenants!); - availableTenantNames.push(defaultTenant); - availableTenantNames.push(globalTenant); - availableTenantNames.push(privateTenant + state.authInfo?.user_name); + availableTenantNames.push(DEFAULT_TENANT); + if (isGlobalEnabled) { + availableTenantNames.push(GLOBAL_TENANT); + } + if (isPrivateEnabled) { + availableTenantNames.push(PRIVATE_TENANT + state.authInfo?.user_name); + } _.assign(options, { namespaces: availableTenantNames }); return await wrapperOptions.client.find(options); }; @@ -94,7 +106,7 @@ export class SecuritySavedObjectsClientWrapper { id: string, options: SavedObjectsBaseOptions = {} ): Promise> => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -107,7 +119,7 @@ export class SecuritySavedObjectsClientWrapper { attributes: Partial, options: SavedObjectsUpdateOptions = {} ): Promise> => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -118,7 +130,7 @@ export class SecuritySavedObjectsClientWrapper { objects: Array>, options?: SavedObjectsCreateOptions ): Promise> => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -129,7 +141,7 @@ export class SecuritySavedObjectsClientWrapper { objects: Array>, options?: SavedObjectsBulkUpdateOptions ): Promise> => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -141,7 +153,7 @@ export class SecuritySavedObjectsClientWrapper { id: string, options: SavedObjectsDeleteOptions = {} ) => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -152,7 +164,7 @@ export class SecuritySavedObjectsClientWrapper { objects: SavedObjectsCheckConflictsObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise => { - if (selectedTenant !== undefined && isPrivateTenant(selectedTenant)) { + if (selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant)) { namespaceValue = selectedTenant + username; } _.assign(options, { namespace: [namespaceValue] }); @@ -176,7 +188,3 @@ export class SecuritySavedObjectsClientWrapper { }; }; } - -function isPrivateTenant(selectedTenant: string | undefined) { - return selectedTenant === '__user__'; -}