[9.0] [Security Solution][Endpoint] Disable Artifact card menu under space awareness conditions where user is not allowed to edit item under active space (#213820) (#214992)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Security Solution][Endpoint] Disable Artifact card menu under space
awareness conditions where user is not allowed to edit item under active
space (#213820)](https://github.com/elastic/kibana/pull/213820)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Paul
Tavares","email":"56442535+paul-tavares@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-13T12:52:33Z","message":"[Security
Solution][Endpoint] Disable Artifact card menu under space awareness
conditions where user is not allowed to edit item under active space
(#213820)\n\n## Summary\n\nThe following changes are being done to
Artifact Card's Menu (which\ndisplays the option to Delete or Update the
artifact) in support of\nspace awareness feature (currently behind
Feature Flag:\n`endpointManagementSpaceAwarenessEnabled`):\n\n- Global
Artifacts: If displaying a global artifact and user does not\nhave the
new Global Artifact Management privilege - disable the Edit\nmenu icon
and display a tooltip on hover\n- Per-Policy Artifacts: if displaying a
per-policy artifact in a space\nother than one of the `ownerSpaceId`
spaces that the artifact is\nassociated with and the user does not have
the new Global Artifact\nManagement privilege - disable the Edit menu
icon and display a tooltip\nwhen the user hover over that button\n\n\n>
[!NOTE]\n> Changes were **NOT** done to Endpoint Exceptions with this
PR.","sha":"2b9d2cff6cb9edd0fe639e82f8fe2e46591c7f0c","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport
missing","Team:Defend
Workflows","backport:prev-minor","v9.1.0"],"title":"[Security
Solution][Endpoint] Disable Artifact card menu under space awareness
conditions where user is not allowed to edit item under active
space","number":213820,"url":"https://github.com/elastic/kibana/pull/213820","mergeCommit":{"message":"[Security
Solution][Endpoint] Disable Artifact card menu under space awareness
conditions where user is not allowed to edit item under active space
(#213820)\n\n## Summary\n\nThe following changes are being done to
Artifact Card's Menu (which\ndisplays the option to Delete or Update the
artifact) in support of\nspace awareness feature (currently behind
Feature Flag:\n`endpointManagementSpaceAwarenessEnabled`):\n\n- Global
Artifacts: If displaying a global artifact and user does not\nhave the
new Global Artifact Management privilege - disable the Edit\nmenu icon
and display a tooltip on hover\n- Per-Policy Artifacts: if displaying a
per-policy artifact in a space\nother than one of the `ownerSpaceId`
spaces that the artifact is\nassociated with and the user does not have
the new Global Artifact\nManagement privilege - disable the Edit menu
icon and display a tooltip\nwhen the user hover over that button\n\n\n>
[!NOTE]\n> Changes were **NOT** done to Endpoint Exceptions with this
PR.","sha":"2b9d2cff6cb9edd0fe639e82f8fe2e46591c7f0c"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213820","number":213820,"mergeCommit":{"message":"[Security
Solution][Endpoint] Disable Artifact card menu under space awareness
conditions where user is not allowed to edit item under active space
(#213820)\n\n## Summary\n\nThe following changes are being done to
Artifact Card's Menu (which\ndisplays the option to Delete or Update the
artifact) in support of\nspace awareness feature (currently behind
Feature Flag:\n`endpointManagementSpaceAwarenessEnabled`):\n\n- Global
Artifacts: If displaying a global artifact and user does not\nhave the
new Global Artifact Management privilege - disable the Edit\nmenu icon
and display a tooltip on hover\n- Per-Policy Artifacts: if displaying a
per-policy artifact in a space\nother than one of the `ownerSpaceId`
spaces that the artifact is\nassociated with and the user does not have
the new Global Artifact\nManagement privilege - disable the Edit menu
icon and display a tooltip\nwhen the user hover over that button\n\n\n>
[!NOTE]\n> Changes were **NOT** done to Endpoint Exceptions with this
PR.","sha":"2b9d2cff6cb9edd0fe639e82f8fe2e46591c7f0c"}}]}] BACKPORT-->
This commit is contained in:
Paul Tavares 2025-03-19 01:06:04 -04:00 committed by GitHub
parent 7f7a93e921
commit c04009575a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 329 additions and 69 deletions

View file

@ -27,6 +27,7 @@ import { PLUGIN_ID } from '@kbn/fleet-plugin/common';
import type { UseBaseQueryResult } from '@tanstack/react-query';
import ReactDOM from 'react-dom';
import type { DeepReadonly } from 'utility-types';
import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks';
import type { UserPrivilegesState } from '../../components/user_privileges/user_privileges_context';
import { getUserPrivilegesMockDefaultValue } from '../../components/user_privileges/__mocks__';
import type { AppLinkItems } from '../../links/types';
@ -134,7 +135,7 @@ export interface AppContextTestRender {
store: Store<State>;
history: ReturnType<typeof createMemoryHistory>;
coreStart: ReturnType<typeof coreMock.createStart>;
depsStart: Pick<StartPlugins, 'data' | 'fleet' | 'unifiedSearch'>;
depsStart: Pick<StartPlugins, 'data' | 'fleet' | 'unifiedSearch' | 'spaces'>;
startServices: StartServices;
middlewareSpy: MiddlewareActionSpyHelper;
/**
@ -252,9 +253,20 @@ const experimentalFeaturesReducer: Reducer<State['app'], UpdateExperimentalFeatu
export const createAppRootMockRenderer = (): AppContextTestRender => {
const history = createMemoryHistory<never>();
const coreStart = createCoreStartMock(history);
const depsStart = depsStartMock();
const middlewareSpy = createSpyMiddleware();
const startServices: StartServices = createStartServicesMock(coreStart);
const depsStart: AppContextTestRender['depsStart'] = {
...depsStartMock(),
spaces: spacesPluginMock.createStartContract(),
};
(depsStart.spaces.getActiveSpace as jest.Mock).mockImplementation(async () => {
return {
id: 'default',
name: 'default',
disabledFeatures: [],
};
});
const storeReducer = {
...SUB_PLUGINS_REDUCER,

View file

@ -6,7 +6,7 @@
*/
import type { ReactNode } from 'react';
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { Provider } from 'react-redux';
import { Router } from '@kbn/shared-ux-router';
import type { History } from 'history';
@ -36,15 +36,21 @@ export const AppRootProvider = memo<{
startServices: StartServices;
queryClient: QueryClient;
children: ReactNode | ReactNode[];
}>(({ store, history, coreStart, queryClient, startServices, children }) => {
}>(({ store, history, coreStart, depsStart, queryClient, startServices, children }) => {
const { theme: themeStart } = coreStart;
const theme = useObservable(themeStart.theme$, themeStart.getTheme());
const isDarkMode = theme.darkMode;
const services = useMemo(() => {
return {
...depsStart,
...startServices,
};
}, [depsStart, startServices]);
return (
<KibanaRenderContextProvider {...coreStart}>
<Provider store={store}>
<KibanaContextProvider services={startServices}>
<KibanaContextProvider services={services}>
<EuiThemeProvider darkMode={isDarkMode}>
<QueryClientProvider client={queryClient}>
<UpsellingProvider upsellingService={startServices.upselling}>

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import type { ReactNode } from 'react';
import React, { memo, useCallback, useMemo, useState } from 'react';
import type { EuiPopoverProps, EuiContextMenuPanelProps, EuiIconProps } from '@elastic/eui';
import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import { EuiToolTip, EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { v4 as uuidv4 } from 'uuid';
import type { ContextMenuItemNavByRouterProps } from '../context_menu_with_router_support';
@ -19,13 +20,23 @@ export interface ActionsContextMenuProps {
/** Default icon is `boxesHorizontal` */
icon?: EuiIconProps['type'];
'data-test-subj'?: string;
/** If menu button should be disabled */
isDisabled?: boolean;
/** If defined, then the disabled button will be wrapped in on-hover tooltip */
disabledTooltip?: ReactNode;
}
/**
* Display a context menu behind a icon button (which defaults to the three horizontal dots icon)
*/
export const ActionsContextMenu = memo<ActionsContextMenuProps>(
({ items, 'data-test-subj': dataTestSubj, icon = 'boxesHorizontal' }) => {
({
items,
'data-test-subj': dataTestSubj,
icon = 'boxesHorizontal',
isDisabled = false,
disabledTooltip,
}) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const [isOpen, setIsOpen] = useState(false);
@ -53,22 +64,33 @@ export const ActionsContextMenu = memo<ActionsContextMenuProps>(
});
}, [handleCloseMenu, items]);
const menuButton = useMemo(() => {
const button = (
<EuiButtonIcon
data-test-subj={getTestId('button')}
iconType={icon}
onClick={handleToggleMenu}
isDisabled={isDisabled}
aria-label={i18n.translate('xpack.securitySolution.actionsContextMenu.label', {
defaultMessage: 'Open',
})}
/>
);
if (isDisabled && disabledTooltip) {
return <EuiToolTip content={disabledTooltip}>{button}</EuiToolTip>;
}
return button;
}, [disabledTooltip, getTestId, handleToggleMenu, icon, isDisabled]);
return (
<EuiPopover
anchorPosition="downRight"
panelPaddingSize="none"
panelProps={panelProps}
data-test-subj={dataTestSubj}
button={
<EuiButtonIcon
data-test-subj={getTestId('button')}
iconType={icon}
onClick={handleToggleMenu}
aria-label={i18n.translate('xpack.securitySolution.actionsContextMenu.label', {
defaultMessage: 'Open',
})}
/>
}
button={menuButton}
isOpen={isOpen}
closePopover={handleCloseMenu}
>

View file

@ -6,14 +6,14 @@
*/
import React, { memo } from 'react';
import type { AppContextTestRender } from '../../../common/mock/endpoint';
import type { AppContextTestRender, UserPrivilegesMockSetter } from '../../../common/mock/endpoint';
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
import type {
ArtifactEntryCardDecoratorProps,
ArtifactEntryCardProps,
} from './artifact_entry_card';
import { ArtifactEntryCard } from './artifact_entry_card';
import { act, fireEvent, getByTestId } from '@testing-library/react';
import { act, fireEvent, getByTestId, waitFor } from '@testing-library/react';
import type { AnyArtifact } from './types';
import { isTrustedApp } from './utils';
import { getTrustedAppProviderMock, getExceptionProviderMock } from './test_utils';
@ -21,6 +21,12 @@ import { OS_LINUX, OS_MAC, OS_WINDOWS } from './components/translations';
import type { TrustedApp } from '../../../../common/endpoint/types';
import { useUserPrivileges } from '../../../common/components/user_privileges';
import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks';
import { GLOBAL_ARTIFACT_TAG } from '../../../../common/endpoint/service/artifacts';
import {
buildPerPolicyTag,
buildSpaceOwnerIdTag,
} from '../../../../common/endpoint/service/artifacts/utils';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
jest.mock('../../../common/components/user_privileges');
const mockUserPrivileges = useUserPrivileges as jest.Mock;
@ -286,4 +292,96 @@ describe.each([
expect(passedItem).toBe(item);
});
});
describe('and space awareness is enabled', () => {
let authzMock: UserPrivilegesMockSetter;
let actions: ArtifactEntryCardProps['actions'];
beforeEach(() => {
actions = [
{
'data-test-subj': 'test-action',
children: 'action one',
},
];
});
beforeEach(() => {
appTestContext.setExperimentalFlag({ endpointManagementSpaceAwarenessEnabled: true });
authzMock = appTestContext.getUserPrivilegesMockSetter(mockUserPrivileges);
authzMock.set({ canManageGlobalArtifacts: false });
(item as ExceptionListItemSchema).tags = [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag('foo')];
});
afterEach(() => {
authzMock.reset();
});
it('should render menu if feature flag is disabled', () => {
appTestContext.setExperimentalFlag({ endpointManagementSpaceAwarenessEnabled: false });
render({ actions });
expect(
(renderResult.getByTestId('testCard-header-actions-button') as HTMLButtonElement).disabled
).toBe(false);
});
it('should disable card actions menu for global artifacts when user does not have global artifact privilege', () => {
render({ actions });
expect(
(renderResult.getByTestId('testCard-header-actions-button') as HTMLButtonElement).disabled
).toBe(true);
});
it('should enable card actions menu for global artifacts when user has the global artifact privilege', () => {
authzMock.set({ canManageGlobalArtifacts: true });
render({ actions });
expect(
(renderResult.getByTestId('testCard-header-actions-button') as HTMLButtonElement).disabled
).toBe(false);
});
it('should disable card actions menu for per-policy artifacts not owned by active space', () => {
(item as ExceptionListItemSchema).tags = [
buildPerPolicyTag('abc'),
buildSpaceOwnerIdTag('foo'),
];
render({ actions });
expect(
(renderResult.getByTestId('testCard-header-actions-button') as HTMLButtonElement).disabled
).toBe(true);
});
it('should enable card actions menu for per-policy artifacts when active space matches artifact owner space id', async () => {
(item as ExceptionListItemSchema).tags = [
buildPerPolicyTag('abc'),
buildSpaceOwnerIdTag('default'),
];
render({ actions });
await waitFor(() => {
expect(
(renderResult.getByTestId('testCard-header-actions-button') as HTMLButtonElement).disabled
).toBe(false);
});
});
it('should enable card actions menu for per-policy artifacts when not owned by active space but user has global artifact privilege', async () => {
authzMock.set({ canManageGlobalArtifacts: true });
(item as ExceptionListItemSchema).tags = [
buildPerPolicyTag('abc'),
buildSpaceOwnerIdTag('foo'),
];
render({ actions });
await waitFor(() => {
expect(
(renderResult.getByTestId('testCard-header-actions-button') as HTMLButtonElement).disabled
).toBe(false);
});
});
});
});

View file

@ -77,7 +77,7 @@ export const ArtifactEntryCard = memo<ArtifactEntryCardProps>(
const policyNavLinks = usePolicyNavLinks(artifact, policies);
return (
<CardContainerPanel {...commonProps} data-test-subj={dataTestSubj}>
<CardContainerPanel {...commonProps} item={item} data-test-subj={dataTestSubj}>
<CardSectionPanel className="top-section">
<CardHeader
name={artifact.name}

View file

@ -18,6 +18,7 @@ import {
EuiButtonEmpty,
} from '@elastic/eui';
import styled from '@emotion/styled';
import { CardArtifactProvider } from './components/card_artifact_context';
import type { CriteriaConditionsProps } from './components/criteria_conditions';
import { CriteriaConditions } from './components/criteria_conditions';
import type { AnyArtifact } from './types';
@ -108,42 +109,44 @@ export const ArtifactEntryCardMinified = memo(
hasShadow={false}
hasBorder
>
{cardTitle}
<EuiSplitPanel.Inner paddingSize="s">
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="s">
<EuiTitle size="xxs">
<h5 data-test-subj={getTestId('descriptionTitle')}>{DESCRIPTION_LABEL}</h5>
</EuiTitle>
<DescriptionField data-test-subj={getTestId('description')}>
{artifact.description}
</DescriptionField>
</EuiPanel>
<CardArtifactProvider item={item}>
{cardTitle}
<EuiSplitPanel.Inner paddingSize="s">
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="s">
<EuiTitle size="xxs">
<h5 data-test-subj={getTestId('descriptionTitle')}>{DESCRIPTION_LABEL}</h5>
</EuiTitle>
<DescriptionField data-test-subj={getTestId('description')}>
{artifact.description}
</DescriptionField>
</EuiPanel>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="s">
<EuiButtonEmpty
data-test-subj={getTestId('collapse')}
color="primary"
size="s"
flush="left"
iconType={accordionTrigger === 'open' ? 'arrowUp' : 'arrowDown'}
iconSide="right"
iconSize="m"
onClick={handleOnToggleAccordion}
style={{ fontWeight: 400 }}
>
{getAccordionTitle()}
</EuiButtonEmpty>
<EuiAccordion id="showDetails" arrowDisplay="none" forceState={accordionTrigger}>
{Decorator && <Decorator item={item} data-test-subj={getTestId('decorator')} />}
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="s">
<EuiButtonEmpty
data-test-subj={getTestId('collapse')}
color="primary"
size="s"
flush="left"
iconType={accordionTrigger === 'open' ? 'arrowUp' : 'arrowDown'}
iconSide="right"
iconSize="m"
onClick={handleOnToggleAccordion}
style={{ fontWeight: 400 }}
>
{getAccordionTitle()}
</EuiButtonEmpty>
<EuiAccordion id="showDetails" arrowDisplay="none" forceState={accordionTrigger}>
{Decorator && <Decorator item={item} data-test-subj={getTestId('decorator')} />}
<CriteriaConditions
os={artifact.os as CriteriaConditionsProps['os']}
entries={artifact.entries}
data-test-subj={getTestId('criteriaConditions')}
/>
</EuiAccordion>
</EuiPanel>
</EuiSplitPanel.Inner>
<CriteriaConditions
os={artifact.os as CriteriaConditionsProps['os']}
entries={artifact.entries}
data-test-subj={getTestId('criteriaConditions')}
/>
</EuiAccordion>
</EuiPanel>
</EuiSplitPanel.Inner>
</CardArtifactProvider>
</CardContainerPanel>
);
}

View file

@ -7,6 +7,7 @@
import React, { memo } from 'react';
import { EuiHorizontalRule } from '@elastic/eui';
import type { AnyArtifact } from './types';
import type { CommonArtifactEntryCardProps } from './artifact_entry_card';
import { CardContainerPanel } from './components/card_container_panel';
import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
@ -36,7 +37,7 @@ export const ArtifactEntryCollapsibleCard = memo<ArtifactEntryCollapsibleCardPro
const getTestId = useTestIdGenerator(dataTestSubj);
return (
<CardContainerPanel {...commonProps} data-test-subj={dataTestSubj}>
<CardContainerPanel {...commonProps} item={item as AnyArtifact} data-test-subj={dataTestSubj}>
<CardSectionPanel className="artifact-entry-collapsible-card">
<CardCompressedHeader
artifact={artifact}

View file

@ -5,11 +5,23 @@
* 2.0.
*/
import React, { memo } from 'react';
import type { ReactNode } from 'react';
import React, { memo, useMemo } from 'react';
import type { CommonProps } from '@elastic/eui';
import { EuiFlexItem } from '@elastic/eui';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { useUserPrivileges } from '../../../../common/components/user_privileges';
import {
MANAGEMENT_OF_GLOBAL_ARTIFACT_NOT_ALLOWED_MESSAGE,
MANAGEMENT_OF_SHARED_PER_POLICY_ARTIFACT_NOT_ALLOWED_MESSAGE,
} from './translations';
import { useSpaceId } from '../../../../common/hooks/use_space_id';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { isArtifactGlobal } from '../../../../../common/endpoint/service/artifacts';
import { useCardArtifact } from './card_artifact_context';
import type { ActionsContextMenuProps } from '../../actions_context_menu';
import { ActionsContextMenu } from '../../actions_context_menu';
import { getArtifactOwnerSpaceIds } from '../../../../../common/endpoint/service/artifacts/utils';
export interface CardActionsFlexItemProps extends Pick<CommonProps, 'data-test-subj'> {
/** If defined, then an overflow menu will be shown with the actions provided */
@ -18,9 +30,51 @@ export interface CardActionsFlexItemProps extends Pick<CommonProps, 'data-test-s
export const CardActionsFlexItem = memo<CardActionsFlexItemProps>(
({ actions, 'data-test-subj': dataTestSubj }) => {
const item = useCardArtifact() as ExceptionListItemSchema;
const canManageGlobalArtifacts =
useUserPrivileges().endpointPrivileges.canManageGlobalArtifacts;
const isGlobal = useMemo(() => isArtifactGlobal(item), [item]);
const ownerSpaceIds = useMemo(() => getArtifactOwnerSpaceIds(item), [item]);
const isSpacesEnabled = useIsExperimentalFeatureEnabled(
'endpointManagementSpaceAwarenessEnabled'
);
const activeSpaceId = useSpaceId();
interface MenuButtonDisableOptions {
isDisabled: boolean;
disabledTooltip: ReactNode;
}
const { isDisabled, disabledTooltip } = useMemo<MenuButtonDisableOptions>(() => {
if (!isSpacesEnabled || canManageGlobalArtifacts) {
return { isDisabled: false, disabledTooltip: undefined };
}
if (isGlobal) {
return {
isDisabled: true,
disabledTooltip: MANAGEMENT_OF_GLOBAL_ARTIFACT_NOT_ALLOWED_MESSAGE,
};
}
if (!activeSpaceId || !ownerSpaceIds.includes(activeSpaceId)) {
return {
isDisabled: true,
disabledTooltip: MANAGEMENT_OF_SHARED_PER_POLICY_ARTIFACT_NOT_ALLOWED_MESSAGE,
};
}
return { isDisabled: false, disabledTooltip: undefined };
}, [activeSpaceId, canManageGlobalArtifacts, isGlobal, isSpacesEnabled, ownerSpaceIds]);
return actions && actions.length > 0 ? (
<EuiFlexItem grow={false}>
<ActionsContextMenu items={actions} icon="boxesHorizontal" data-test-subj={dataTestSubj} />
<ActionsContextMenu
items={actions}
icon="boxesHorizontal"
isDisabled={isDisabled}
disabledTooltip={disabledTooltip}
data-test-subj={dataTestSubj}
/>
</EuiFlexItem>
) : null;
}

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo, useContext, type PropsWithChildren } from 'react';
import type { MaybeImmutable } from '../../../../../common/endpoint/types';
import type { AnyArtifact } from '..';
const CardArtifactContext = React.createContext<MaybeImmutable<AnyArtifact> | undefined>(undefined);
export interface CardArtifactProviderProps extends PropsWithChildren {
item: MaybeImmutable<AnyArtifact>;
}
/**
* Stores and provides the Artifact item that is being rendered
*/
export const CardArtifactProvider = memo<CardArtifactProviderProps>(({ item, children }) => {
return <CardArtifactContext.Provider value={item}>{children}</CardArtifactContext.Provider>;
});
CardArtifactProvider.displayName = 'CardArtifactProvider';
/**
* Retrieve the artifact item (`ExceptionListItemSchema`) that is currently being rendered
*/
export const useCardArtifact = (): MaybeImmutable<AnyArtifact> => {
const artifact = useContext(CardArtifactContext);
if (!artifact) {
throw new Error('Card has not been initialized correctly - missing Artifact item');
}
return artifact;
};

View file

@ -9,6 +9,9 @@ import styled from '@emotion/styled';
import { EuiPanel } from '@elastic/eui';
import type { EuiPanelProps } from '@elastic/eui/src/components/panel/panel';
import React, { memo } from 'react';
import type { MaybeImmutable } from '../../../../../common/endpoint/types';
import { CardArtifactProvider } from './card_artifact_context';
import type { AnyArtifact } from '..';
export const EuiPanelStyled = styled(EuiPanel)`
&.artifactEntryCard + &.artifactEntryCard {
@ -16,17 +19,23 @@ export const EuiPanelStyled = styled(EuiPanel)`
}
`;
export type CardContainerPanelProps = Exclude<EuiPanelProps, 'hasBorder' | 'paddingSize'>;
export type CardContainerPanelProps = Exclude<EuiPanelProps, 'hasBorder' | 'paddingSize'> & {
item: MaybeImmutable<AnyArtifact>;
};
export const CardContainerPanel = memo<CardContainerPanelProps>(({ className, ...props }) => {
return (
<EuiPanelStyled
{...props}
hasBorder={true}
paddingSize="none"
className={`artifactEntryCard ${className ?? ''}`}
/>
);
});
export const CardContainerPanel = memo<CardContainerPanelProps>(
({ className, item, children, ...props }) => {
return (
<EuiPanelStyled
{...props}
hasBorder={true}
paddingSize="none"
className={`artifactEntryCard ${className ?? ''}`}
>
<CardArtifactProvider item={item}>{children}</CardArtifactProvider>
</EuiPanelStyled>
);
}
);
CardContainerPanel.displayName = 'CardContainerPanel';

View file

@ -162,3 +162,16 @@ export const DESCRIPTION_LABEL = i18n.translate(
defaultMessage: 'Description',
}
);
export const MANAGEMENT_OF_GLOBAL_ARTIFACT_NOT_ALLOWED_MESSAGE = i18n.translate(
'xpack.securitySolution.translations.noGlobalArtifactManagementAllowedMessage',
{ defaultMessage: 'Management of global artifacts requires additional privilege' }
);
export const MANAGEMENT_OF_SHARED_PER_POLICY_ARTIFACT_NOT_ALLOWED_MESSAGE = i18n.translate(
'xpack.securitySolution.translations.sharedPerPolicyArtifactNotAllowed',
{
defaultMessage:
'Management of artifacts shared across multiple spaces is only allowed from the space where it was created from',
}
);

View file

@ -883,19 +883,23 @@ interface GetOrCreateDefaultAgentPolicyOptions {
kbnClient: KbnClient;
log: ToolingLog;
policyName?: string;
overrides?: Partial<Omit<CreateAgentPolicyRequest['body'], 'name'>>;
}
/**
* Creates a default Fleet Agent policy (if it does not yet exist) for testing. If
* policy already exists, then it will be reused.
* policy already exists, then it will be reused. It uses the policy name to find an
* existing match.
* @param kbnClient
* @param log
* @param policyName
* @param overrides
*/
export const getOrCreateDefaultAgentPolicy = async ({
kbnClient,
log,
policyName = DEFAULT_AGENT_POLICY_NAME,
overrides = {},
}: GetOrCreateDefaultAgentPolicyOptions): Promise<AgentPolicy> => {
const existingPolicy = await fetchAgentPolicyList(kbnClient, {
kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.name: "${policyName}"`,
@ -919,6 +923,7 @@ export const getOrCreateDefaultAgentPolicy = async ({
description: `Policy created by security solution tooling: ${__filename}`,
namespace: spaceId,
monitoring_enabled: ['logs', 'metrics'],
...overrides,
},
});