mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solutions] Add PLI authorisation for Advanced Insights (Entity Risk) (#161190)
## Summary
Add PLI authorization checks for Entity Analytics features.
*This PR only restricts access to the features* but doesn't implement
PLG/Upselling. It will be added later when we have defined the UX for
it.
The `advancedInsights` PLI was already configured, so I only had to add
extra checks to make sure users can't see the Risk score on other
components.
Updated components:
* "All hosts" table on the Hosts page
* "All users" table on the Users page
* Host overview on the Host details page and Host details flyout
* User overview on the User details page and User details flyout
* Alerts flyout
* Remove sample Upselling components config
### Not included
* Upselling/PLG
* I left empty tabs/pages where the Upselling component will be added
### How to test it?
#### ESS
* Run ESS with a basic license
* Run ESS with a platinum
#### Serverless
* Run Serverless with security essentials (serverless.security.yml)
```
xpack.serverless.security.productTypes:
[
{ product_line: 'security', product_tier: 'essentials' }
]
```
* Run Serverless with security complete
(kibana/config/serverless.security.yml)
```
xpack.serverless.security.productTypes:
[
{ product_line: 'security', product_tier: 'complete' },
]
```
1ab84134
-bee1-497c-9b41-a9ec398bd921
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c0cb6133ea
commit
a074c06864
39 changed files with 272 additions and 154 deletions
|
@ -24,6 +24,10 @@ jest.mock('../../../lib/kibana', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../helper_hooks', () => ({
|
||||
useHasSecurityCapability: () => true,
|
||||
}));
|
||||
|
||||
jest.mock('../table/field_name_cell');
|
||||
|
||||
const RISK_SCORE_DATA_ROWS = 2;
|
||||
|
|
|
@ -30,6 +30,7 @@ import { RiskSummary } from './risk_summary';
|
|||
import { EnrichmentSummary } from './enrichment_summary';
|
||||
import type { HostRisk, UserRisk } from '../../../../explore/containers/risk_score';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { useHasSecurityCapability } from '../../../../helper_hooks';
|
||||
|
||||
const UppercaseEuiTitle = styled(EuiTitle)`
|
||||
text-transform: uppercase;
|
||||
|
@ -151,6 +152,12 @@ const ThreatSummaryViewComponent: React.FC<{
|
|||
(eventDetail) => eventDetail?.field === 'user.risk.calculated_level'
|
||||
)?.values?.[0] as RiskSeverity | undefined;
|
||||
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
|
||||
if (!hasEntityAnalyticsCapability && enrichments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
|
@ -161,21 +168,25 @@ const ThreatSummaryViewComponent: React.FC<{
|
|||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup direction="column" gutterSize="m" style={{ flexGrow: 0 }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskSummary
|
||||
riskEntity={RiskScoreEntity.host}
|
||||
risk={hostRisk}
|
||||
originalRisk={originalHostRisk}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{hasEntityAnalyticsCapability && (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskSummary
|
||||
riskEntity={RiskScoreEntity.host}
|
||||
risk={hostRisk}
|
||||
originalRisk={originalHostRisk}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskSummary
|
||||
riskEntity={RiskScoreEntity.user}
|
||||
risk={userRisk}
|
||||
originalRisk={originalUserRisk}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskSummary
|
||||
riskEntity={RiskScoreEntity.user}
|
||||
risk={userRisk}
|
||||
originalRisk={originalUserRisk}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EnrichmentSummary
|
||||
browserFields={browserFields}
|
||||
|
|
|
@ -198,7 +198,7 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
|
||||
const enrichmentCount = allEnrichments.length;
|
||||
|
||||
const { hostRisk, userRisk, isLicenseValid } = useRiskScoreData(data);
|
||||
const { hostRisk, userRisk, isAuthorized } = useRiskScoreData(data);
|
||||
|
||||
const renderer = useMemo(
|
||||
() =>
|
||||
|
@ -212,9 +212,9 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
|
||||
const showThreatSummary = useMemo(() => {
|
||||
const hasEnrichments = enrichmentCount > 0;
|
||||
const hasRiskInfoWithLicense = isLicenseValid && (hostRisk || userRisk);
|
||||
const hasRiskInfoWithLicense = isAuthorized && (hostRisk || userRisk);
|
||||
return hasEnrichments || hasRiskInfoWithLicense;
|
||||
}, [enrichmentCount, hostRisk, isLicenseValid, userRisk]);
|
||||
}, [enrichmentCount, hostRisk, isAuthorized, userRisk]);
|
||||
const endpointResponseActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'endpointResponseActionsEnabled'
|
||||
);
|
||||
|
|
|
@ -20,7 +20,7 @@ const defaultResult = {
|
|||
data: [],
|
||||
inspect: {},
|
||||
isInspected: false,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
isModuleEnabled: true,
|
||||
refetch: () => {},
|
||||
totalCount: 0,
|
||||
|
@ -55,7 +55,7 @@ describe('useRiskScoreData', () => {
|
|||
expect(result.current).toEqual({
|
||||
hostRisk: defaultRisk,
|
||||
userRisk: defaultRisk,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
|
|||
const {
|
||||
data: hostRiskData,
|
||||
loading: hostRiskLoading,
|
||||
isLicenseValid: isHostLicenseValid,
|
||||
isAuthorized: isHostRiskScoreAuthorized,
|
||||
isModuleEnabled: isHostRiskModuleEnabled,
|
||||
} = useRiskScore({
|
||||
filterQuery: hostNameFilterQuery,
|
||||
|
@ -57,7 +57,7 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
|
|||
const {
|
||||
data: userRiskData,
|
||||
loading: userRiskLoading,
|
||||
isLicenseValid: isUserLicenseValid,
|
||||
isAuthorized: isUserRiskScoreAuthorized,
|
||||
isModuleEnabled: isUserRiskModuleEnabled,
|
||||
} = useRiskScore({
|
||||
filterQuery: userNameFilterQuery,
|
||||
|
@ -75,5 +75,9 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
|
|||
[userRiskLoading, isUserRiskModuleEnabled, userRiskData]
|
||||
);
|
||||
|
||||
return { userRisk, hostRisk, isLicenseValid: isHostLicenseValid && isUserLicenseValid };
|
||||
return {
|
||||
userRisk,
|
||||
hostRisk,
|
||||
isAuthorized: isHostRiskScoreAuthorized && isUserRiskScoreAuthorized,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
DEFAULT_RULES_TABLE_REFRESH_SETTING,
|
||||
DEFAULT_RULE_REFRESH_INTERVAL_ON,
|
||||
DEFAULT_RULE_REFRESH_INTERVAL_VALUE,
|
||||
SERVER_APP_ID,
|
||||
} from '../../../../common/constants';
|
||||
import type { StartServices } from '../../../types';
|
||||
import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage';
|
||||
|
@ -178,6 +179,16 @@ export const createStartServicesMock = (
|
|||
})),
|
||||
},
|
||||
},
|
||||
application: {
|
||||
...core.application,
|
||||
capabilities: {
|
||||
...core.application.capabilities,
|
||||
[SERVER_APP_ID]: {
|
||||
crud: true,
|
||||
read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
security,
|
||||
storage,
|
||||
fleet,
|
||||
|
|
|
@ -60,7 +60,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => {
|
|||
inspect: null,
|
||||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
loading: false,
|
||||
};
|
||||
const HostPanelWithDefaultProps = (propOverrides: Partial<HostPanelProps>) => (
|
||||
|
@ -106,7 +106,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => {
|
|||
it('should not show risk if the license is not valid', () => {
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
...defaultRiskReturnValues,
|
||||
isLicenseValid: false,
|
||||
isAuthorized: false,
|
||||
data: null,
|
||||
});
|
||||
const { queryByTestId } = render(<HostPanelWithDefaultProps />);
|
||||
|
@ -119,7 +119,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => {
|
|||
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
...defaultRiskReturnValues,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
data: [
|
||||
{
|
||||
host: {
|
||||
|
|
|
@ -88,7 +88,7 @@ export const HostPanel = React.memo(
|
|||
);
|
||||
}, [browserFields, data, id]);
|
||||
|
||||
const { data: hostRisk, isLicenseValid: isRiskLicenseValid } = useRiskScore({
|
||||
const { data: hostRisk, isAuthorized: isRiskScoreAuthorized } = useRiskScore({
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
skip: hostName == null,
|
||||
});
|
||||
|
@ -149,7 +149,7 @@ export const HostPanel = React.memo(
|
|||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
{isRiskLicenseValid && (
|
||||
{isRiskScoreAuthorized && (
|
||||
<>
|
||||
<EuiFlexGroup data-test-subj="host-panel-risk">
|
||||
{hostRiskScore && (
|
||||
|
|
|
@ -62,7 +62,7 @@ export const UserPanel = React.memo(
|
|||
({ data, selectedPatterns, openUserDetailsPanel }: UserPanelProps) => {
|
||||
const userName = useMemo(() => getTimelineEventData('user.name', data), [data]);
|
||||
|
||||
const { data: userRisk, isLicenseValid: isRiskLicenseValid } = useRiskScore({
|
||||
const { data: userRisk, isAuthorized: isRiskScoreAuthorized } = useRiskScore({
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
skip: userName == null,
|
||||
});
|
||||
|
@ -114,7 +114,7 @@ export const UserPanel = React.memo(
|
|||
</UserPanelSection>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
{isRiskLicenseValid && (
|
||||
{isRiskScoreAuthorized && (
|
||||
<>
|
||||
<EuiFlexGroup data-test-subj="user-panel-risk">
|
||||
{userRiskScore && (
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('AlertDetailsPage - SummaryTab - UserPanel', () => {
|
|||
inspect: null,
|
||||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
loading: false,
|
||||
};
|
||||
const UserPanelWithDefaultProps = (propOverrides: Partial<UserPanelProps>) => (
|
||||
|
@ -64,7 +64,7 @@ describe('AlertDetailsPage - SummaryTab - UserPanel', () => {
|
|||
it('should not show risk if the license is not valid', () => {
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
...defaultRiskReturnValues,
|
||||
isLicenseValid: false,
|
||||
isAuthorized: false,
|
||||
data: null,
|
||||
});
|
||||
const { queryByTestId } = render(<UserPanelWithDefaultProps />);
|
||||
|
@ -77,7 +77,7 @@ describe('AlertDetailsPage - SummaryTab - UserPanel', () => {
|
|||
|
||||
mockUseRiskScore.mockReturnValue({
|
||||
...defaultRiskReturnValues,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
data: [
|
||||
{
|
||||
user: {
|
||||
|
|
|
@ -32,7 +32,6 @@ import type { UsersComponentsQueryProps } from '../../../users/pages/navigation/
|
|||
import type { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types';
|
||||
import { useDashboardHref } from '../../../../common/hooks/use_dashboard_href';
|
||||
import { RiskScoresNoDataDetected } from '../risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { useUpsellingComponent } from '../../../../common/hooks/use_upselling';
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: ${({ theme }) => theme.eui.euiSizeL};
|
||||
|
@ -49,7 +48,6 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
riskEntity: RiskScoreEntity;
|
||||
}
|
||||
> = ({ entityName, startDate, endDate, setQuery, deleteQuery, riskEntity }) => {
|
||||
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
|
||||
const queryId = useMemo(
|
||||
() =>
|
||||
riskEntity === RiskScoreEntity.host
|
||||
|
@ -84,13 +82,14 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
[entityName, riskEntity]
|
||||
);
|
||||
|
||||
const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled } = useRiskScore({
|
||||
filterQuery,
|
||||
onlyLatest: false,
|
||||
riskEntity,
|
||||
skip: !overTimeToggleStatus && !contributorsToggleStatus,
|
||||
timerange,
|
||||
});
|
||||
const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled, isAuthorized } =
|
||||
useRiskScore({
|
||||
filterQuery,
|
||||
onlyLatest: false,
|
||||
riskEntity,
|
||||
skip: !overTimeToggleStatus && !contributorsToggleStatus,
|
||||
timerange,
|
||||
});
|
||||
|
||||
const rules = useMemo(() => {
|
||||
const lastRiskItem = data && data.length > 0 ? data[data.length - 1] : null;
|
||||
|
@ -130,8 +129,8 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
if (RiskScoreUpsell) {
|
||||
return <RiskScoreUpsell />;
|
||||
if (!isAuthorized) {
|
||||
return <>{'TODO: Add RiskScore Upsell'}</>;
|
||||
}
|
||||
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
|
|
|
@ -34,7 +34,7 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
|||
const defaultFeatureStatus = {
|
||||
isLoading: false,
|
||||
isDeprecated: false,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
isEnabled: true,
|
||||
refetch: () => {},
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ const defaultRisk = {
|
|||
data: undefined,
|
||||
inspect: {},
|
||||
isInspected: false,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
isModuleEnabled: true,
|
||||
isDeprecated: false,
|
||||
totalCount: 0,
|
||||
|
@ -72,7 +72,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
|||
test('does not search if license is not valid', () => {
|
||||
mockUseRiskScoreFeatureStatus.mockReturnValue({
|
||||
...defaultFeatureStatus,
|
||||
isLicenseValid: false,
|
||||
isAuthorized: false,
|
||||
});
|
||||
const { result } = renderHook(() => useRiskScore({ riskEntity }), {
|
||||
wrapper: TestProviders,
|
||||
|
@ -81,7 +81,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
|||
expect(result.current).toEqual({
|
||||
loading: false,
|
||||
...defaultRisk,
|
||||
isLicenseValid: false,
|
||||
isAuthorized: false,
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,7 +40,7 @@ export interface RiskScoreState<T extends RiskScoreEntity.host | RiskScoreEntity
|
|||
refetch: inputsModel.Refetch;
|
||||
totalCount: number;
|
||||
isModuleEnabled: boolean;
|
||||
isLicenseValid: boolean;
|
||||
isAuthorized: boolean;
|
||||
isDeprecated: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us
|
|||
const {
|
||||
isDeprecated,
|
||||
isEnabled,
|
||||
isLicenseValid,
|
||||
isAuthorized,
|
||||
isLoading: isDeprecatedLoading,
|
||||
refetch: refetchDeprecated,
|
||||
} = useRiskScoreFeatureStatus(riskEntity, defaultIndex);
|
||||
|
@ -136,20 +136,12 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us
|
|||
inspect,
|
||||
refetch: refetchAll,
|
||||
totalCount: response.totalCount,
|
||||
isLicenseValid,
|
||||
isAuthorized,
|
||||
isDeprecated,
|
||||
isModuleEnabled: isEnabled,
|
||||
isInspected: false,
|
||||
}),
|
||||
[
|
||||
inspect,
|
||||
isDeprecated,
|
||||
isEnabled,
|
||||
isLicenseValid,
|
||||
refetchAll,
|
||||
response.data,
|
||||
response.totalCount,
|
||||
]
|
||||
[inspect, isDeprecated, isEnabled, isAuthorized, refetchAll, response.data, response.totalCount]
|
||||
);
|
||||
|
||||
const requestTimerange = useMemo(
|
||||
|
@ -205,21 +197,13 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us
|
|||
!skip &&
|
||||
!isDeprecatedLoading &&
|
||||
riskScoreRequest != null &&
|
||||
isLicenseValid &&
|
||||
isAuthorized &&
|
||||
isEnabled &&
|
||||
!isDeprecated
|
||||
) {
|
||||
search(riskScoreRequest);
|
||||
}
|
||||
}, [
|
||||
isEnabled,
|
||||
isDeprecated,
|
||||
isLicenseValid,
|
||||
isDeprecatedLoading,
|
||||
riskScoreRequest,
|
||||
search,
|
||||
skip,
|
||||
]);
|
||||
}, [isEnabled, isDeprecated, isAuthorized, isDeprecatedLoading, riskScoreRequest, search, skip]);
|
||||
|
||||
return { ...riskScoreResponse, loading: loading || isDeprecatedLoading };
|
||||
};
|
||||
|
|
|
@ -11,19 +11,23 @@ import { useRiskScoreFeatureStatus } from '.';
|
|||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { useFetch } from '../../../../common/hooks/use_fetch';
|
||||
import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities';
|
||||
import { useHasSecurityCapability } from '../../../../helper_hooks';
|
||||
|
||||
jest.mock('../../../../common/hooks/use_fetch');
|
||||
jest.mock('../../../../common/components/ml/hooks/use_ml_capabilities');
|
||||
jest.mock('../../../../helper_hooks');
|
||||
|
||||
const mockFetch = jest.fn();
|
||||
const mockUseMlCapabilities = useMlCapabilities as jest.Mock;
|
||||
const mockUseFetch = useFetch as jest.Mock;
|
||||
const mockUseHasSecurityCapability = useHasSecurityCapability as jest.Mock;
|
||||
|
||||
describe(`risk score feature status`, () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true });
|
||||
mockUseFetch.mockReturnValue(defaultFetch);
|
||||
mockUseHasSecurityCapability.mockReturnValue(true);
|
||||
});
|
||||
|
||||
const defaultFetch = {
|
||||
|
@ -36,7 +40,7 @@ describe(`risk score feature status`, () => {
|
|||
const defaultResult = {
|
||||
error: undefined,
|
||||
isDeprecated: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
isEnabled: true,
|
||||
isLoading: true,
|
||||
};
|
||||
|
@ -52,7 +56,25 @@ describe(`risk score feature status`, () => {
|
|||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
expect(result.current).toEqual({
|
||||
...defaultResult,
|
||||
isLicenseValid: false,
|
||||
isAuthorized: false,
|
||||
isDeprecated: false,
|
||||
isEnabled: false,
|
||||
refetch: result.current.refetch,
|
||||
});
|
||||
});
|
||||
|
||||
test("does not search if the user doesn't has entity analytics capability", () => {
|
||||
mockUseHasSecurityCapability.mockReturnValue(false);
|
||||
const { result } = renderHook(
|
||||
() => useRiskScoreFeatureStatus(RiskScoreEntity.host, 'the_right_one'),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
expect(result.current).toEqual({
|
||||
...defaultResult,
|
||||
isAuthorized: false,
|
||||
isDeprecated: false,
|
||||
isEnabled: false,
|
||||
refetch: result.current.refetch,
|
||||
|
|
|
@ -10,15 +10,16 @@ import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml
|
|||
import { REQUEST_NAMES, useFetch } from '../../../../common/hooks/use_fetch';
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { getRiskScoreIndexStatus } from './api';
|
||||
import { useHasSecurityCapability } from '../../../../helper_hooks';
|
||||
|
||||
interface RiskScoresFeatureStatus {
|
||||
error: unknown;
|
||||
// Is transform index an old version?
|
||||
isDeprecated: boolean;
|
||||
// does the transform index exist?
|
||||
// Does the transform index exist?
|
||||
isEnabled: boolean;
|
||||
// is the user's license platinum?
|
||||
isLicenseValid: boolean;
|
||||
// Does the user has the authorization for the risk score feature?
|
||||
isAuthorized: boolean;
|
||||
isLoading: boolean;
|
||||
refetch: (indexName: string) => void;
|
||||
}
|
||||
|
@ -28,6 +29,8 @@ export const useRiskScoreFeatureStatus = (
|
|||
defaultIndex?: string
|
||||
): RiskScoresFeatureStatus => {
|
||||
const { isPlatinumOrTrialLicense, capabilitiesFetched } = useMlCapabilities();
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
const isAuthorized = isPlatinumOrTrialLicense && hasEntityAnalyticsCapability;
|
||||
|
||||
const { fetch, data, isLoading, error } = useFetch(
|
||||
REQUEST_NAMES.GET_RISK_SCORE_DEPRECATED,
|
||||
|
@ -35,35 +38,36 @@ export const useRiskScoreFeatureStatus = (
|
|||
);
|
||||
|
||||
const response = useMemo(
|
||||
// if license is enabled, let isDeprecated = true so the actual
|
||||
// if authorized is true, let isDeprecated = true so the actual
|
||||
// risk score fetch is not called until this check is complete
|
||||
() =>
|
||||
data ? data : { isDeprecated: isPlatinumOrTrialLicense, isEnabled: isPlatinumOrTrialLicense },
|
||||
// isPlatinumOrTrialLicense is initial state, not update requirement
|
||||
() => (data ? data : { isDeprecated: isAuthorized, isEnabled: isAuthorized }),
|
||||
// isAuthorized is initial state, not update requirement
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[data]
|
||||
);
|
||||
|
||||
const searchIndexStatus = useCallback(
|
||||
(indexName: string) => {
|
||||
fetch({
|
||||
query: { indexName, entity: riskEntity },
|
||||
});
|
||||
if (isAuthorized) {
|
||||
fetch({
|
||||
query: { indexName, entity: riskEntity },
|
||||
});
|
||||
}
|
||||
},
|
||||
[riskEntity, fetch]
|
||||
[isAuthorized, fetch, riskEntity]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlatinumOrTrialLicense && defaultIndex != null) {
|
||||
if (defaultIndex != null) {
|
||||
searchIndexStatus(defaultIndex);
|
||||
}
|
||||
}, [isPlatinumOrTrialLicense, defaultIndex, searchIndexStatus]);
|
||||
}, [defaultIndex, searchIndexStatus]);
|
||||
|
||||
return {
|
||||
error,
|
||||
isLoading: isLoading || !capabilitiesFetched || defaultIndex == null,
|
||||
refetch: searchIndexStatus,
|
||||
isLicenseValid: isPlatinumOrTrialLicense,
|
||||
isAuthorized,
|
||||
...response,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -50,6 +50,11 @@ jest.mock('../../../../common/components/ml/hooks/use_ml_capabilities', () => ({
|
|||
useMlCapabilities: () => mockUseMlCapabilities(),
|
||||
}));
|
||||
|
||||
const mockUseHasSecurityCapability = jest.fn().mockReturnValue(false);
|
||||
jest.mock('../../../../helper_hooks', () => ({
|
||||
useHasSecurityCapability: () => mockUseHasSecurityCapability(),
|
||||
}));
|
||||
|
||||
describe('Hosts Table', () => {
|
||||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
@ -84,8 +89,9 @@ describe('Hosts Table', () => {
|
|||
expect(wrapper.find('HostsTable')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders "Host Risk classfication" column when "isPlatinumOrTrialLicense" is truthy', () => {
|
||||
test('it renders "Host Risk classification" column when "isPlatinumOrTrialLicense" is truthy and user has risk-entity capability', () => {
|
||||
mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true });
|
||||
mockUseHasSecurityCapability.mockReturnValue(true);
|
||||
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders store={store}>
|
||||
|
@ -107,8 +113,33 @@ describe('Hosts Table', () => {
|
|||
expect(queryByTestId('tableHeaderCell_node.risk_4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("it doesn't renders 'Host Risk classfication' column when 'isPlatinumOrTrialLicense' is falsy", () => {
|
||||
test("it doesn't renders 'Host Risk classification' column when 'isPlatinumOrTrialLicense' is falsy", () => {
|
||||
mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: false });
|
||||
mockUseHasSecurityCapability.mockReturnValue(true);
|
||||
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders store={store}>
|
||||
<HostsTable
|
||||
id="hostsQuery"
|
||||
isInspect={false}
|
||||
loading={false}
|
||||
data={mockData}
|
||||
totalCount={0}
|
||||
fakeTotalCount={-1}
|
||||
setQuerySkip={jest.fn()}
|
||||
showMorePagesIndicator={false}
|
||||
loadPage={loadPage}
|
||||
type={hostsModel.HostsType.page}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByTestId('tableHeaderCell_node.riskScore_4')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("it doesn't renders 'Host Risk classification' column when user doesn't has entity-analytics capabilities", () => {
|
||||
mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true });
|
||||
mockUseHasSecurityCapability.mockReturnValue(false);
|
||||
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders store={store}>
|
||||
|
|
|
@ -31,6 +31,7 @@ import { SecurityPageName } from '../../../../../common/constants';
|
|||
import { HostsTableType } from '../../store/model';
|
||||
import { useNavigateTo } from '../../../../common/lib/kibana/hooks';
|
||||
import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities';
|
||||
import { useHasSecurityCapability } from '../../../../helper_hooks';
|
||||
|
||||
const tableType = hostsModel.HostsTableType.hosts;
|
||||
|
||||
|
@ -132,6 +133,8 @@ const HostsTableComponent: React.FC<HostsTableProps> = ({
|
|||
},
|
||||
[direction, sortField, type, dispatch]
|
||||
);
|
||||
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
|
||||
const dispatchSeverityUpdate = useCallback(
|
||||
|
@ -151,8 +154,12 @@ const HostsTableComponent: React.FC<HostsTableProps> = ({
|
|||
);
|
||||
|
||||
const hostsColumns = useMemo(
|
||||
() => getHostsColumns(isPlatinumOrTrialLicense, dispatchSeverityUpdate),
|
||||
[dispatchSeverityUpdate, isPlatinumOrTrialLicense]
|
||||
() =>
|
||||
getHostsColumns(
|
||||
isPlatinumOrTrialLicense && hasEntityAnalyticsCapability,
|
||||
dispatchSeverityUpdate
|
||||
),
|
||||
[dispatchSeverityUpdate, isPlatinumOrTrialLicense, hasEntityAnalyticsCapability]
|
||||
);
|
||||
|
||||
const sorting = useMemo(() => getSorting(sortField, direction), [sortField, direction]);
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
import { useQueryToggle } from '../../../../common/containers/query_toggle';
|
||||
import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoresNoDataDetected } from '../../../components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { useUpsellingComponent } from '../../../../common/hooks/use_upselling';
|
||||
|
||||
const HostRiskScoreTableManage = manageQuery(HostRiskScoreTable);
|
||||
|
||||
|
@ -35,7 +34,6 @@ export const HostRiskScoreQueryTabBody = ({
|
|||
startDate: from,
|
||||
type,
|
||||
}: HostsComponentsQueryProps) => {
|
||||
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
|
||||
const getHostRiskScoreSelector = useMemo(() => hostsSelectors.hostRiskScoreSelector(), []);
|
||||
const { activePage, limit, sort } = useDeepEqualSelector((state: State) =>
|
||||
getHostRiskScoreSelector(state, hostsModel.HostsType.page)
|
||||
|
@ -71,6 +69,7 @@ export const HostRiskScoreQueryTabBody = ({
|
|||
isModuleEnabled,
|
||||
loading,
|
||||
refetch,
|
||||
isAuthorized,
|
||||
totalCount,
|
||||
} = useRiskScore({
|
||||
filterQuery,
|
||||
|
@ -92,8 +91,8 @@ export const HostRiskScoreQueryTabBody = ({
|
|||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
if (RiskScoreUpsell) {
|
||||
return <RiskScoreUpsell />;
|
||||
if (!isAuthorized) {
|
||||
return <>{'TODO: Add RiskScore Upsell'}</>;
|
||||
}
|
||||
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
import { useQueryToggle } from '../../../../common/containers/query_toggle';
|
||||
import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { RiskScoresNoDataDetected } from '../../../components/risk_score/risk_score_onboarding/risk_score_no_data_detected';
|
||||
import { useUpsellingComponent } from '../../../../common/hooks/use_upselling';
|
||||
|
||||
const UserRiskScoreTableManage = manageQuery(UserRiskScoreTable);
|
||||
|
||||
|
@ -37,8 +36,6 @@ export const UserRiskScoreQueryTabBody = ({
|
|||
startDate: from,
|
||||
type,
|
||||
}: UsersComponentsQueryProps) => {
|
||||
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
|
||||
|
||||
const getUserRiskScoreSelector = useMemo(() => usersSelectors.userRiskScoreSelector(), []);
|
||||
const { activePage, limit, sort } = useDeepEqualSelector((state: State) =>
|
||||
getUserRiskScoreSelector(state)
|
||||
|
@ -75,6 +72,7 @@ export const UserRiskScoreQueryTabBody = ({
|
|||
loading,
|
||||
refetch,
|
||||
totalCount,
|
||||
isAuthorized,
|
||||
} = useRiskScore({
|
||||
filterQuery,
|
||||
pagination,
|
||||
|
@ -95,8 +93,8 @@ export const UserRiskScoreQueryTabBody = ({
|
|||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
if (RiskScoreUpsell) {
|
||||
return <RiskScoreUpsell />;
|
||||
if (!isAuthorized) {
|
||||
return <>{'TODO: Add RiskScore Upsell'}</>;
|
||||
}
|
||||
|
||||
if (status.isDisabled || status.isDeprecated) {
|
||||
|
|
|
@ -55,6 +55,11 @@ jest.mock('uuid', () => ({
|
|||
jest.mock('../../../common/components/ml/hooks/use_ml_capabilities');
|
||||
const mockUseMlUserPermissions = useMlCapabilities as jest.Mock;
|
||||
|
||||
const mockUseHasSecurityCapability = jest.fn().mockReturnValue(false);
|
||||
jest.mock('../../../helper_hooks', () => ({
|
||||
useHasSecurityCapability: () => mockUseHasSecurityCapability(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../common/containers/sourcerer', () => ({
|
||||
useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['index'] }),
|
||||
}));
|
||||
|
@ -105,7 +110,7 @@ const mockRiskScoreResponse = {
|
|||
},
|
||||
},
|
||||
],
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
};
|
||||
|
||||
const mockRelatedUsersResponse = {
|
||||
|
@ -150,11 +155,13 @@ describe('<HostDetails />', () => {
|
|||
expect(getByTestId(HOST_DETAILS_INFO_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render host risk score when license is valid', () => {
|
||||
it('should render host risk score when authorized', () => {
|
||||
mockUseMlUserPermissions.mockReturnValue({
|
||||
isPlatinumOrTrialLicense: true,
|
||||
capabilities: {},
|
||||
});
|
||||
mockUseRiskScore.mockReturnValue({ data: [], isAuthorized: true });
|
||||
|
||||
const { getByText } = render(
|
||||
<TestProviders>
|
||||
<HostDetails {...defaultProps} />
|
||||
|
@ -163,8 +170,8 @@ describe('<HostDetails />', () => {
|
|||
expect(getByText('Host risk score')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render host risk score when license is not valid', () => {
|
||||
mockUseRiskScore.mockReturnValue({ data: [], isLicenseValid: false });
|
||||
it('should not render host risk score when unauthorized', () => {
|
||||
mockUseRiskScore.mockReturnValue({ data: [], isAuthorized: false });
|
||||
const { queryByText } = render(
|
||||
<TestProviders>
|
||||
<HostDetails {...defaultProps} />
|
||||
|
@ -190,11 +197,13 @@ describe('<HostDetails />', () => {
|
|||
expect(getByTestId(HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render user risk score column when license is valid', () => {
|
||||
it('should render user risk score column when license and capabilities are valid', () => {
|
||||
mockUseMlUserPermissions.mockReturnValue({
|
||||
isPlatinumOrTrialLicense: true,
|
||||
capabilities: {},
|
||||
});
|
||||
mockUseHasSecurityCapability.mockReturnValue(true);
|
||||
|
||||
const { queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
<HostDetails {...defaultProps} />
|
||||
|
@ -206,6 +215,21 @@ describe('<HostDetails />', () => {
|
|||
expect(queryAllByRole('row')[1].textContent).toContain('Low');
|
||||
});
|
||||
|
||||
it('should not render host risk score column when user has no entity-risk capability', () => {
|
||||
mockUseMlUserPermissions.mockReturnValue({
|
||||
isPlatinumOrTrialLicense: true,
|
||||
capabilities: {},
|
||||
});
|
||||
mockUseHasSecurityCapability.mockReturnValue(false);
|
||||
|
||||
const { queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
<HostDetails {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(queryAllByRole('columnheader').length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not render host risk score column when license is not valid', () => {
|
||||
const { queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
|
|
|
@ -50,6 +50,7 @@ import { HOST_DETAILS_TEST_ID, HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID } from '
|
|||
import { ENTITY_RISK_CLASSIFICATION } from '../../../explore/components/risk_score/translations';
|
||||
import { USER_RISK_TOOLTIP } from '../../../explore/users/components/all_users/translations';
|
||||
import * as i18n from './translations';
|
||||
import { useHasSecurityCapability } from '../../../helper_hooks';
|
||||
|
||||
const HOST_DETAILS_ID = 'entities-hosts-details';
|
||||
const RELATED_USERS_ID = 'entities-hosts-related-users';
|
||||
|
@ -77,7 +78,10 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp })
|
|||
// create a unique, but stable (across re-renders) query id
|
||||
const hostDetailsQueryId = useMemo(() => `${HOST_DETAILS_ID}-${uuid()}`, []);
|
||||
const relatedUsersQueryId = useMemo(() => `${RELATED_USERS_ID}-${uuid()}`, []);
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
const isEntityAnalyticsAuthorized = isPlatinumOrTrialLicense && hasEntityAnalyticsCapability;
|
||||
|
||||
const narrowDateRange = useCallback(
|
||||
(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
|
@ -151,7 +155,7 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp })
|
|||
);
|
||||
},
|
||||
},
|
||||
...(isPlatinumOrTrialLicense
|
||||
...(isEntityAnalyticsAuthorized
|
||||
? [
|
||||
{
|
||||
field: 'risk',
|
||||
|
@ -176,7 +180,7 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp })
|
|||
]
|
||||
: []),
|
||||
],
|
||||
[isPlatinumOrTrialLicense]
|
||||
[isEntityAnalyticsAuthorized]
|
||||
);
|
||||
|
||||
const relatedUsersCount = useMemo(
|
||||
|
|
|
@ -71,6 +71,8 @@ jest.mock('../../../common/components/ml/anomaly/anomaly_table_provider', () =>
|
|||
}) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false, jobNameById: {} }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../helper_hooks', () => ({ useHasSecurityCapability: () => true }));
|
||||
|
||||
jest.mock('../../../explore/users/containers/users/observed_details');
|
||||
const mockUseObservedUserDetails = useObservedUserDetails as jest.Mock;
|
||||
|
||||
|
@ -105,7 +107,7 @@ const mockRiskScoreResponse = {
|
|||
},
|
||||
},
|
||||
],
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
};
|
||||
|
||||
const mockRelatedHostsResponse = {
|
||||
|
@ -165,7 +167,7 @@ describe('<HostDetails />', () => {
|
|||
});
|
||||
|
||||
it('should not render user risk score when license is not valid', () => {
|
||||
mockUseRiskScore.mockReturnValue({ data: [], isLicenseValid: false });
|
||||
mockUseRiskScore.mockReturnValue({ data: [], isAuthorized: false });
|
||||
const { queryByText } = render(
|
||||
<TestProviders>
|
||||
<UserDetails {...defaultProps} />
|
||||
|
|
|
@ -50,6 +50,7 @@ import { USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID, USER_DETAILS_TEST_ID } from '
|
|||
import { ENTITY_RISK_CLASSIFICATION } from '../../../explore/components/risk_score/translations';
|
||||
import { HOST_RISK_TOOLTIP } from '../../../explore/hosts/components/hosts_table/translations';
|
||||
import * as i18n from './translations';
|
||||
import { useHasSecurityCapability } from '../../../helper_hooks';
|
||||
|
||||
const USER_DETAILS_ID = 'entities-users-details';
|
||||
const RELATED_HOSTS_ID = 'entities-users-related-hosts';
|
||||
|
@ -77,7 +78,11 @@ export const UserDetails: React.FC<UserDetailsProps> = ({ userName, timestamp })
|
|||
// create a unique, but stable (across re-renders) query id
|
||||
const userDetailsQueryId = useMemo(() => `${USER_DETAILS_ID}-${uuid()}`, []);
|
||||
const relatedHostsQueryId = useMemo(() => `${RELATED_HOSTS_ID}-${uuid()}`, []);
|
||||
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense;
|
||||
const isEntityAnalyticsAuthorized = isPlatinumOrTrialLicense && hasEntityAnalyticsCapability;
|
||||
|
||||
const narrowDateRange = useCallback(
|
||||
(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
|
@ -151,7 +156,7 @@ export const UserDetails: React.FC<UserDetailsProps> = ({ userName, timestamp })
|
|||
);
|
||||
},
|
||||
},
|
||||
...(isPlatinumOrTrialLicense
|
||||
...(isEntityAnalyticsAuthorized
|
||||
? [
|
||||
{
|
||||
field: 'risk',
|
||||
|
@ -176,7 +181,7 @@ export const UserDetails: React.FC<UserDetailsProps> = ({ userName, timestamp })
|
|||
]
|
||||
: []),
|
||||
],
|
||||
[isPlatinumOrTrialLicense]
|
||||
[isEntityAnalyticsAuthorized]
|
||||
);
|
||||
|
||||
const relatedHostsCount = useMemo(
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('<HostEntityContent />', () => {
|
|||
describe('license is valid', () => {
|
||||
it('should render ip addresses and host risk classification', () => {
|
||||
mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isLicenseValid: true });
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true });
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
|
@ -63,7 +63,7 @@ describe('<HostEntityContent />', () => {
|
|||
|
||||
it('should render correctly if returned data is null', () => {
|
||||
mockUseHostDetails.mockReturnValue([false, { hostDetails: null }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isLicenseValid: true });
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true });
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
|
@ -79,7 +79,7 @@ describe('<HostEntityContent />', () => {
|
|||
describe('license is not valid', () => {
|
||||
it('should render ip but not host risk classification', () => {
|
||||
mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isLicenseValid: false });
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: false });
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<HostEntityOverview hostName={hostName} />
|
||||
|
@ -92,7 +92,7 @@ describe('<HostEntityContent />', () => {
|
|||
|
||||
it('should render correctly if returned data is null', () => {
|
||||
mockUseHostDetails.mockReturnValue([false, { hostDetails: null }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isLicenseValid: false });
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: false });
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<HostEntityOverview hostName={hostName} />
|
||||
|
|
|
@ -66,7 +66,7 @@ export const HostEntityOverview: React.FC<HostEntityOverviewProps> = ({ hostName
|
|||
[hostName]
|
||||
);
|
||||
|
||||
const { data: hostRisk, isLicenseValid } = useRiskScore({
|
||||
const { data: hostRisk, isAuthorized } = useRiskScore({
|
||||
filterQuery,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
skip: hostName == null,
|
||||
|
@ -138,7 +138,7 @@ export const HostEntityOverview: React.FC<HostEntityOverviewProps> = ({ hostName
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{isLicenseValid && (
|
||||
{isAuthorized && (
|
||||
<DescriptionListStyled
|
||||
data-test-subj={ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID}
|
||||
listItems={[hostRiskLevel]}
|
||||
|
|
|
@ -49,7 +49,7 @@ describe('<UserEntityOverview />', () => {
|
|||
describe('license is valid', () => {
|
||||
it('should render ip addresses and user risk classification', () => {
|
||||
mockUseUserDetails.mockReturnValue([false, { userDetails: userData }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isLicenseValid: true });
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true });
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
|
@ -64,7 +64,7 @@ describe('<UserEntityOverview />', () => {
|
|||
|
||||
it('should render correctly if returned data is null', () => {
|
||||
mockUseUserDetails.mockReturnValue([false, { userDetails: null }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isLicenseValid: true });
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true });
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
|
@ -80,7 +80,7 @@ describe('<UserEntityOverview />', () => {
|
|||
describe('license is not valid', () => {
|
||||
it('should render ip but not user risk classification', () => {
|
||||
mockUseUserDetails.mockReturnValue([false, { userDetails: userData }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isLicenseValid: false });
|
||||
mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: false });
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<UserEntityOverview userName={userName} />
|
||||
|
@ -93,7 +93,7 @@ describe('<UserEntityOverview />', () => {
|
|||
|
||||
it('should render correctly if returned data is null', () => {
|
||||
mockUseUserDetails.mockReturnValue([false, { userDetails: null }]);
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isLicenseValid: false });
|
||||
mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: false });
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<UserEntityOverview userName={userName} />
|
||||
|
|
|
@ -73,7 +73,7 @@ export const UserEntityOverview: React.FC<UserEntityOverviewProps> = ({ userName
|
|||
startDate: from,
|
||||
});
|
||||
|
||||
const { data: userRisk, isLicenseValid } = useRiskScore({
|
||||
const { data: userRisk, isAuthorized } = useRiskScore({
|
||||
filterQuery,
|
||||
riskEntity: RiskScoreEntity.user,
|
||||
timerange,
|
||||
|
@ -138,7 +138,7 @@ export const UserEntityOverview: React.FC<UserEntityOverviewProps> = ({ userName
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{isLicenseValid && (
|
||||
{isAuthorized && (
|
||||
<DescriptionListStyled
|
||||
data-test-subj={ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID}
|
||||
listItems={[userRiskLevel]}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useKibana } from './common/lib/kibana';
|
||||
|
||||
export const useOnOpenCloseHandler = (): [boolean, () => void, () => void] => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
@ -19,3 +20,12 @@ export const useOnOpenCloseHandler = (): [boolean, () => void, () => void] => {
|
|||
}, []);
|
||||
return [isOpen, handleOnOpen, handleOnClose];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param capability Main Security feature capability name.
|
||||
*/
|
||||
export const useHasSecurityCapability = (capability: string): boolean => {
|
||||
const { capabilities } = useKibana().services.application;
|
||||
return !!capabilities.siem[capability];
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ const defaultProps = {
|
|||
inspect: null,
|
||||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
loading: false,
|
||||
};
|
||||
const mockUseRiskScore = useRiskScore as jest.Mock;
|
||||
|
@ -156,7 +156,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
|||
{
|
||||
'@timestamp': '1234567899',
|
||||
[riskEntity]: {
|
||||
name: 'testUsermame',
|
||||
name: 'testUsername',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_level: RiskSeverity.high,
|
||||
|
|
|
@ -111,7 +111,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
inspect,
|
||||
refetch,
|
||||
isDeprecated,
|
||||
isLicenseValid,
|
||||
isAuthorized,
|
||||
isModuleEnabled,
|
||||
} = useRiskScore({
|
||||
filterQuery,
|
||||
|
@ -140,7 +140,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
|
||||
const refreshPage = useRefetchQueries();
|
||||
|
||||
if (!isLicenseValid) {
|
||||
if (!isAuthorized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const defaultProps = {
|
|||
inspect: null,
|
||||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
|
@ -88,7 +88,7 @@ describe('Host Summary Component', () => {
|
|||
data: [
|
||||
{
|
||||
host: {
|
||||
name: 'testHostmame',
|
||||
name: 'testHostname',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_score_norm: riskScore,
|
||||
|
|
|
@ -98,7 +98,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
}),
|
||||
[from, to]
|
||||
);
|
||||
const { data: hostRisk, isLicenseValid } = useRiskScore({
|
||||
const { data: hostRisk, isAuthorized } = useRiskScore({
|
||||
filterQuery,
|
||||
riskEntity: RiskScoreEntity.host,
|
||||
skip: hostName == null,
|
||||
|
@ -297,7 +297,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
)}
|
||||
</OverviewWrapper>
|
||||
</InspectButtonContainer>
|
||||
{isLicenseValid && (
|
||||
{isAuthorized && (
|
||||
<HostRiskOverviewWrapper
|
||||
gutterSize={isInDetailsSidePanel ? 'm' : 'none'}
|
||||
direction={isInDetailsSidePanel ? 'column' : 'row'}
|
||||
|
|
|
@ -21,7 +21,7 @@ const defaultProps = {
|
|||
inspect: null,
|
||||
refetch: () => {},
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
|
@ -101,7 +101,7 @@ describe('User Summary Component', () => {
|
|||
data: [
|
||||
{
|
||||
user: {
|
||||
name: 'testUsermame',
|
||||
name: 'testUsername',
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_level: risk,
|
||||
|
|
|
@ -98,7 +98,7 @@ export const UserOverview = React.memo<UserSummaryProps>(
|
|||
[from, to]
|
||||
);
|
||||
|
||||
const { data: userRisk, isLicenseValid } = useRiskScore({
|
||||
const { data: userRisk, isAuthorized } = useRiskScore({
|
||||
filterQuery,
|
||||
skip: userName == null,
|
||||
timerange,
|
||||
|
@ -291,7 +291,7 @@ export const UserOverview = React.memo<UserSummaryProps>(
|
|||
)}
|
||||
</OverviewWrapper>
|
||||
</InspectButtonContainer>
|
||||
{isLicenseValid && (
|
||||
{isAuthorized && (
|
||||
<UserRiskOverviewWrapper
|
||||
gutterSize={isInDetailsSidePanel ? 'm' : 'none'}
|
||||
direction={isInDetailsSidePanel ? 'column' : 'row'}
|
||||
|
|
|
@ -88,7 +88,7 @@ export const entityAnalyticsLinks: LinkItem = {
|
|||
'Entity analytics, anomalies, and threats to narrow down the monitoring surface area.',
|
||||
}),
|
||||
path: ENTITY_ANALYTICS_PATH,
|
||||
capabilities: [`${SERVER_APP_ID}.entity-analytics`],
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
isBeta: false,
|
||||
globalSearchKeywords: [ENTITY_ANALYTICS],
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ export const mockRiskScoreState = {
|
|||
refetch: () => {},
|
||||
totalCount: 0,
|
||||
isModuleEnabled: true,
|
||||
isLicenseValid: true,
|
||||
isAuthorized: true,
|
||||
isDeprecated: false,
|
||||
loading: false,
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('RiskScoreField', () => {
|
|||
it('does not render content when the license is invalid', () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<RiskScoreField riskScoreState={{ ...mockRiskScoreState, isLicenseValid: false }} />
|
||||
<RiskScoreField riskScoreState={{ ...mockRiskScoreState, isAuthorized: false }} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
|
|
@ -25,10 +25,10 @@ export const RiskScoreField = ({
|
|||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { fontSize: xsFontSize } = useEuiFontSize('xs');
|
||||
const { data: userRisk, isLicenseValid: isRiskLicenseValid } = riskScoreState;
|
||||
const { data: userRisk, isAuthorized: isRiskScoreAuthorized } = riskScoreState;
|
||||
const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined;
|
||||
|
||||
if (!isRiskLicenseValid) {
|
||||
if (!isRiskScoreAuthorized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { lazy } from 'react';
|
||||
import { SecurityPageName } from '@kbn/security-solution-navigation';
|
||||
import { AppFeatureKey } from '@kbn/security-solution-plugin/common';
|
||||
import type { SecurityPageName, AppFeatureKey } from '@kbn/security-solution-plugin/common';
|
||||
import type {
|
||||
UpsellingService,
|
||||
PageUpsellings,
|
||||
|
@ -16,9 +14,6 @@ import type {
|
|||
import type { SecurityProductTypes } from '../../common/config';
|
||||
import { getProductAppFeatures } from '../../common/pli/pli_features';
|
||||
|
||||
const GenericUpsellingPageLazy = lazy(() => import('./pages/generic_upselling_page'));
|
||||
const GenericUpsellingSectionLazy = lazy(() => import('./pages/generic_upselling_section'));
|
||||
|
||||
interface UpsellingsConfig {
|
||||
pli: AppFeatureKey;
|
||||
component: React.ComponentType;
|
||||
|
@ -59,18 +54,22 @@ export const registerUpsellings = (
|
|||
|
||||
// Upsellings for entire pages, linked to a SecurityPageName
|
||||
export const upsellingPages: UpsellingPages = [
|
||||
{
|
||||
pageName: SecurityPageName.entityAnalytics,
|
||||
pli: AppFeatureKey.advancedInsights,
|
||||
component: () => <GenericUpsellingPageLazy requiredPLI={AppFeatureKey.advancedInsights} />,
|
||||
},
|
||||
// Sample code for registering a Upselling page
|
||||
// Make sure the component is lazy loaded `const GenericUpsellingPageLazy = lazy(() => import('./pages/generic_upselling_page'));`
|
||||
// {
|
||||
// pageName: SecurityPageName.entityAnalytics,
|
||||
// pli: AppFeatureKey.advancedInsights,
|
||||
// component: () => <GenericUpsellingPageLazy requiredPLI={AppFeatureKey.advancedInsights} />,
|
||||
// },
|
||||
];
|
||||
|
||||
// Upsellings for sections, linked by arbitrary ids
|
||||
export const upsellingSections: UpsellingSections = [
|
||||
{
|
||||
id: 'entity_analytics_panel',
|
||||
pli: AppFeatureKey.advancedInsights,
|
||||
component: () => <GenericUpsellingSectionLazy requiredPLI={AppFeatureKey.advancedInsights} />,
|
||||
},
|
||||
// Sample code for registering a Upselling section
|
||||
// Make sure the component is lazy loaded `const GenericUpsellingSectionLazy = lazy(() => import('./pages/generic_upselling_section'));`
|
||||
// {
|
||||
// id: 'entity_analytics_panel',
|
||||
// pli: AppFeatureKey.advancedInsights,
|
||||
// component: () => <GenericUpsellingSectionLazy requiredPLI={AppFeatureKey.advancedInsights} />,
|
||||
// },
|
||||
];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue