import { CoreSetup , CoreStart , Plugin , PluginInitializerContext , HttpStart , IBasePath , ApplicationStart , } from 'src/core/public' ; import { i18n } from '@kbn/i18n' ; import { Subscription } from 'rxjs' ; import type { AuthenticatedUser , SecurityPluginSetup , SecurityPluginStart , } from '../../security/public' ; import { getIsCloudEnabled } from '../common/is_cloud_enabled' ; import { ELASTIC_SUPPORT_LINK , CLOUD_SNAPSHOTS_PATH } from '../common/constants' ; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public' ; import { createUserMenuLinks } from './user_menu_links' ; import { getFullCloudUrl } from './utils' ; export interface CloudConfigType { id ? : string ; cname ? : string ; base_url ? : string ; profile_url ? : string ; deployment_url ? : string ; organization_url ? : string ; full_story : { enabled : boolean ; org_id ? : string ; } ; } interface CloudSetupDependencies { home ? : HomePublicPluginSetup ; security ? : Pick < SecurityPluginSetup , 'authc' > ; } interface CloudStartDependencies { security ? : SecurityPluginStart ; } export interface CloudSetup { cloudId ? : string ; cname ? : string ; baseUrl ? : string ; deploymentUrl ? : string ; profileUrl ? : string ; organizationUrl ? : string ; snapshotsUrl ? : string ; isCloudEnabled : boolean ; } interface SetupFullstoryDeps extends CloudSetupDependencies { application ? : Promise < ApplicationStart > ; basePath : IBasePath ; } export class CloudPlugin implements Plugin < CloudSetup > { private config ! : CloudConfigType ; private isCloudEnabled : boolean ; private appSubscription ? : Subscription ; constructor ( private readonly initializerContext ) { this . config = this . initializerContext . config . get < CloudConfigType > ( ) ; this . isCloudEnabled = false ; } public setup ( core , { home , security } ) { const application = core . getStartServices ( ) . then ( ( [ coreStart ] ) => { return coreStart . application ; } ) ; this . setupFullstory ( { basePath : core . http . basePath , security , application } ) . catch ( ( e ) => console . debug ( `Error setting up FullStory: ${ e . toString ( ) }` ) ) ; const { id , cname , profile_url : profileUrl , organization_url : organizationUrl , deployment_url : deploymentUrl , base_url : baseUrl , } = this . config ; this . isCloudEnabled = getIsCloudEnabled ( id ) ; if ( home ) { home . environment . update ( { cloud : this . isCloudEnabled } ) ; if ( this . isCloudEnabled ) { home . tutorials . setVariable ( 'cloud' , { id , baseUrl , profileUrl , deploymentUrl } ) ; } } const fullCloudDeploymentUrl = getFullCloudUrl ( baseUrl , deploymentUrl ) ; const fullCloudProfileUrl = getFullCloudUrl ( baseUrl , profileUrl ) ; const fullCloudOrganizationUrl = getFullCloudUrl ( baseUrl , organizationUrl ) ; const fullCloudSnapshotsUrl = `${ fullCloudDeploymentUrl }/${ CLOUD_SNAPSHOTS_PATH }` ; return { cloudId : id , cname , baseUrl , deploymentUrl : fullCloudDeploymentUrl , profileUrl : fullCloudProfileUrl , organizationUrl : fullCloudOrganizationUrl , snapshotsUrl : fullCloudSnapshotsUrl , isCloudEnabled : this . isCloudEnabled , } ; } public start ( coreStart , { security } ) { const { deployment_url : deploymentUrl , base_url : baseUrl } = this . config ; coreStart . chrome . setHelpSupportUrl ( ELASTIC_SUPPORT_LINK ) ; const setLinks = ( authorized ) => { if ( ! authorized ) return ; if ( baseUrl && deploymentUrl ) { coreStart . chrome . setCustomNavLink ( { title : i18n . translate ( 'xpack.cloud.deploymentLinkLabel' , { defaultMessage : 'Manage this deployment' , } ) , euiIconType : 'logoCloud' , href : getFullCloudUrl ( baseUrl , deploymentUrl ) , } ) ; } if ( security && this . isCloudEnabled ) { const userMenuLinks = createUserMenuLinks ( this . config ) ; security . navControlService . addUserMenuLinks ( userMenuLinks ) ; } } ; this . checkIfAuthorizedForLinks ( { http : coreStart . http , security } ) . then ( setLinks ) . catch ( ( ) => setLinks ( true ) ) ; } public stop ( ) { this . appSubscription ?. unsubscribe ( ) ; } /**
     * Determines if the current user should see links back to Cloud.
     * This isn't a true authorization check, but rather a heuristic to
     * see if the current user is *likely* a cloud deployment administrator.
     *
     * At this point, we do not have enough information to reliably make this determination,
     * but we do know that all cloud deployment admins are superusers by default.
     */ private async checkIfAuthorizedForLinks ( { http , security , } ) { if ( http . anonymousPaths . isAnonymous ( window . location . pathname ) ) { return false ; } if ( ! security ) return true ; const user = await security . authc . getCurrentUser ( ) . catch ( ( ) => null ) ; return user ?. roles . includes ( 'superuser' ) ?? true ; } private async setupFullstory ( { basePath , security , application } ) { const { enabled , org_id : orgId } = this . config . full_story ; if ( ! enabled || ! orgId ) { return ; } const fullStoryChunkPromise = import ( './fullstory' ) ; const userIdPromise = security ? loadFullStoryUserId ( { getCurrentUser : security . authc . getCurrentUser } ) : Promise . resolve ( undefined ) ; const [ { initializeFullStory } , userId ] = await Promise . all ( [ fullStoryChunkPromise , userIdPromise , ] ) ; const { fullStory , sha256 } = initializeFullStory ( { basePath , orgId , packageInfo : this . initializerContext . env . packageInfo , } ) ; try { if ( userId ) { const hashedId = sha256 ( userId . toString ( ) ) ; application ?. then ( async ( ) => { const appStart = await application ; this . appSubscription = appStart . currentAppId$ . subscribe ( ( appId ) => { fullStory . setUserVars ( { app_id_str : appId ?? 'unknown' , } ) ; } ) ; } ) . catch ( ( e ) => { console . error ( `[cloud.full_story] Could not retrieve application service due to error: ${ e . toString ( ) }` , e ) ; } ) ; const kibanaVer = this . initializerContext . env . packageInfo . version ; const parsedVer = ( kibanaVer . indexOf ( '.' ) > - 1 ? kibanaVer . split ( '.' ) : [ ] ) . map ( ( s ) => parseInt ( s , 10 ) ) ; fullStory . identify ( hashedId , { version_str : kibanaVer , version_major_int : parsedVer [ 0 ] ?? - 1 , version_minor_int : parsedVer [ 1 ] ?? - 1 , version_patch_int : parsedVer [ 2 ] ?? - 1 , } ) ; } } catch ( e ) { console . error ( `[cloud.full_story] Could not call FS.identify due to error: ${ e . toString ( ) }` , e ) ; } const memory = window . performance . memory ; let memoryInfo = { } ; if ( memory ) { memoryInfo = { memory_js_heap_size_limit_int : memory . jsHeapSizeLimit , memory_js_heap_size_total_int : memory . totalJSHeapSize , memory_js_heap_size_used_int : memory . usedJSHeapSize , } ; } fullStory . event ( 'Loaded Kibana' , { kibana_version_str : this . initializerContext . env . packageInfo . version , ... memoryInfo , } ) ; } } internal export const loadFullStoryUserId = async ( { getCurrentUser , } ) => { try { const currentUser = await getCurrentUser ( ) . catch ( ( ) => undefined ) ; if ( ! currentUser ) { return undefined ; } if ( ! currentUser . username ) { console . debug ( `[cloud.full_story] username not specified. User metadata: ${ JSON . stringify ( currentUser . metadata ) }` ) ; } return currentUser . username ; } catch ( e ) { console . error ( `[cloud.full_story] Error loading the current user: ${ e . toString ( ) }` , e ) ; return undefined ; } } ;