From 87d40fa0c1076b03e8ca5472b3d085c3c361aa60 Mon Sep 17 00:00:00 2001 From: Kajetan Nobel Date: Tue, 18 Jul 2023 13:16:56 +0200 Subject: [PATCH] Introduce read-only tenant mode logic Signed-off-by: Kajetan Nobel --- server/auth/types/authentication_type.ts | 7 +- server/plugin.ts | 117 +++++++++++++++++++++-- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 56ec21463..faec853a6 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -37,6 +37,11 @@ export interface IAuthenticationType { type: string; authHandler: AuthenticationHandler; init: () => Promise; + requestIncludesAuthInfo(request: OpenSearchDashboardsRequest): boolean; + buildAuthHeaderFromCookie( + cookie: SecuritySessionCookie, + request: OpenSearchDashboardsRequest + ): any; } export type IAuthHandlerConstructor = new ( @@ -287,4 +292,4 @@ export abstract class AuthenticationType implements IAuthenticationType { request: OpenSearchDashboardsRequest ): any; public abstract init(): Promise; -} +} \ No newline at end of file diff --git a/server/plugin.ts b/server/plugin.ts index c6aec6e58..7fd64fdb6 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -15,7 +15,7 @@ import { first } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import { ResponseObject } from '@hapi/hapi'; +import { merge } from 'lodash'; import { PluginInitializerContext, CoreSetup, @@ -25,6 +25,9 @@ import { ILegacyClusterClient, SessionStorageFactory, SharedGlobalConfig, + OpenSearchDashboardsRequest, + Capabilities, + CapabilitiesSwitcher, } from '../../../src/core/server'; import { SecurityPluginSetup, SecurityPluginStart } from './types'; @@ -39,15 +42,13 @@ import { ISavedObjectTypeRegistry, } from '../../../src/core/server/saved_objects'; import { setupIndexTemplate, migrateTenantIndices } from './multitenancy/tenant_index'; -import { - IAuthenticationType, - OpenSearchDashboardsAuthState, -} from './auth/types/authentication_type'; +import { IAuthenticationType } from './auth/types/authentication_type'; import { getAuthenticationHandler } from './auth/auth_handler_factory'; 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 { globalTenantName, isPrivateTenant } from '../common'; import { addTenantParameterToResolvedShortLink } from './multitenancy/tenant_resolver'; export interface SecurityPluginRequestContext { @@ -86,6 +87,107 @@ export class SecurityPlugin implements Plugin { + return Object.entries(capabilities).reduce((acc, cur) => { + const [key, value] = cur; + + return { ...acc, [key]: capabilities.hide_for_read_only.includes(key) ? false : value }; + }, {}); + } + + toggleForReadOnlyTenant(uiCapabilities: Capabilities): Partial { + const defaultTenantOnlyCapabilities = Object.entries(uiCapabilities).reduce((acc, cur) => { + const [key, value] = cur; + + if (!value.hide_for_read_only) { + return acc; + } + + const updatedValue = this.toggleReadOnlyCapabilities(value); + + return { ...acc, [key]: updatedValue }; + }, {}); + + const finalCapabilities = merge(uiCapabilities, defaultTenantOnlyCapabilities); + + return finalCapabilities; + } + + capabilitiesSwitcher( + securityClient: SecurityClient, + auth: IAuthenticationType, + securitySessionStorageFactory: SessionStorageFactory + ): CapabilitiesSwitcher { + return async ( + request: OpenSearchDashboardsRequest, + uiCapabilities: Capabilities + ): Promise> => { + // omit for anonymous pages to avoid authentication errors + if (this.isAnonymousPage(request)) { + return uiCapabilities; + } + + try { + const cookie = await securitySessionStorageFactory.asScoped(request).get(); + let headers = request.headers; + + if (!auth.requestIncludesAuthInfo(request) && cookie) { + headers = auth.buildAuthHeaderFromCookie(cookie, request); + } + + const authInfo = await securityClient.authinfo(request, headers); + + if (!authInfo.user_requested_tenant && cookie) { + authInfo.user_requested_tenant = cookie.tenant; + } + + if (this.isReadOnlyTenant(authInfo)) { + return this.toggleForReadOnlyTenant(uiCapabilities); + } + } catch (error: any) { + this.logger.error(`Could not check auth info: ${error.stack}`); + } + + return uiCapabilities; + }; + } + + registerSwitcher( + core: CoreSetup, + securityClient: SecurityClient, + auth: IAuthenticationType, + securitySessionStorageFactory: SessionStorageFactory + ) { + core.capabilities.registerSwitcher( + this.capabilitiesSwitcher(securityClient, auth, securitySessionStorageFactory) + ); + } + public async setup(core: CoreSetup) { this.logger.debug('opendistro_security: Setup'); @@ -141,6 +243,9 @@ export class SecurityPlugin implements Plugin