mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution][Endpoint] Add validations to Update and Delete artifact APIs in support of spaces (#212308)
## Summary Adds additional validations to Artifact APIs _(via `lists` plugin server-side extension points)_ for the following conditions: - If user has the global artifact management privilege, then they are able to update/delete the artifact with no restriction (same as today) - If user does NOT have the new global artifact management privilege, then the update/delete action should fail: - If it's a global artifact - If it's a per policy artifact but it was created from a different space than the active space the API is being called from > [!NOTE] > Functionality is currently behind the following feature flag: `endpointManagementSpaceAwarenessEnabled` ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [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
This commit is contained in:
parent
0a562628b6
commit
7e79844925
16 changed files with 573 additions and 37 deletions
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getExceptionListItemSchemaMock } from '../common/schemas/response/exception_list_item_schema.mock';
|
||||
|
||||
import { ListPluginSetup } from './types';
|
||||
import { getListClientMock } from './services/lists/list_client.mock';
|
||||
import {
|
||||
|
@ -25,5 +27,6 @@ export const listMock = {
|
|||
createSetup: createSetupMock,
|
||||
getCreateExceptionListItemOptionsMock,
|
||||
getExceptionListClient: getExceptionListClientMock,
|
||||
getExceptionListItemSchemaMock,
|
||||
getListClient: getListClientMock,
|
||||
};
|
||||
|
|
|
@ -40,7 +40,9 @@ export const getExceptionsPreDeleteItemHandler = (
|
|||
|
||||
// Validate Trusted Applications
|
||||
if (TrustedAppValidator.isTrustedApp({ listId })) {
|
||||
await new TrustedAppValidator(endpointAppContextService, request).validatePreDeleteItem();
|
||||
await new TrustedAppValidator(endpointAppContextService, request).validatePreDeleteItem(
|
||||
exceptionItem
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -49,19 +51,23 @@ export const getExceptionsPreDeleteItemHandler = (
|
|||
await new HostIsolationExceptionsValidator(
|
||||
endpointAppContextService,
|
||||
request
|
||||
).validatePreDeleteItem();
|
||||
).validatePreDeleteItem(exceptionItem);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Event Filter validation
|
||||
if (EventFilterValidator.isEventFilter({ listId })) {
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreDeleteItem();
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreDeleteItem(
|
||||
exceptionItem
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Validate Blocklists
|
||||
if (BlocklistValidator.isBlocklist({ listId })) {
|
||||
await new BlocklistValidator(endpointAppContextService, request).validatePreDeleteItem();
|
||||
await new BlocklistValidator(endpointAppContextService, request).validatePreDeleteItem(
|
||||
exceptionItem
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -70,7 +76,7 @@ export const getExceptionsPreDeleteItemHandler = (
|
|||
await new EndpointExceptionsValidator(
|
||||
endpointAppContextService,
|
||||
request
|
||||
).validatePreDeleteItem();
|
||||
).validatePreDeleteItem(exceptionItem);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,11 @@ import {
|
|||
createMockEndpointAppContextServiceSetupContract,
|
||||
createMockEndpointAppContextServiceStartContract,
|
||||
} from '../../../endpoint/mocks';
|
||||
import { BaseValidatorMock, createExceptionItemLikeOptionsMock } from './mocks';
|
||||
import {
|
||||
BaseValidatorMock,
|
||||
createExceptionItemLikeOptionsMock,
|
||||
createExceptionListItemMock,
|
||||
} from './mocks';
|
||||
import { EndpointArtifactExceptionValidationError } from './errors';
|
||||
import { httpServerMock } from '@kbn/core/server/mocks';
|
||||
import type { PackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
|
@ -23,10 +27,15 @@ import {
|
|||
GLOBAL_ARTIFACT_TAG,
|
||||
} from '../../../../common/endpoint/service/artifacts';
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
import { setArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
|
||||
import {
|
||||
buildPerPolicyTag,
|
||||
buildSpaceOwnerIdTag,
|
||||
setArtifactOwnerSpaceId,
|
||||
} from '../../../../common/endpoint/service/artifacts/utils';
|
||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||
import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks';
|
||||
import type { EndpointAuthz } from '../../../../common/endpoint/types/authz';
|
||||
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
describe('When using Artifacts Exceptions BaseValidator', () => {
|
||||
let endpointAppContextServices: EndpointAppContextService;
|
||||
|
@ -198,16 +207,23 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
|
|||
});
|
||||
|
||||
describe('with space awareness', () => {
|
||||
const noGlobalArtifactManagementAuthzMessage =
|
||||
const noAuthzToManageOwnerSpaceIdError =
|
||||
'EndpointArtifactError: Endpoint authorization failure. Management of "ownerSpaceId" tag requires global artifact management privilege';
|
||||
const noAuthzToManageGlobalArtifactsError =
|
||||
'EndpointArtifactError: Endpoint authorization failure. Management of global artifacts requires additional privilege (global artifact management)';
|
||||
const itemCanNotBeManagedInActiveSpaceErrorMessage =
|
||||
'EndpointArtifactError: Updates to this shared item can only be done from the following space ID: foo (or by someone having global artifact management privilege)';
|
||||
const setSpaceAwarenessFeatureFlag = (value: 'enabled' | 'disabled'): void => {
|
||||
// @ts-expect-error updating a readonly field
|
||||
endpointAppContextServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled =
|
||||
value === 'enabled';
|
||||
};
|
||||
let authzMock: EndpointAuthz;
|
||||
|
||||
beforeEach(() => {
|
||||
authzMock = getEndpointAuthzInitialStateMock();
|
||||
endpointAppContextServices = createMockEndpointAppContextService();
|
||||
// @ts-expect-error updating a readonly field
|
||||
endpointAppContextServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled =
|
||||
true;
|
||||
setSpaceAwarenessFeatureFlag('enabled');
|
||||
(endpointAppContextServices.getEndpointAuthz as jest.Mock).mockResolvedValue(authzMock);
|
||||
setArtifactOwnerSpaceId(exceptionLikeItem, DEFAULT_SPACE_ID);
|
||||
validator = new BaseValidatorMock(endpointAppContextServices, kibanaRequest);
|
||||
|
@ -219,7 +235,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
|
|||
authzMock.canManageGlobalArtifacts = false;
|
||||
|
||||
await expect(validator._validateCreateOwnerSpaceIds(exceptionLikeItem)).rejects.toThrow(
|
||||
noGlobalArtifactManagementAuthzMessage
|
||||
noAuthzToManageOwnerSpaceIdError
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -240,9 +256,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
|
|||
});
|
||||
|
||||
it('should not error if feature flag is disabled', async () => {
|
||||
// @ts-expect-error updating a readonly field
|
||||
endpointAppContextServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled =
|
||||
false;
|
||||
setSpaceAwarenessFeatureFlag('disabled');
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
setArtifactOwnerSpaceId(exceptionLikeItem, 'foo');
|
||||
setArtifactOwnerSpaceId(exceptionLikeItem, 'bar');
|
||||
|
@ -267,7 +281,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
|
|||
|
||||
await expect(
|
||||
validator._validateUpdateOwnerSpaceIds(exceptionLikeItem, savedExceptionLikeItem)
|
||||
).rejects.toThrow(noGlobalArtifactManagementAuthzMessage);
|
||||
).rejects.toThrow(noAuthzToManageOwnerSpaceIdError);
|
||||
});
|
||||
|
||||
it('should allow changes to spaceOwnerId tags if user has global artifact management authz', async () => {
|
||||
|
@ -279,9 +293,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
|
|||
});
|
||||
|
||||
it('should not error if feature flag is disabled', async () => {
|
||||
// @ts-expect-error updating a readonly field
|
||||
endpointAppContextServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled =
|
||||
false;
|
||||
setSpaceAwarenessFeatureFlag('disabled');
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
setArtifactOwnerSpaceId(exceptionLikeItem, 'foo');
|
||||
setArtifactOwnerSpaceId(exceptionLikeItem, 'bar');
|
||||
|
@ -291,5 +303,154 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
|
|||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validateCanCreateGlobalArtifacts()', () => {
|
||||
beforeEach(() => {
|
||||
exceptionLikeItem.tags = [GLOBAL_ARTIFACT_TAG];
|
||||
});
|
||||
|
||||
it('should do nothing if feature flag is turned off', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
setSpaceAwarenessFeatureFlag('disabled');
|
||||
|
||||
await expect(
|
||||
validator._validateCanCreateGlobalArtifacts(exceptionLikeItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should error is user does not have new global artifact management privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
|
||||
await expect(
|
||||
validator._validateCanCreateGlobalArtifacts(exceptionLikeItem)
|
||||
).rejects.toThrow(noAuthzToManageGlobalArtifactsError);
|
||||
});
|
||||
|
||||
it('should allow creation of global artifacts when user has privilege', async () => {
|
||||
await expect(
|
||||
validator._validateCanCreateGlobalArtifacts(exceptionLikeItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validateCanUpdateItemInActiveSpace()', () => {
|
||||
let savedExceptionItem: ExceptionListItemSchema;
|
||||
|
||||
beforeEach(() => {
|
||||
savedExceptionItem = createExceptionListItemMock({
|
||||
// Saved item is owned by different space id
|
||||
tags: [buildPerPolicyTag('123'), buildSpaceOwnerIdTag('foo')],
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if feature flag is turned off', async () => {
|
||||
setSpaceAwarenessFeatureFlag('disabled');
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
|
||||
await expect(
|
||||
validator._validateCanUpdateItemInActiveSpace(exceptionLikeItem, savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should error if updating a global item when user does not have global artifact privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
savedExceptionItem.tags = [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag('foo')];
|
||||
|
||||
await expect(
|
||||
validator._validateCanUpdateItemInActiveSpace(exceptionLikeItem, savedExceptionItem)
|
||||
).rejects.toThrow(noAuthzToManageGlobalArtifactsError);
|
||||
});
|
||||
|
||||
it('should error if updating an item outside of its owner space id when user does not have global artifact privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
|
||||
await expect(
|
||||
validator._validateCanUpdateItemInActiveSpace(exceptionLikeItem, savedExceptionItem)
|
||||
).rejects.toThrow(itemCanNotBeManagedInActiveSpaceErrorMessage);
|
||||
});
|
||||
|
||||
it('should allow updates to global items when user has global artifact privilege', async () => {
|
||||
savedExceptionItem.tags = [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag('foo')];
|
||||
|
||||
await expect(
|
||||
validator._validateCanUpdateItemInActiveSpace(exceptionLikeItem, savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should allow update to item outside of owner space id when user has global artifact privilege', async () => {
|
||||
await expect(
|
||||
validator._validateCanUpdateItemInActiveSpace(exceptionLikeItem, savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should allow update to item inside of owner space id when user has no global artifact privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
savedExceptionItem.tags = [buildPerPolicyTag('123'), buildSpaceOwnerIdTag('default')];
|
||||
|
||||
await expect(
|
||||
validator._validateCanUpdateItemInActiveSpace(exceptionLikeItem, savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validateCanDeleteItemInActiveSpace()', () => {
|
||||
let savedExceptionItem: ExceptionListItemSchema;
|
||||
|
||||
beforeEach(() => {
|
||||
savedExceptionItem = createExceptionListItemMock({
|
||||
// Saved item is owned by different space id
|
||||
tags: [buildPerPolicyTag('123'), buildSpaceOwnerIdTag('foo')],
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if feature flag is turned off', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
setSpaceAwarenessFeatureFlag('disabled');
|
||||
|
||||
await expect(
|
||||
validator._validateCanDeleteItemInActiveSpace(savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should error if deleting a global artifact when user does not have global artifact privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
savedExceptionItem.tags = [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag('foo')];
|
||||
|
||||
await expect(
|
||||
validator._validateCanDeleteItemInActiveSpace(savedExceptionItem)
|
||||
).rejects.toThrow(noAuthzToManageGlobalArtifactsError);
|
||||
});
|
||||
|
||||
it('should error if deleting item outside of its owner space id when user does not have global artifact privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
|
||||
await expect(
|
||||
validator._validateCanDeleteItemInActiveSpace(savedExceptionItem)
|
||||
).rejects.toThrow(itemCanNotBeManagedInActiveSpaceErrorMessage);
|
||||
});
|
||||
|
||||
it('should allow delete of global item when user has global artifact privilege', async () => {
|
||||
savedExceptionItem.tags = [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag('foo')];
|
||||
|
||||
await expect(
|
||||
validator._validateCanDeleteItemInActiveSpace(savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should allow deleting item from outside of its owner space id when user has global artifact privilege', async () => {
|
||||
await expect(
|
||||
validator._validateCanDeleteItemInActiveSpace(savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should allow deleting of item inside from owner space id when user has no global artifact privilege', async () => {
|
||||
authzMock.canManageGlobalArtifacts = false;
|
||||
savedExceptionItem.tags = [buildPerPolicyTag('123'), buildSpaceOwnerIdTag('default')];
|
||||
|
||||
await expect(
|
||||
validator._validateCanDeleteItemInActiveSpace(savedExceptionItem)
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { KibanaRequest } from '@kbn/core/server';
|
||||
import type { KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
@ -29,7 +29,7 @@ import {
|
|||
import { EndpointArtifactExceptionValidationError } from './errors';
|
||||
import { EndpointExceptionsValidationError } from './endpoint_exception_errors';
|
||||
|
||||
const NO_GLOBAL_ARTIFACT_AUTHZ_MESSAGE = i18n.translate(
|
||||
const OWNER_SPACE_ID_TAG_MANAGEMENT_NOT_ALLOWED_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.baseValidator.noGlobalArtifactAuthzApiMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
|
@ -37,6 +37,23 @@ const NO_GLOBAL_ARTIFACT_AUTHZ_MESSAGE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.baseValidator.noGlobalArtifactManagementMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Management of global artifacts requires additional privilege (global artifact management)',
|
||||
}
|
||||
);
|
||||
|
||||
const ITEM_CANNOT_BE_MANAGED_IN_CURRENT_SPACE_MESSAGE = (spaceIds: string[]): string =>
|
||||
i18n.translate('xpack.securitySolution.baseValidator.cannotManageItemInCurrentSpace', {
|
||||
defaultMessage: `Updates to this shared item can only be done from the following space {numberOfSpaces, plural, one {ID} other {IDs} }: {itemOwnerSpaces} (or by someone having global artifact management privilege)`,
|
||||
values: {
|
||||
numberOfSpaces: spaceIds.length,
|
||||
itemOwnerSpaces: spaceIds.join(', '),
|
||||
},
|
||||
});
|
||||
|
||||
export const BasicEndpointExceptionDataSchema = schema.object(
|
||||
{
|
||||
// must have a name
|
||||
|
@ -66,6 +83,7 @@ export const BasicEndpointExceptionDataSchema = schema.object(
|
|||
*/
|
||||
export class BaseValidator {
|
||||
private readonly endpointAuthzPromise: ReturnType<EndpointAppContextService['getEndpointAuthz']>;
|
||||
protected readonly logger: Logger;
|
||||
|
||||
constructor(
|
||||
protected readonly endpointAppContext: EndpointAppContextService,
|
||||
|
@ -74,6 +92,8 @@ export class BaseValidator {
|
|||
*/
|
||||
private readonly request?: KibanaRequest
|
||||
) {
|
||||
this.logger = endpointAppContext.createLogger(this.constructor.name ?? 'artifactBaseValidator');
|
||||
|
||||
if (this.request) {
|
||||
this.endpointAuthzPromise = this.endpointAppContext.getEndpointAuthz(this.request);
|
||||
} else {
|
||||
|
@ -104,8 +124,8 @@ export class BaseValidator {
|
|||
}
|
||||
}
|
||||
|
||||
protected isItemByPolicy(item: ExceptionItemLikeOptions): boolean {
|
||||
return isArtifactByPolicy(item);
|
||||
protected isItemByPolicy(item: Partial<Pick<ExceptionListItemSchema, 'tags'>>): boolean {
|
||||
return isArtifactByPolicy(item as Pick<ExceptionListItemSchema, 'tags'>);
|
||||
}
|
||||
|
||||
protected async isAllowedToCreateArtifactsByPolicy(): Promise<boolean> {
|
||||
|
@ -221,7 +241,7 @@ export class BaseValidator {
|
|||
!(await this.endpointAuthzPromise).canManageGlobalArtifacts
|
||||
) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
`Endpoint authorization failure. ${NO_GLOBAL_ARTIFACT_AUTHZ_MESSAGE}`,
|
||||
`${ENDPOINT_AUTHZ_ERROR_MESSAGE}. ${OWNER_SPACE_ID_TAG_MANAGEMENT_NOT_ALLOWED_MESSAGE}`,
|
||||
403
|
||||
);
|
||||
}
|
||||
|
@ -245,7 +265,7 @@ export class BaseValidator {
|
|||
(ownerSpaceIds.length === 1 && ownerSpaceIds[0] !== activeSpaceId)
|
||||
) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
`Endpoint authorization failure. ${NO_GLOBAL_ARTIFACT_AUTHZ_MESSAGE}`,
|
||||
`${ENDPOINT_AUTHZ_ERROR_MESSAGE}. ${OWNER_SPACE_ID_TAG_MANAGEMENT_NOT_ALLOWED_MESSAGE}`,
|
||||
403
|
||||
);
|
||||
}
|
||||
|
@ -282,4 +302,78 @@ export class BaseValidator {
|
|||
setArtifactOwnerSpaceId(item, await this.getActiveSpaceId());
|
||||
}
|
||||
}
|
||||
|
||||
protected async validateCanCreateGlobalArtifacts(item: ExceptionItemLikeOptions): Promise<void> {
|
||||
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
|
||||
if (
|
||||
!this.isItemByPolicy(item) &&
|
||||
!(await this.endpointAuthzPromise).canManageGlobalArtifacts
|
||||
) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
`${ENDPOINT_AUTHZ_ERROR_MESSAGE}. ${GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE}`,
|
||||
403
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async validateCanUpdateItemInActiveSpace(
|
||||
updatedItem: Partial<Pick<ExceptionListItemSchema, 'tags'>>,
|
||||
currentSavedItem: ExceptionListItemSchema
|
||||
): Promise<void> {
|
||||
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
|
||||
// Those with global artifact management privilege can do it all
|
||||
if ((await this.endpointAuthzPromise).canManageGlobalArtifacts) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If either the updated item or the saved item is a global artifact, then
|
||||
// error out - user needs global artifact management privilege
|
||||
if (!this.isItemByPolicy(updatedItem) || !this.isItemByPolicy(currentSavedItem)) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
`${ENDPOINT_AUTHZ_ERROR_MESSAGE}. ${GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE}`,
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
const itemOwnerSpaces = getArtifactOwnerSpaceIds(currentSavedItem);
|
||||
|
||||
// Per-space items can only be managed from one of the `ownerSpaceId`'s
|
||||
if (!itemOwnerSpaces.includes(await this.getActiveSpaceId())) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
ITEM_CANNOT_BE_MANAGED_IN_CURRENT_SPACE_MESSAGE(itemOwnerSpaces),
|
||||
403
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async validateCanDeleteItemInActiveSpace(
|
||||
currentSavedItem: ExceptionListItemSchema
|
||||
): Promise<void> {
|
||||
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
|
||||
// Those with global artifact management privilege can do it all
|
||||
if ((await this.endpointAuthzPromise).canManageGlobalArtifacts) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If item is a global artifact then error - user must have global artifact management privilege
|
||||
if (!this.isItemByPolicy(currentSavedItem)) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
`${ENDPOINT_AUTHZ_ERROR_MESSAGE}. ${GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE}`,
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
const itemOwnerSpaces = getArtifactOwnerSpaceIds(currentSavedItem);
|
||||
|
||||
// Per-space items can only be deleted from one of the `ownerSpaceId`'s
|
||||
if (!itemOwnerSpaces.includes(await this.getActiveSpaceId())) {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
ITEM_CANNOT_BE_MANAGED_IN_CURRENT_SPACE_MESSAGE(itemOwnerSpaces),
|
||||
403
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,8 +250,9 @@ export class BlocklistValidator extends BaseValidator {
|
|||
return item;
|
||||
}
|
||||
|
||||
async validatePreDeleteItem(): Promise<void> {
|
||||
async validatePreDeleteItem(currentItem: ExceptionListItemSchema): Promise<void> {
|
||||
await this.validateHasWritePrivilege();
|
||||
await this.validateCanDeleteItemInActiveSpace(currentItem);
|
||||
}
|
||||
|
||||
async validatePreGetOneItem(): Promise<void> {
|
||||
|
@ -301,6 +302,7 @@ export class BlocklistValidator extends BaseValidator {
|
|||
|
||||
await this.validateByPolicyItem(updatedItem);
|
||||
await this.validateUpdateOwnerSpaceIds(updatedItem, currentItem);
|
||||
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
|
||||
|
||||
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
|
||||
await this.setOwnerSpaceId(_updatedItem);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { BlocklistValidator } from './blocklist_validator';
|
||||
import { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
||||
|
||||
describe('Blocklists API validations', () => {
|
||||
it('should initialize', () => {
|
||||
expect(
|
||||
new BlocklistValidator(
|
||||
createMockEndpointAppContextService(),
|
||||
httpServerMock.createKibanaRequest()
|
||||
)
|
||||
).not.toBeUndefined();
|
||||
});
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// API TESTS FOR THIS ARTIFACT TYPE SHOULD BE COVERED WITH INTEGRATION TESTS.
|
||||
// ADD THEM HERE:
|
||||
//
|
||||
// `x-pack/test/security_solution_api_integration/test_suites/edr_workflows`
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
||||
import { EndpointExceptionsValidator } from './endpoint_exceptions_validator';
|
||||
|
||||
describe('Endpoint Exceptions API validations', () => {
|
||||
it('should initialize', () => {
|
||||
expect(
|
||||
new EndpointExceptionsValidator(
|
||||
createMockEndpointAppContextService(),
|
||||
httpServerMock.createKibanaRequest()
|
||||
)
|
||||
).not.toBeUndefined();
|
||||
});
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// API TESTS FOR THIS ARTIFACT TYPE SHOULD BE COVERED WITH INTEGRATION TESTS.
|
||||
// ADD THEM HERE:
|
||||
//
|
||||
// `x-pack/test/security_solution_api_integration/test_suites/edr_workflows`
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
});
|
|
@ -11,8 +11,9 @@ import type {
|
|||
} from '@kbn/lists-plugin/server';
|
||||
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
|
||||
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { EndpointExceptionsValidationError } from './endpoint_exception_errors';
|
||||
import { hasArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
|
||||
import { BaseValidator } from './base_validator';
|
||||
import { BaseValidator, GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE } from './base_validator';
|
||||
|
||||
export class EndpointExceptionsValidator extends BaseValidator {
|
||||
static isEndpointException(item: { listId: string }): boolean {
|
||||
|
@ -24,7 +25,21 @@ export class EndpointExceptionsValidator extends BaseValidator {
|
|||
}
|
||||
|
||||
protected async validateHasWritePrivilege(): Promise<void> {
|
||||
return this.validateHasEndpointExceptionsPrivileges('canWriteEndpointExceptions');
|
||||
await this.validateHasEndpointExceptionsPrivileges('canWriteEndpointExceptions');
|
||||
|
||||
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
|
||||
// Endpoint Exceptions are currently ONLY global, so we need to make sure the user
|
||||
// also has the new Global Artifacts privilege
|
||||
try {
|
||||
await this.validateHasPrivilege('canManageGlobalArtifacts');
|
||||
} catch (error) {
|
||||
// We provide a more detailed error here
|
||||
throw new EndpointExceptionsValidationError(
|
||||
`${error.message}. ${GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE}`,
|
||||
403
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validatePreCreateItem(item: CreateExceptionListItemOptions) {
|
||||
|
@ -50,8 +65,9 @@ export class EndpointExceptionsValidator extends BaseValidator {
|
|||
return item;
|
||||
}
|
||||
|
||||
async validatePreDeleteItem(): Promise<void> {
|
||||
async validatePreDeleteItem(currentItem: ExceptionListItemSchema): Promise<void> {
|
||||
await this.validateHasWritePrivilege();
|
||||
await this.validateCanDeleteItemInActiveSpace(currentItem);
|
||||
}
|
||||
|
||||
async validatePreGetOneItem(): Promise<void> {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
||||
import { EventFilterValidator } from './event_filter_validator';
|
||||
|
||||
describe('Endpoint Exceptions API validations', () => {
|
||||
it('should initialize', () => {
|
||||
expect(
|
||||
new EventFilterValidator(
|
||||
createMockEndpointAppContextService(),
|
||||
httpServerMock.createKibanaRequest()
|
||||
)
|
||||
).not.toBeUndefined();
|
||||
});
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// API TESTS FOR THIS ARTIFACT TYPE SHOULD BE COVERED WITH INTEGRATION TESTS.
|
||||
// ADD THEM HERE:
|
||||
//
|
||||
// `x-pack/test/security_solution_api_integration/test_suites/edr_workflows`
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
});
|
|
@ -89,6 +89,7 @@ export class EventFilterValidator extends BaseValidator {
|
|||
|
||||
await this.validateByPolicyItem(updatedItem);
|
||||
await this.validateUpdateOwnerSpaceIds(_updatedItem, currentItem);
|
||||
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
|
||||
|
||||
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
|
||||
await this.setOwnerSpaceId(_updatedItem);
|
||||
|
@ -115,8 +116,9 @@ export class EventFilterValidator extends BaseValidator {
|
|||
await this.validateHasReadPrivilege();
|
||||
}
|
||||
|
||||
async validatePreDeleteItem(): Promise<void> {
|
||||
async validatePreDeleteItem(currentItem: ExceptionListItemSchema): Promise<void> {
|
||||
await this.validateHasWritePrivilege();
|
||||
await this.validateCanDeleteItemInActiveSpace(currentItem);
|
||||
}
|
||||
|
||||
async validatePreExport(): Promise<void> {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
||||
import { HostIsolationExceptionsValidator } from './host_isolation_exceptions_validator';
|
||||
|
||||
describe('Endpoint Exceptions API validations', () => {
|
||||
it('should initialize', () => {
|
||||
expect(
|
||||
new HostIsolationExceptionsValidator(
|
||||
createMockEndpointAppContextService(),
|
||||
httpServerMock.createKibanaRequest()
|
||||
)
|
||||
).not.toBeUndefined();
|
||||
});
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// API TESTS FOR THIS ARTIFACT TYPE SHOULD BE COVERED WITH INTEGRATION TESTS.
|
||||
// ADD THEM HERE:
|
||||
//
|
||||
// `x-pack/test/security_solution_api_integration/test_suites/edr_workflows`
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
});
|
|
@ -97,6 +97,7 @@ export class HostIsolationExceptionsValidator extends BaseValidator {
|
|||
await this.validateHostIsolationData(updatedItem);
|
||||
await this.validateByPolicyItem(updatedItem);
|
||||
await this.validateUpdateOwnerSpaceIds(_updatedItem, currentItem);
|
||||
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
|
||||
|
||||
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
|
||||
await this.setOwnerSpaceId(_updatedItem);
|
||||
|
@ -113,8 +114,9 @@ export class HostIsolationExceptionsValidator extends BaseValidator {
|
|||
await this.validateHasReadPrivilege();
|
||||
}
|
||||
|
||||
async validatePreDeleteItem(): Promise<void> {
|
||||
async validatePreDeleteItem(currentItem: ExceptionListItemSchema): Promise<void> {
|
||||
await this.validateHasDeletePrivilege();
|
||||
await this.validateCanDeleteItemInActiveSpace(currentItem);
|
||||
}
|
||||
|
||||
async validatePreExport(): Promise<void> {
|
||||
|
|
|
@ -7,9 +7,14 @@
|
|||
|
||||
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { listMock } from '@kbn/lists-plugin/server/mocks';
|
||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||
import { buildSpaceOwnerIdTag } from '../../../../common/endpoint/service/artifacts/utils';
|
||||
import { BaseValidator } from './base_validator';
|
||||
import type { ExceptionItemLikeOptions } from '../types';
|
||||
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../../common/endpoint/service/artifacts';
|
||||
import {
|
||||
BY_POLICY_ARTIFACT_TAG_PREFIX,
|
||||
GLOBAL_ARTIFACT_TAG,
|
||||
} from '../../../../common/endpoint/service/artifacts';
|
||||
|
||||
/**
|
||||
* Exposes all `protected` methods of `BaseValidator` by prefixing them with an underscore.
|
||||
|
@ -56,6 +61,21 @@ export class BaseValidatorMock extends BaseValidator {
|
|||
): Promise<void> {
|
||||
return this.validateUpdateOwnerSpaceIds(updatedItem, currentItem);
|
||||
}
|
||||
|
||||
_validateCanCreateGlobalArtifacts(item: ExceptionItemLikeOptions): Promise<void> {
|
||||
return this.validateCanCreateGlobalArtifacts(item);
|
||||
}
|
||||
|
||||
_validateCanUpdateItemInActiveSpace(
|
||||
updatedItem: Partial<Pick<ExceptionListItemSchema, 'tags'>>,
|
||||
currentSavedItem: ExceptionListItemSchema
|
||||
): Promise<void> {
|
||||
return this.validateCanUpdateItemInActiveSpace(updatedItem, currentSavedItem);
|
||||
}
|
||||
|
||||
_validateCanDeleteItemInActiveSpace(currentSavedItem: ExceptionListItemSchema): Promise<void> {
|
||||
return this.validateCanDeleteItemInActiveSpace(currentSavedItem);
|
||||
}
|
||||
}
|
||||
|
||||
export const createExceptionItemLikeOptionsMock = (
|
||||
|
@ -69,3 +89,14 @@ export const createExceptionItemLikeOptionsMock = (
|
|||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
export const createExceptionListItemMock = (
|
||||
overrides: Partial<ExceptionListItemSchema> = {}
|
||||
): ExceptionListItemSchema => {
|
||||
return listMock.getExceptionListItemSchemaMock({
|
||||
namespace_type: 'agnostic',
|
||||
os_types: ['windows'],
|
||||
tags: [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag(DEFAULT_SPACE_ID)],
|
||||
...overrides,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
||||
import { TrustedAppValidator } from './trusted_app_validator';
|
||||
|
||||
describe('Endpoint Exceptions API validations', () => {
|
||||
it('should initialize', () => {
|
||||
expect(
|
||||
new TrustedAppValidator(
|
||||
createMockEndpointAppContextService(),
|
||||
httpServerMock.createKibanaRequest()
|
||||
)
|
||||
).not.toBeUndefined();
|
||||
});
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// API TESTS FOR THIS ARTIFACT TYPE SHOULD BE COVERED WITH INTEGRATION TESTS.
|
||||
// ADD THEM HERE:
|
||||
//
|
||||
// `x-pack/test/security_solution_api_integration/test_suites/edr_workflows`
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
});
|
|
@ -208,14 +208,16 @@ export class TrustedAppValidator extends BaseValidator {
|
|||
await this.validateCanCreateByPolicyArtifacts(item);
|
||||
await this.validateByPolicyItem(item);
|
||||
await this.validateCreateOwnerSpaceIds(item);
|
||||
await this.validateCanCreateGlobalArtifacts(item);
|
||||
|
||||
await this.setOwnerSpaceId(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
async validatePreDeleteItem(): Promise<void> {
|
||||
async validatePreDeleteItem(currentItem: ExceptionListItemSchema): Promise<void> {
|
||||
await this.validateHasWritePrivilege();
|
||||
await this.validateCanDeleteItemInActiveSpace(currentItem);
|
||||
}
|
||||
|
||||
async validatePreGetOneItem(): Promise<void> {
|
||||
|
@ -260,6 +262,7 @@ export class TrustedAppValidator extends BaseValidator {
|
|||
|
||||
await this.validateByPolicyItem(updatedItem);
|
||||
await this.validateUpdateOwnerSpaceIds(_updatedItem, currentItem);
|
||||
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
|
||||
|
||||
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
|
||||
await this.setOwnerSpaceId(_updatedItem);
|
||||
|
|
|
@ -212,8 +212,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.expect(403);
|
||||
});
|
||||
|
||||
// TODO:PT Un-skip in next PR. I got a little ahead of myself and added a test for the change that wil come with the next PR.
|
||||
it.skip('should error if attempting to update a global artifact', async () => {
|
||||
it('should error if attempting to update a global artifact', async () => {
|
||||
await supertestArtifactManager
|
||||
.put(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
|
||||
.set('elastic-api-version', '2023-10-31')
|
||||
|
@ -229,8 +228,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.expect(403);
|
||||
});
|
||||
|
||||
// TODO:PT Un-skip in next PR. I got a little ahead of myself and added a test for the change that wil come with the next PR.
|
||||
it.skip('should error when attempting to change a global artifact to per-policy', async () => {
|
||||
it('should error when attempting to change a global artifact to per-policy', async () => {
|
||||
await supertestArtifactManager
|
||||
.put(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
|
||||
.set('elastic-api-version', '2023-10-31')
|
||||
|
@ -247,6 +245,45 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it('should error when attempting to update item outside of its owner space id', async () => {
|
||||
const { body } = await supertestArtifactManager
|
||||
.put(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
|
||||
.set('elastic-api-version', '2023-10-31')
|
||||
.set('x-elastic-internal-origin', 'kibana')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
|
||||
.send(
|
||||
exceptionItemToCreateExceptionItem({
|
||||
...spaceOnePerPolicyArtifact.artifact,
|
||||
description: 'updating item',
|
||||
})
|
||||
)
|
||||
.expect(403);
|
||||
|
||||
expect(body.message).to.eql(
|
||||
`EndpointArtifactError: Updates to this shared item can only be done from the following space ID: ${spaceOneId} (or by someone having global artifact management privilege)`
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when attempting to delete item outside of its owner space id', async () => {
|
||||
const { body } = await supertestArtifactManager
|
||||
.delete(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
|
||||
.set('elastic-api-version', '2023-10-31')
|
||||
.set('x-elastic-internal-origin', 'kibana')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
|
||||
.query({
|
||||
item_id: spaceOnePerPolicyArtifact.artifact.item_id,
|
||||
namespace_type: spaceOnePerPolicyArtifact.artifact.namespace_type,
|
||||
})
|
||||
.send()
|
||||
.expect(403);
|
||||
|
||||
expect(body.message).to.eql(
|
||||
`EndpointArtifactError: Updates to this shared item can only be done from the following space ID: ${spaceOneId} (or by someone having global artifact management privilege)`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and user has privilege to manage global artifacts', () => {
|
||||
|
@ -332,6 +369,40 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should allow updating items outside of their owner space ids', async () => {
|
||||
await supertestGlobalArtifactManager
|
||||
.put(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
|
||||
.set('elastic-api-version', '2023-10-31')
|
||||
.set('x-elastic-internal-origin', 'kibana')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.on('error', createSupertestErrorLogger(log))
|
||||
.send(
|
||||
exceptionItemToCreateExceptionItem({
|
||||
...spaceOneGlobalArtifact.artifact,
|
||||
description: 'updated from outside of its own space id',
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should allow deleting items outside of their owner space ids', async () => {
|
||||
await supertestGlobalArtifactManager
|
||||
.delete(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
|
||||
.set('elastic-api-version', '2023-10-31')
|
||||
.set('x-elastic-internal-origin', 'kibana')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
|
||||
.query({
|
||||
item_id: spaceOnePerPolicyArtifact.artifact.item_id,
|
||||
namespace_type: spaceOnePerPolicyArtifact.artifact.namespace_type,
|
||||
})
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// @ts-expect-error
|
||||
spaceOnePerPolicyArtifact = undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue