import Hapi from '@hapi/hapi' ; import * as Rx from 'rxjs' ; import { filter , first , map , switchMap , take } from 'rxjs/operators' ; import { BasePath , IClusterClient , KibanaRequest , PackageInfo , PluginInitializerContext , SavedObjectsClientContract , SavedObjectsServiceStart , ServiceStatusLevels , StatusServiceSetup , UiSettingsServiceStart , } from '../../../../src/core/server' ; import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server' ; import { IEventLogService } from '../../event_log/server' ; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server' ; import { LicensingPluginStart } from '../../licensing/server' ; import type { ScreenshotResult , ScreenshottingStart } from '../../screenshotting/server' ; import { SecurityPluginSetup } from '../../security/server' ; import { DEFAULT_SPACE_ID } from '../../spaces/common/constants' ; import { SpacesPluginSetup } from '../../spaces/server' ; import { TaskManagerSetupContract , TaskManagerStartContract } from '../../task_manager/server' ; import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../common/constants' ; import { durationToNumber } from '../common/schema_utils' ; import { ReportingConfig , ReportingSetup } from './' ; import { ReportingConfigType } from './config' ; import { checkLicense , getExportTypesRegistry , LevelLogger } from './lib' ; import { reportingEventLoggerFactory } from './lib/event_logger/logger' ; import { IReport , ReportingStore } from './lib/store' ; import { ExecuteReportTask , MonitorReportsTask , ReportTaskParams } from './lib/tasks' ; import { ReportingPluginRouter , ScreenshotOptions } from './types' ; export interface ReportingInternalSetup { eventLog : IEventLogService ; basePath : Pick < BasePath , 'set' > ; router : ReportingPluginRouter ; features : FeaturesPluginSetup ; security ? : SecurityPluginSetup ; spaces ? : SpacesPluginSetup ; taskManager : TaskManagerSetupContract ; logger : LevelLogger ; status : StatusServiceSetup ; } export interface ReportingInternalStart { store : ReportingStore ; savedObjects : SavedObjectsServiceStart ; uiSettings : UiSettingsServiceStart ; esClient : IClusterClient ; data : DataPluginStart ; licensing : LicensingPluginStart ; logger : LevelLogger ; screenshotting : ScreenshottingStart ; taskManager : TaskManagerStartContract ; } internal export class ReportingCore { private packageInfo : PackageInfo ; private pluginSetupDeps ? : ReportingInternalSetup ; private pluginStartDeps ? : ReportingInternalStart ; private readonly pluginSetup$ = new Rx . ReplaySubject < boolean > ( ) ; private readonly pluginStart$ = new Rx . ReplaySubject < ReportingInternalStart > ( ) ; private deprecatedAllowedRoles : string [ ] | false = false ; private exportTypesRegistry = getExportTypesRegistry ( ) ; private executeTask : ExecuteReportTask ; private monitorTask : MonitorReportsTask ; private config ? : ReportingConfig ; private executing : Set < string > ; public getContract : ( ) => ReportingSetup ; constructor ( private logger , context ) { this . packageInfo = context . env . packageInfo ; const syncConfig = context . config . get < ReportingConfigType > ( ) ; this . deprecatedAllowedRoles = syncConfig . roles . enabled ? syncConfig . roles . allow : false ; this . executeTask = new ExecuteReportTask ( this , syncConfig , this . logger ) ; this . monitorTask = new MonitorReportsTask ( this , syncConfig , this . logger ) ; this . getContract = ( ) => ( { usesUiCapabilities : ( ) => syncConfig . roles . enabled === false , } ) ; this . executing = new Set ( ) ; } public getKibanaPackageInfo ( ) { return this . packageInfo ; } public pluginSetup ( setupDeps ) { this . pluginSetup$ . next ( true ) ; this . pluginSetupDeps = setupDeps ; const { executeTask , monitorTask } = this ; setupDeps . taskManager . registerTaskDefinitions ( { [ executeTask . TYPE ] : executeTask . getTaskDefinition ( ) , [ monitorTask . TYPE ] : monitorTask . getTaskDefinition ( ) , } ) ; } public async pluginStart ( startDeps ) { this . pluginStart$ . next ( startDeps ) ; this . pluginStartDeps = startDeps ; await this . assertKibanaIsAvailable ( ) ; const { taskManager } = startDeps ; const { executeTask , monitorTask } = this ; await Promise . all ( [ executeTask . init ( taskManager ) , monitorTask . init ( taskManager ) ] ) ; } private async assertKibanaIsAvailable ( ) { const { status } = this . getPluginSetupDeps ( ) ; await status . overall$ . pipe ( filter ( ( current ) => current . level === ServiceStatusLevels . available ) , first ( ) ) . toPromise ( ) ; } public async pluginSetsUp ( ) { if ( this . pluginSetupDeps && this . config ) { return true ; } return await this . pluginSetup$ . pipe ( take ( 2 ) ) . toPromise ( ) ; } public async pluginStartsUp ( ) { return await this . getPluginStartDeps ( ) . then ( ( ) => true ) ; } public pluginIsStarted ( ) { return this . pluginSetupDeps != null && this . config != null && this . pluginStartDeps != null ; } public setConfig ( config ) { this . config = config ; this . pluginSetup$ . next ( true ) ; } /**
     * If xpack.reporting.roles.enabled === true, register Reporting as a feature
     * that is controlled by user role names
     */ public registerFeature ( ) { const { features } = this . getPluginSetupDeps ( ) ; const deprecatedRoles = this . getDeprecatedAllowedRoles ( ) ; if ( deprecatedRoles !== false ) { const allowedRoles = [ 'superuser' , ... ( deprecatedRoles ?? [ ] ) ] ; const privileges = allowedRoles . map ( ( role ) => ( { requiredClusterPrivileges : [ ] , requiredRoles : [ role ] , ui : [ ] , } ) ) ; features . registerElasticsearchFeature ( { id : 'reporting' , catalogue : [ 'reporting' ] , management : { insightsAndAlerting : [ 'reporting' ] , } , privileges , } ) ; } else { this . logger . debug ( `Reporting roles configuration is disabled. Please assign access to Reporting use Kibana feature controls for applications.` ) ; features . enableReportingUiCapabilities ( ) ; } } public getConfig ( ) { if ( ! this . config ) { throw new Error ( 'Config is not yet initialized' ) ; } return this . config ; } public getDeprecatedAllowedRoles ( ) { return this . deprecatedAllowedRoles ; } public async getPluginStartDeps ( ) { if ( this . pluginStartDeps ) { return this . pluginStartDeps ; } return await this . pluginStart$ . pipe ( first ( ) ) . toPromise ( ) ; } public getExportTypesRegistry ( ) { return this . exportTypesRegistry ; } public async scheduleTask ( report ) { return await this . executeTask . scheduleTask ( report ) ; } public async getStore ( ) { return ( await this . getPluginStartDeps ( ) ) . store ; } public async getLicenseInfo ( ) { const { license$ } = ( await this . getPluginStartDeps ( ) ) . licensing ; const registry = this . getExportTypesRegistry ( ) ; return await license$ . pipe ( map ( ( license ) => checkLicense ( registry , license ) ) , first ( ) ) . toPromise ( ) ; } public getPluginSetupDeps ( ) { if ( ! this . pluginSetupDeps ) { throw new Error ( `"pluginSetupDeps" dependencies haven't initialized yet` ) ; } return this . pluginSetupDeps ; } private async getSavedObjectsClient ( request ) { const { savedObjects } = await this . getPluginStartDeps ( ) ; return savedObjects . getScopedClient ( request ) as SavedObjectsClientContract ; } public async getUiSettingsServiceFactory ( savedObjectsClient ) { const { uiSettings : uiSettingsService } = await this . getPluginStartDeps ( ) ; const scopedUiSettingsService = uiSettingsService . asScopedToClient ( savedObjectsClient ) ; return scopedUiSettingsService ; } public getSpaceId ( request , logger = this . logger ) { const spacesService = this . getPluginSetupDeps ( ) . spaces ?. spacesService ; if ( spacesService ) { const spaceId = spacesService ?. getSpaceId ( request ) ; if ( spaceId !== DEFAULT_SPACE_ID ) { logger . info ( `Request uses Space ID: ${ spaceId }` ) ; return spaceId ; } else { logger . debug ( `Request uses default Space` ) ; } } } public getFakeRequest ( baseRequest , spaceId , logger = this . logger ) { const fakeRequest = KibanaRequest . from ( { path : '/' , route : { settings : { } } , url : { href : '/' } , raw : { req : { url : '/' } } , ... baseRequest , } as Hapi . Request ) ; const spacesService = this . getPluginSetupDeps ( ) . spaces ?. spacesService ; if ( spacesService ) { if ( spaceId && spaceId !== DEFAULT_SPACE_ID ) { logger . info ( `Generating request for space: ${ spaceId }` ) ; this . getPluginSetupDeps ( ) . basePath . set ( fakeRequest , `/s/${ spaceId }` ) ; } } return fakeRequest ; } public async getUiSettingsClient ( request , logger = this . logger ) { const spacesService = this . getPluginSetupDeps ( ) . spaces ?. spacesService ; const spaceId = this . getSpaceId ( request , logger ) ; if ( spacesService && spaceId ) { logger . info ( `Creating UI Settings Client for space: ${ spaceId }` ) ; } const savedObjectsClient = await this . getSavedObjectsClient ( request ) ; return await this . getUiSettingsServiceFactory ( savedObjectsClient ) ; } public async getDataViewsService ( request ) { const { savedObjects } = await this . getPluginStartDeps ( ) ; const savedObjectsClient = savedObjects . getScopedClient ( request ) ; const { indexPatterns } = await this . getDataService ( ) ; const { asCurrentUser : esClient } = ( await this . getEsClient ( ) ) . asScoped ( request ) ; const dataViews = await indexPatterns . dataViewsServiceFactory ( savedObjectsClient , esClient ) ; return dataViews ; } public async getDataService ( ) { const startDeps = await this . getPluginStartDeps ( ) ; return startDeps . data ; } public async getEsClient ( ) { const startDeps = await this . getPluginStartDeps ( ) ; return startDeps . esClient ; } public getScreenshots ( options ) { return Rx . defer ( ( ) => this . getPluginStartDeps ( ) ) . pipe ( switchMap ( ( { screenshotting } ) => { const config = this . getConfig ( ) ; return screenshotting . getScreenshots ( { ... options , timeouts : { loadDelay : durationToNumber ( config . get ( 'capture' , 'loadDelay' ) ) , openUrl : durationToNumber ( config . get ( 'capture' , 'timeouts' , 'openUrl' ) ) , waitForElements : durationToNumber ( config . get ( 'capture' , 'timeouts' , 'waitForElements' ) ) , renderComplete : durationToNumber ( config . get ( 'capture' , 'timeouts' , 'renderComplete' ) ) , } , layout : { zoom : config . get ( 'capture' , 'zoom' ) , ... options . layout , } , urls : options . urls . map ( ( url ) => typeof url === 'string' ? url : [ url [ 0 ] , { [ REPORTING_REDIRECT_LOCATOR_STORE_KEY ] : url [ 1 ] } ] ) , } ) ; } ) ) ; } public trackReport ( reportId ) { this . executing . add ( reportId ) ; } public untrackReport ( reportId ) { this . executing . delete ( reportId ) ; } public countConcurrentReports ( ) { return this . executing . size ; } public getEventLogger ( report , task ? ) { const ReportingEventLogger = reportingEventLoggerFactory ( this . pluginSetupDeps ! . eventLog ) ; return new ReportingEventLogger ( report , task ) ; } }