diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx index fa91a99c858a8..595aaf5127ca3 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx @@ -6,55 +6,16 @@ */ import React, { memo } from 'react'; -import { EuiButtonGroup, EuiSpacer } from '@elastic/eui'; -import type { EuiButtonGroupOptionProps } from '@elastic/eui/src/components/button/button_group/button_group'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useExpandableFlyoutState } from '@kbn/expandable-flyout'; +import { EuiSpacer } from '@elastic/eui'; import { MisconfigurationFindingsDetailsTable } from './misconfiguration_findings_details_table'; -enum InsightsTabCspTab { - MISCONFIGURATION = 'misconfigurationTabId', -} - -const insightsButtons: EuiButtonGroupOptionProps[] = [ - { - id: InsightsTabCspTab.MISCONFIGURATION, - label: ( - - ), - 'data-test-subj': 'misconfigurationTabDataTestId', - }, -]; - /** * Insights view displayed in the document details expandable flyout left section */ export const InsightsTabCsp = memo( ({ name, fieldName }: { name: string; fieldName: 'host.name' | 'user.name' }) => { - const panels = useExpandableFlyoutState(); - const activeInsightsId = panels.left?.path?.subTab ?? 'misconfigurationTabId'; - return ( <> - {}} - buttonSize="compressed" - isFullWidth - data-test-subj={'insightButtonGroupsTestId'} - /> diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 1362e0e42e6ba..ba413709d6cca 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -102,14 +102,14 @@ export const MisconfigurationFindingsDetailsTable = memo( const navToFindings = useNavigateFindings(); - const navToFindingsByHostName = (hostName: string) => { - navToFindings({ 'host.name': hostName }, ['rule.name']); - }; - const navToFindingsByRuleAndResourceId = (ruleId: string, resourceId: string) => { navToFindings({ 'rule.id': ruleId, 'resource.id': resourceId }); }; + const navToFindingsByName = (name: string, queryField: 'host.name' | 'user.name') => { + navToFindings({ [queryField]: name }, ['rule.name']); + }; + const columns: Array> = [ { field: 'rule', @@ -154,13 +154,13 @@ export const MisconfigurationFindingsDetailsTable = memo( { - navToFindingsByHostName(queryName); + navToFindingsByName(queryName, fieldName); }} > {i18n.translate( 'xpack.securitySolution.flyout.left.insights.misconfigurations.tableTitle', { - defaultMessage: 'Misconfigurations', + defaultMessage: 'Misconfigurations ', } )} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx index 3058300036565..6045a8b8c9a5e 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx @@ -13,7 +13,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; import { MisconfigurationsPreview } from './misconfiguration/misconfiguration_preview'; -export const EntityInsight = ({ hostName }: { hostName: string }) => { +export const EntityInsight = ({ + name, + fieldName, + isPreviewMode, +}: { + name: string; + fieldName: 'host.name' | 'user.name'; + isPreviewMode?: boolean; +}) => { const { euiTheme } = useEuiTheme(); const getSetupStatus = useCspSetupStatusApi(); const hasMisconfigurationFindings = getSetupStatus.data?.hasMisconfigurationsFindings; @@ -22,7 +30,6 @@ export const EntityInsight = ({ hostName }: { hostName: string }) => { <> {hasMisconfigurationFindings && ( <> - ({ hostName }: { hostName: string }) => { } > - + + )} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx index 1c4c2adb60218..2e10d481b9934 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx @@ -11,8 +11,9 @@ import { render } from '@testing-library/react'; import React from 'react'; import { MisconfigurationsPreview } from './misconfiguration_preview'; -const mockProps = { - hostName: 'testContextID', +const mockProps: { name: string; fieldName: 'host.name' | 'user.name' } = { + name: 'testContextID', + fieldName: 'host.name', }; describe('MisconfigurationsPreview', () => { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index f6ba0389f752a..e6c3950e81583 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -17,10 +17,12 @@ import { i18n } from '@kbn/i18n'; import { ExpandablePanel } from '@kbn/security-solution-common'; import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left'; import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine'; -import { buildHostNamesFilter } from '../../../../common/search_strategy'; +import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy'; +import { buildHostNamesFilter, buildUserNamesFilter } from '../../../../common/search_strategy'; const FIRST_RECORD_PAGINATION = { cursorStart: 0, @@ -120,46 +122,63 @@ const MisconfigurationPreviewScore = ({ ); }; -export const MisconfigurationsPreview = ({ hostName }: { hostName: string }) => { +export const MisconfigurationsPreview = ({ + name, + fieldName, + isPreviewMode, +}: { + name: string; + fieldName: 'host.name' | 'user.name'; + isPreviewMode?: boolean; +}) => { const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', hostName), + query: buildEntityFlyoutPreviewQuery(fieldName, name), sort: [], enabled: true, pageSize: 1, }); - + const isUsingHostName = fieldName === 'host.name'; const passedFindings = data?.count.passed || 0; const failedFindings = data?.count.failed || 0; const { euiTheme } = useEuiTheme(); const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - const hostNameFilterQuery = useMemo( - () => (hostName ? buildHostNamesFilter([hostName]) : undefined), - [hostName] + + const buildFilterQuery = useMemo( + () => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])), + [isUsingHostName, name] ); const riskScoreState = useRiskScore({ - riskEntity: RiskScoreEntity.host, - filterQuery: hostNameFilterQuery, + riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user, + filterQuery: buildFilterQuery, onlyLatest: false, pagination: FIRST_RECORD_PAGINATION, }); const { data: hostRisk } = riskScoreState; - const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; - const isRiskScoreExist = !!hostRiskData?.host.risk; + const riskData = hostRisk?.[0]; + const isRiskScoreExist = isUsingHostName + ? !!(riskData as HostRiskScore)?.host.risk + : !!(riskData as UserRiskScore)?.user.risk; const { openLeftPanel } = useExpandableFlyoutApi(); - const isPreviewMode = false; const goToEntityInsightTab = useCallback(() => { openLeftPanel({ - id: HostDetailsPanelKey, - params: { - name: hostName, - isRiskScoreExist, - hasMisconfigurationFindings, - path: { tab: 'csp_insights' }, - }, + id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey, + params: isUsingHostName + ? { + name, + isRiskScoreExist, + hasMisconfigurationFindings, + path: { tab: 'csp_insights' }, + } + : { + user: { name }, + isRiskScoreExist, + hasMisconfigurationFindings, + path: { tab: 'csp_insights' }, + }, }); - }, [hasMisconfigurationFindings, hostName, isRiskScoreExist, openLeftPanel]); + }, [hasMisconfigurationFindings, isRiskScoreExist, isUsingHostName, name, openLeftPanel]); const link = useMemo( () => !isPreviewMode @@ -178,7 +197,7 @@ export const MisconfigurationsPreview = ({ hostName }: { hostName: string }) => return ( + - ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx index bdff465e0b982..9c4b9938d6daa 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx @@ -51,4 +51,46 @@ describe('LeftPanel', () => { expect(tabElement).not.toBeInTheDocument(); }); + + it("doesn't render insights panel when there no misconfiguration findings", () => { + const { queryByText } = render( + , + { + wrapper: TestProviders, + } + ); + + const tabElement = queryByText('Insights'); + + expect(tabElement).not.toBeInTheDocument(); + }); + + it('render insights panel when there are misconfiguration findings', () => { + const { queryByText } = render( + , + { + wrapper: TestProviders, + } + ); + + const tabElement = queryByText('Insights'); + + expect(tabElement).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx index a04bd739eb299..ae3e99cc17cfe 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx @@ -28,6 +28,7 @@ export interface UserDetailsPanelProps extends Record { user: UserParam; path?: PanelPath; scopeId: string; + hasMisconfigurationFindings?: boolean; } export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps { key: 'user_details'; @@ -40,10 +41,24 @@ export const UserDetailsPanel = ({ user, path, scopeId, + hasMisconfigurationFindings, }: UserDetailsPanelProps) => { const managedUser = useManagedUser(user.name, user.email); - const tabs = useTabs(managedUser.data, user.name, isRiskScoreExist, scopeId); - const { selectedTabId, setSelectedTabId } = useSelectedTab(isRiskScoreExist, user, tabs, path); + const tabs = useTabs( + managedUser.data, + user.name, + isRiskScoreExist, + scopeId, + hasMisconfigurationFindings + ); + + const { selectedTabId, setSelectedTabId } = useSelectedTab( + isRiskScoreExist, + user, + tabs, + path, + hasMisconfigurationFindings + ); if (managedUser.isLoading) return ; @@ -67,7 +82,8 @@ const useSelectedTab = ( isRiskScoreExist: boolean, user: UserParam, tabs: LeftPanelTabsType, - path: PanelPath | undefined + path: PanelPath | undefined, + hasMisconfigurationFindings?: boolean ) => { const { openLeftPanel } = useExpandableFlyoutApi(); @@ -81,12 +97,13 @@ const useSelectedTab = ( const setSelectedTabId = (tabId: EntityDetailsLeftPanelTab) => { openLeftPanel({ id: UserDetailsPanelKey, - path: { - tab: tabId, - }, params: { user, isRiskScoreExist, + hasMisconfigurationFindings, + path: { + tab: tabId, + }, }, }); }; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx index 3a6814a28e62c..6f27b054759f2 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx @@ -8,7 +8,10 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { getRiskInputTab } from '../../../entity_analytics/components/entity_details_flyout'; +import { + getInsightsInputTab, + getRiskInputTab, +} from '../../../entity_analytics/components/entity_details_flyout'; import { UserAssetTableType } from '../../../explore/users/store/model'; import { ManagedUserDatasetKey } from '../../../../common/search_strategy/security_solution/users/managed_details'; import type { @@ -26,7 +29,8 @@ export const useTabs = ( managedUser: ManagedUserHits, name: string, isRiskScoreExist: boolean, - scopeId: string + scopeId: string, + hasMisconfigurationFindings?: boolean ): LeftPanelTabsType => useMemo(() => { const tabs: LeftPanelTabsType = []; @@ -51,8 +55,12 @@ export const useTabs = ( tabs.push(getEntraTab(entraManagedUser)); } + if (hasMisconfigurationFindings) { + tabs.push(getInsightsInputTab({ name, fieldName: 'user.name' })); + } + return tabs; - }, [isRiskScoreExist, managedUser, name, scopeId]); + }, [hasMisconfigurationFindings, isRiskScoreExist, managedUser, name, scopeId]); const getOktaTab = (oktaManagedUser: ManagedUserHit) => ({ id: EntityDetailsLeftPanelTab.OKTA, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx index 26945a12f8bd6..42b281d0c8d2b 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx @@ -23,6 +23,7 @@ import { ObservedEntity } from '../shared/components/observed_entity'; import type { ObservedEntityData } from '../shared/components/observed_entity/types'; import { useObservedUserItems } from './hooks/use_observed_user_items'; import type { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; +import { EntityInsight } from '../../../cloud_security_posture/components'; interface UserPanelContentProps { userName: string; @@ -72,6 +73,7 @@ export const UserPanelContent = ({ entity={{ name: userName, type: 'user' }} onChange={onAssetCriticalityChange} /> + 0 || failedFindings > 0; + useQueryInspector({ deleteQuery, inspect, @@ -119,11 +134,20 @@ export const UserPanel = ({ name: userName, email, }, + path: tab ? { tab } : undefined, + hasMisconfigurationFindings, }, - path: tab ? { tab } : undefined, }); }, - [telemetry, openLeftPanel, userRiskData?.user?.risk, userName, email, scopeId] + [ + telemetry, + openLeftPanel, + userRiskData?.user?.risk, + scopeId, + userName, + email, + hasMisconfigurationFindings, + ] ); const openPanelFirstTab = useCallback(() => openPanelTab(), [openPanelTab]); @@ -156,7 +180,9 @@ export const UserPanel = ({ return ( <> ( - + ), }, {