[Cases] Validate attributes before create SOs (#158590)

## Summary

This PR uses `io-ts`'s `decode` utitlity function to validate the
attributes before creating cases saved objects.

### 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

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christos Nasikas 2023-06-07 17:58:34 +02:00 committed by GitHub
parent 0ea97e2e8c
commit b5adaecb6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 785 additions and 119 deletions

View file

@ -184,15 +184,21 @@ const CommentAttributesWithoutRefsRt = rt.union([
AttributesTypePersistableStateRt,
]);
export const CommentRequestRt = rt.union([
const BasicCommentRequestRt = rt.union([
ContextTypeUserRt,
AlertCommentRequestRt,
ActionsCommentRequestRt,
ExternalReferenceNoSORt,
ExternalReferenceSORt,
PersistableStateAttachmentRt,
]);
export const CommentRequestRt = rt.union([BasicCommentRequestRt, ExternalReferenceSORt]);
export const CommentRequestWithoutRefsRt = rt.union([
BasicCommentRequestRt,
ExternalReferenceSOWithoutRefsRt,
]);
export const CommentRt = rt.intersection([
CommentAttributesRt,
rt.strict({

View file

@ -6,15 +6,26 @@
*/
import * as rt from 'io-ts';
import { CommentRequestRt } from '../comment';
import { CommentRequestRt, CommentRequestWithoutRefsRt } from '../comment';
import type { UserActionWithAttributes } from './common';
import { ActionTypes } from './common';
export const CommentUserActionPayloadRt = rt.strict({ comment: CommentRequestRt });
export const CommentUserActionPayloadWithoutIdsRt = rt.strict({
comment: CommentRequestWithoutRefsRt,
});
export const CommentUserActionRt = rt.strict({
type: rt.literal(ActionTypes.comment),
payload: CommentUserActionPayloadRt,
});
export const CommentUserActionWithoutIdsRt = rt.strict({
type: rt.literal(ActionTypes.comment),
payload: CommentUserActionPayloadWithoutIdsRt,
});
export type CommentUserAction = UserActionWithAttributes<rt.TypeOf<typeof CommentUserActionRt>>;
export type CommentUserActionPayloadWithoutIds = UserActionWithAttributes<
rt.TypeOf<typeof CommentUserActionPayloadWithoutIdsRt>
>;

View file

@ -16,7 +16,7 @@ import {
} from './common';
import { CreateCaseUserActionRt, CreateCaseUserActionWithoutConnectorIdRt } from './create_case';
import { DescriptionUserActionRt } from './description';
import { CommentUserActionRt } from './comment';
import { CommentUserActionRt, CommentUserActionWithoutIdsRt } from './comment';
import { ConnectorUserActionRt, ConnectorUserActionWithoutConnectorIdRt } from './connector';
import { PushedUserActionRt, PushedUserActionWithoutConnectorIdRt } from './pushed';
import { TagsUserActionRt } from './tags';
@ -28,36 +28,39 @@ import { SeverityUserActionRt } from './severity';
import { AssigneesUserActionRt } from './assignees';
import { CaseUserActionStatsRt } from './stats';
const CommonUserActionsRt = rt.union([
const BasicUserActionsRt = rt.union([
DescriptionUserActionRt,
CommentUserActionRt,
TagsUserActionRt,
TitleUserActionRt,
SettingsUserActionRt,
StatusUserActionRt,
SeverityUserActionRt,
AssigneesUserActionRt,
DeleteCaseUserActionRt,
]);
const CommonUserActionsWithIdsRt = rt.union([BasicUserActionsRt, CommentUserActionRt]);
const CommonUserActionsWithoutIdsRt = rt.union([BasicUserActionsRt, CommentUserActionWithoutIdsRt]);
const UserActionPayloadRt = rt.union([
CommonUserActionsRt,
CommonUserActionsWithIdsRt,
CreateCaseUserActionRt,
ConnectorUserActionRt,
PushedUserActionRt,
DeleteCaseUserActionRt,
]);
const UserActionsWithoutConnectorIdRt = rt.union([
CommonUserActionsRt,
const UserActionsWithoutIdsRt = rt.union([
CommonUserActionsWithoutIdsRt,
CreateCaseUserActionWithoutConnectorIdRt,
ConnectorUserActionWithoutConnectorIdRt,
PushedUserActionWithoutConnectorIdRt,
DeleteCaseUserActionRt,
]);
const CaseUserActionBasicRt = rt.intersection([UserActionPayloadRt, UserActionCommonAttributesRt]);
const CaseUserActionBasicWithoutConnectorIdRt = rt.intersection([
UserActionsWithoutConnectorIdRt,
export const CaseUserActionWithoutReferenceIdsRt = rt.intersection([
UserActionsWithoutIdsRt,
UserActionCommonAttributesRt,
]);
@ -86,8 +89,8 @@ export const UserActionsRt = rt.array(UserActionRt);
export const CaseUserActionsDeprecatedResponseRt = rt.array(CaseUserActionDeprecatedResponseRt);
export const CaseUserActionStatsResponseRt = CaseUserActionStatsRt;
export type CaseUserActionAttributesWithoutConnectorId = rt.TypeOf<
typeof CaseUserActionBasicWithoutConnectorIdRt
export type CaseUserActionWithoutReferenceIds = rt.TypeOf<
typeof CaseUserActionWithoutReferenceIdsRt
>;
export type CaseUserActionStatsResponse = rt.TypeOf<typeof CaseUserActionStatsRt>;
export type UserActions = rt.TypeOf<typeof UserActionsRt>;

View file

@ -9,7 +9,6 @@ import type {
Comment,
CommentResponseAlertsType,
CaseUserActionsDeprecatedResponse,
ConnectorMappings,
} from '../../../common/api';
import {
CommentType,
@ -242,24 +241,6 @@ export const basicParams = {
...entity,
};
export const mappings: ConnectorMappings = [
{
source: 'title',
target: 'short_description',
action_type: 'overwrite',
},
{
source: 'description',
target: 'description',
action_type: 'append',
},
{
source: 'comments',
target: 'comments',
action_type: 'append',
},
];
export const userActions: CaseUserActionsDeprecatedResponse = [
{
action: Actions.create,

View file

@ -10,7 +10,6 @@ import {
userActions,
commentAlert,
commentAlertMultipleIds,
mappings,
isolateCommentActions,
releaseCommentActions,
isolateCommentActionsMultipleTargets,
@ -34,7 +33,7 @@ import { flattenCaseSavedObject } from '../../common/utils';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { casesConnectors } from '../../connectors';
import { userProfiles, userProfilesMap } from '../user_profiles.mock';
import { mockCases } from '../../mocks';
import { mappings, mockCases } from '../../mocks';
const allComments = [
commentObj,

View file

@ -7,7 +7,7 @@
import type { SavedObject } from '@kbn/core/server';
import type { UserActionAttributes } from '../../../common/api';
import { UserActionAttributesRt } from '../../../common/api';
import { CaseUserActionWithoutReferenceIdsRt, UserActionAttributesRt } from '../../../common/api';
import type { User } from './user';
interface UserActionCommonPersistedAttributes {
@ -23,6 +23,7 @@ export interface UserActionPersistedAttributes extends UserActionCommonPersisted
}
export const UserActionTransformedAttributesRt = UserActionAttributesRt;
export const UserActionPersistedAttributesRt = CaseUserActionWithoutReferenceIdsRt;
export type UserActionTransformedAttributes = UserActionAttributes;
export type UserActionSavedObjectTransformed = SavedObject<UserActionTransformedAttributes>;

View file

@ -11,6 +11,7 @@ import type {
CommentAttributes,
CommentRequestAlertType,
CommentRequestUserType,
ConnectorMappings,
} from '../common/api';
import { CaseSeverity, CaseStatuses, CommentType, ConnectorTypes } from '../common/api';
import { SECURITY_SOLUTION_OWNER } from '../common/constants';
@ -448,6 +449,24 @@ export const multipleAlert: CommentRequestAlertType = {
index: ['test-index-3', 'test-index-4', 'test-index-5'],
};
export const mappings: ConnectorMappings = [
{
source: 'title',
target: 'short_description',
action_type: 'overwrite',
},
{
source: 'description',
target: 'description',
action_type: 'append',
},
{
source: 'comments',
target: 'comments',
action_type: 'append',
},
];
const casesClientMock = createCasesClientMock();
export const mockCasesContract = (): CasesStart => ({

View file

@ -13,7 +13,7 @@ import type {
SavedObjectsExportTransformContext,
} from '@kbn/core/server';
import type {
CaseUserActionAttributesWithoutConnectorId,
CaseUserActionWithoutReferenceIds,
CommentAttributesWithoutRefs,
} from '../../../common/api';
import {
@ -40,9 +40,7 @@ export async function handleExport({
}): Promise<
Array<
SavedObject<
| CasePersistedAttributes
| CommentAttributesWithoutRefs
| CaseUserActionAttributesWithoutConnectorId
CasePersistedAttributes | CommentAttributesWithoutRefs | CaseUserActionWithoutReferenceIds
>
>
> {
@ -75,9 +73,7 @@ export async function handleExport({
async function getAttachmentsAndUserActionsForCases(
savedObjectsClient: SavedObjectsClientContract,
caseIds: string[]
): Promise<
Array<SavedObject<CommentAttributesWithoutRefs | CaseUserActionAttributesWithoutConnectorId>>
> {
): Promise<Array<SavedObject<CommentAttributesWithoutRefs | CaseUserActionWithoutReferenceIds>>> {
const [attachments, userActions] = await Promise.all([
getAssociatedObjects<CommentAttributesWithoutRefs>({
savedObjectsClient,
@ -85,7 +81,7 @@ async function getAttachmentsAndUserActionsForCases(
sortField: defaultSortField,
type: CASE_COMMENT_SAVED_OBJECT,
}),
getAssociatedObjects<CaseUserActionAttributesWithoutConnectorId>({
getAssociatedObjects<CaseUserActionWithoutReferenceIds>({
savedObjectsClient,
caseIds,
sortField: defaultSortField,

View file

@ -42,7 +42,7 @@ describe('AttachmentService', () => {
describe('create', () => {
describe('Decoding', () => {
it('does not throw when the response has the required fields', async () => {
it('does not throw when the response and the request has the required fields', async () => {
unsecuredSavedObjectsClient.create.mockResolvedValue(createUserAttachment());
await expect(
@ -54,7 +54,7 @@ describe('AttachmentService', () => {
).resolves.not.toThrow();
});
it('strips excess fields', async () => {
it('strips excess fields from the response', async () => {
unsecuredSavedObjectsClient.create.mockResolvedValue(createUserAttachment({ foo: 'bar' }));
const res = await service.create({
@ -82,12 +82,43 @@ describe('AttachmentService', () => {
`"Invalid value \\"undefined\\" supplied to \\"comment\\",Invalid value \\"user\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"alertId\\",Invalid value \\"undefined\\" supplied to \\"index\\",Invalid value \\"undefined\\" supplied to \\"rule\\",Invalid value \\"undefined\\" supplied to \\"actions\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceMetadata\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceStorage\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentState\\""`
);
});
it('throws when the request is missing the attributes.comment', async () => {
const invalidAttachment = createUserAttachment();
unset(invalidAttachment, 'attributes.comment');
unsecuredSavedObjectsClient.create.mockResolvedValue(createUserAttachment());
await expect(
service.create({
attributes: invalidAttachment.attributes,
references: [],
id: '1',
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Invalid value \\"undefined\\" supplied to \\"comment\\",Invalid value \\"user\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"alertId\\",Invalid value \\"undefined\\" supplied to \\"index\\",Invalid value \\"undefined\\" supplied to \\"rule\\",Invalid value \\"undefined\\" supplied to \\"actions\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceMetadata\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceStorage\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentState\\""`
);
});
it('strips excess fields from the request', async () => {
unsecuredSavedObjectsClient.create.mockResolvedValue(createUserAttachment());
await service.create({
// @ts-expect-error: excess attributes
attributes: { ...createUserAttachment().attributes, foo: 'bar' },
references: [],
id: '1',
});
const persistedAttributes = unsecuredSavedObjectsClient.create.mock.calls[0][1];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
});
describe('bulkCreate', () => {
describe('Decoding', () => {
it('does not throw when the response has the required fields', async () => {
it('does not throw when the response and the request has the required fields', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [createUserAttachment()],
});
@ -150,6 +181,43 @@ describe('AttachmentService', () => {
`"Invalid value \\"undefined\\" supplied to \\"comment\\",Invalid value \\"user\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"alertId\\",Invalid value \\"undefined\\" supplied to \\"index\\",Invalid value \\"undefined\\" supplied to \\"rule\\",Invalid value \\"undefined\\" supplied to \\"actions\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceMetadata\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceStorage\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentState\\""`
);
});
it('throws when the request is missing the attributes.comment', async () => {
const invalidAttachment = createUserAttachment();
unset(invalidAttachment, 'attributes.comment');
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [createUserAttachment()],
});
await expect(
service.bulkCreate({
attachments: [{ attributes: invalidAttachment.attributes, references: [], id: '1' }],
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Invalid value \\"undefined\\" supplied to \\"comment\\",Invalid value \\"user\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"alertId\\",Invalid value \\"undefined\\" supplied to \\"index\\",Invalid value \\"undefined\\" supplied to \\"rule\\",Invalid value \\"undefined\\" supplied to \\"actions\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceMetadata\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceId\\",Invalid value \\"undefined\\" supplied to \\"externalReferenceStorage\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentTypeId\\",Invalid value \\"undefined\\" supplied to \\"persistableStateAttachmentState\\""`
);
});
it('strips excess fields from the request', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [createUserAttachment()],
});
await service.bulkCreate({
attachments: [
{
// @ts-expect-error: excess attributes
attributes: { ...createUserAttachment().attributes, foo: 'bar' },
references: [],
id: '1',
},
],
});
const persistedAttributes = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0];
expect(persistedAttributes.attributes).not.toHaveProperty('foo');
});
});
});
@ -205,7 +273,7 @@ describe('AttachmentService', () => {
});
describe('Decoding', () => {
it('does not throw when the response has the required fields', async () => {
it('does not throw when the response and the request has the required fields', async () => {
unsecuredSavedObjectsClient.update.mockResolvedValue(createUserAttachment());
await expect(
@ -235,13 +303,42 @@ describe('AttachmentService', () => {
await expect(
service.update({
updatedAttributes: createUserAttachment().attributes,
updatedAttributes: createAlertAttachment().attributes,
attachmentId: '1',
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Invalid value \\"alert\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"rule,name\\""`
);
});
it('throws when the request is missing the attributes.rule.name', async () => {
const invalidAttachment = createAlertAttachment();
unset(invalidAttachment, 'attributes.rule.name');
unsecuredSavedObjectsClient.update.mockResolvedValue(createAlertAttachment());
await expect(
service.update({
updatedAttributes: invalidAttachment.attributes,
attachmentId: '1',
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Invalid value \\"alert\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"rule,name\\""`
);
});
it('strips excess fields from the request', async () => {
unsecuredSavedObjectsClient.update.mockResolvedValue(createUserAttachment());
await service.update({
// @ts-expect-error: excess attributes
updatedAttributes: { ...createUserAttachment().attributes, foo: 'bar' },
attachmentId: '1',
});
const persistedAttributes = unsecuredSavedObjectsClient.update.mock.calls[0][2];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
});
@ -299,7 +396,7 @@ describe('AttachmentService', () => {
});
describe('Decoding', () => {
it('does not throw when the response has the required fields', async () => {
it('does not throw when the response and the request has the required fields', async () => {
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
saved_objects: [createUserAttachment()],
});
@ -360,6 +457,49 @@ describe('AttachmentService', () => {
`"Invalid value \\"alert\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"rule,name\\""`
);
});
it('throws when the request is missing the attributes.rule.name', async () => {
const invalidAttachment = createAlertAttachment();
unset(invalidAttachment, 'attributes.rule.name');
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
saved_objects: [createAlertAttachment()],
});
await expect(
service.bulkUpdate({
comments: [
{
updatedAttributes: invalidAttachment.attributes,
attachmentId: '1',
},
],
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Invalid value \\"alert\\" supplied to \\"type\\",Invalid value \\"undefined\\" supplied to \\"rule,name\\""`
);
});
it('strips excess fields from the request', async () => {
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
saved_objects: [createUserAttachment()],
});
await service.bulkUpdate({
comments: [
{
// @ts-expect-error: excess attributes
updatedAttributes: { ...createUserAttachment().attributes, foo: 'bar' },
attachmentId: '1',
},
],
});
const persistedAttributes =
unsecuredSavedObjectsClient.bulkUpdate.mock.calls[0][0][0].attributes;
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
});

View file

@ -14,7 +14,7 @@ import type {
} from '@kbn/core/server';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { CommentType, decodeOrThrow } from '../../../common/api';
import { CommentAttributesRt, CommentType, decodeOrThrow } from '../../../common/api';
import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../../common/constants';
import { buildFilter, combineFilters } from '../../client/utils';
import { defaultSortField, isSOError } from '../../common/utils';
@ -167,9 +167,11 @@ export class AttachmentService {
try {
this.context.log.debug(`Attempting to POST a new comment`);
const decodedAttributes = decodeOrThrow(CommentAttributesRt)(attributes);
const { attributes: extractedAttributes, references: extractedReferences } =
extractAttachmentSORefsFromAttributes(
attributes,
decodedAttributes,
references,
this.context.persistableStateAttachmentTypeRegistry
);
@ -210,9 +212,11 @@ export class AttachmentService {
const res =
await this.context.unsecuredSavedObjectsClient.bulkCreate<AttachmentPersistedAttributes>(
attachments.map((attachment) => {
const decodedAttributes = decodeOrThrow(CommentAttributesRt)(attachment.attributes);
const { attributes: extractedAttributes, references: extractedReferences } =
extractAttachmentSORefsFromAttributes(
attachment.attributes,
decodedAttributes,
attachment.references,
this.context.persistableStateAttachmentTypeRegistry
);
@ -267,12 +271,14 @@ export class AttachmentService {
try {
this.context.log.debug(`Attempting to UPDATE comment ${attachmentId}`);
const decodedAttributes = decodeOrThrow(AttachmentPartialAttributesRt)(updatedAttributes);
const {
attributes: extractedAttributes,
references: extractedReferences,
didDeleteOperation,
} = extractAttachmentSORefsFromAttributes(
updatedAttributes,
decodedAttributes,
options?.references ?? [],
this.context.persistableStateAttachmentTypeRegistry
);
@ -327,12 +333,16 @@ export class AttachmentService {
const res =
await this.context.unsecuredSavedObjectsClient.bulkUpdate<AttachmentPersistedAttributes>(
comments.map((c) => {
const decodedAttributes = decodeOrThrow(AttachmentPartialAttributesRt)(
c.updatedAttributes
);
const {
attributes: extractedAttributes,
references: extractedReferences,
didDeleteOperation,
} = extractAttachmentSORefsFromAttributes(
c.updatedAttributes,
decodedAttributes,
c.options?.references ?? [],
this.context.persistableStateAttachmentTypeRegistry
);

View file

@ -2705,4 +2705,134 @@ describe('CasesService', () => {
});
});
});
describe('Decoding requests', () => {
describe('create case', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse());
});
it('decodes correctly the requested attributes', async () => {
const attributes = createCasePostParams({ connector: createJiraConnector() });
await expect(service.postNewCase({ id: 'a', attributes })).resolves.not.toThrow();
});
it('throws if title is omitted', async () => {
const attributes = createCasePostParams({ connector: createJiraConnector() });
unset(attributes, 'title');
await expect(
service.postNewCase({
attributes,
id: '1',
})
).rejects.toThrow(`Invalid value "undefined" supplied to "title"`);
});
it('remove excess fields', async () => {
const attributes = {
...createCasePostParams({ connector: createJiraConnector() }),
foo: 'bar',
};
await expect(service.postNewCase({ id: 'a', attributes })).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.create.mock.calls[0][1];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
describe('patch case', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.update.mockResolvedValue(
{} as SavedObjectsUpdateResponse<CasePersistedAttributes>
);
});
it('decodes correctly the requested attributes', async () => {
const updatedAttributes = createCasePostParams({
connector: createJiraConnector(),
status: CaseStatuses['in-progress'],
});
await expect(
service.patchCase({
caseId: '1',
updatedAttributes,
originalCase: {} as CaseSavedObjectTransformed,
})
).resolves.not.toThrow();
});
it('remove excess fields', async () => {
const updatedAttributes = {
...createCasePostParams({ connector: createJiraConnector() }),
foo: 'bar',
};
await expect(
service.patchCase({
caseId: '1',
updatedAttributes,
originalCase: {} as CaseSavedObjectTransformed,
})
).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.update.mock.calls[0][2];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
describe('patch cases', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({
saved_objects: [createCaseSavedObjectResponse({ caseId: '1' })],
});
});
it('decodes correctly the requested attributes', async () => {
const updatedAttributes = createCasePostParams({
connector: createJiraConnector(),
status: CaseStatuses['in-progress'],
});
await expect(
service.patchCases({
cases: [
{
caseId: '1',
updatedAttributes,
originalCase: {} as CaseSavedObjectTransformed,
},
],
})
).resolves.not.toThrow();
});
it('remove excess fields', async () => {
const updatedAttributes = {
...createCasePostParams({ connector: createJiraConnector() }),
foo: 'bar',
};
await expect(
service.patchCases({
cases: [
{
caseId: '1',
updatedAttributes,
originalCase: {} as CaseSavedObjectTransformed,
},
],
})
).resolves.not.toThrow();
const persistedAttributes =
unsecuredSavedObjectsClient.bulkUpdate.mock.calls[0][0][0].attributes;
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
});
});

View file

@ -73,6 +73,8 @@ import type {
import type { AttachmentTransformedAttributes } from '../../common/types/attachments';
import { bulkDecodeSOAttributes } from '../utils';
const PartialCaseTransformedAttributesRt = getPartialCaseTransformedAttributesRt();
export class CasesService {
private readonly log: Logger;
private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract;
@ -562,7 +564,9 @@ export class CasesService {
}: PostCaseArgs): Promise<CaseSavedObjectTransformed> {
try {
this.log.debug(`Attempting to POST a new case`);
const transformedAttributes = transformAttributesToESModel(attributes);
const decodedAttributes = decodeOrThrow(CaseTransformedAttributesRt)(attributes);
const transformedAttributes = transformAttributesToESModel(decodedAttributes);
transformedAttributes.attributes.total_alerts = -1;
transformedAttributes.attributes.total_comments = -1;
@ -592,7 +596,11 @@ export class CasesService {
}: PatchCaseArgs): Promise<SavedObjectsUpdateResponse<CaseTransformedAttributes>> {
try {
this.log.debug(`Attempting to UPDATE case ${caseId}`);
const transformedAttributes = transformAttributesToESModel(updatedAttributes);
const decodedAttributes = decodeOrThrow(PartialCaseTransformedAttributesRt)(
updatedAttributes
);
const transformedAttributes = transformAttributesToESModel(decodedAttributes);
const updatedCase = await this.unsecuredSavedObjectsClient.update<CasePersistedAttributes>(
CASE_SAVED_OBJECT,
@ -606,7 +614,7 @@ export class CasesService {
);
const res = transformUpdateResponseToExternalModel(updatedCase);
const decodeRes = decodeOrThrow(getPartialCaseTransformedAttributesRt())(res.attributes);
const decodeRes = decodeOrThrow(PartialCaseTransformedAttributesRt)(res.attributes);
return {
...res,
@ -626,7 +634,11 @@ export class CasesService {
this.log.debug(`Attempting to UPDATE case ${cases.map((c) => c.caseId).join(', ')}`);
const bulkUpdate = cases.map(({ caseId, updatedAttributes, version, originalCase }) => {
const { attributes, referenceHandler } = transformAttributesToESModel(updatedAttributes);
const decodedAttributes = decodeOrThrow(PartialCaseTransformedAttributesRt)(
updatedAttributes
);
const { attributes, referenceHandler } = transformAttributesToESModel(decodedAttributes);
return {
type: CASE_SAVED_OBJECT,
id: caseId,
@ -648,7 +660,7 @@ export class CasesService {
}
const so = Object.assign(theCase, transformUpdateResponseToExternalModel(theCase));
const decodeRes = decodeOrThrow(getPartialCaseTransformedAttributesRt())(so.attributes);
const decodeRes = decodeOrThrow(PartialCaseTransformedAttributesRt)(so.attributes);
const soWithDecodedRes = Object.assign(so, { attributes: decodeRes });
acc.push(soWithDecodedRes);

View file

@ -29,6 +29,7 @@ import { getNoneCaseConnector } from '../../common/utils';
import type { ESCaseConnectorWithId } from '../test_utils';
import { createESJiraConnector, createJiraConnector } from '../test_utils';
import type { ConfigurationPersistedAttributes } from '../../common/types/configure';
import { unset } from 'lodash';
const basicConfigFields = {
closure_type: 'close-by-pushing' as const,
@ -744,4 +745,94 @@ describe('CaseConfigureService', () => {
});
});
});
describe('Decoding requests', () => {
describe('post', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.create.mockResolvedValue({
attributes: createConfigPostParams(createJiraConnector()),
id: '1',
type: CASE_CONFIGURE_SAVED_OBJECT,
references: [],
});
});
it('decodes correctly the requested attributes', async () => {
const attributes = createConfigPostParams(createJiraConnector());
await expect(
service.post({
unsecuredSavedObjectsClient,
attributes,
id: '1',
})
).resolves.not.toThrow();
});
it('throws if closure_type is omitted', async () => {
const attributes = createConfigPostParams(createJiraConnector());
unset(attributes, 'closure_type');
await expect(
service.post({
unsecuredSavedObjectsClient,
attributes,
id: '1',
})
).rejects.toThrow(`Invalid value "undefined" supplied to "closure_type"`);
});
it('strips out excess attributes', async () => {
const attributes = { ...createConfigPostParams(createJiraConnector()), foo: 'bar' };
await expect(
service.post({
unsecuredSavedObjectsClient,
attributes,
id: '1',
})
).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.create.mock.calls[0][1];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
describe('patch', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.update.mockReturnValue(
Promise.resolve({} as SavedObjectsUpdateResponse<ConfigurationPatchRequest>)
);
});
it('decodes correctly the requested attributes', async () => {
const updatedAttributes = createConfigPostParams(createJiraConnector());
await expect(
service.patch({
configurationId: '1',
unsecuredSavedObjectsClient,
updatedAttributes,
originalConfiguration: {} as SavedObject<ConfigurationAttributes>,
})
).resolves.not.toThrow();
});
it('strips out excess attributes', async () => {
const updatedAttributes = { ...createConfigPostParams(createJiraConnector()), foo: 'bar' };
await expect(
service.patch({
configurationId: '1',
unsecuredSavedObjectsClient,
updatedAttributes,
originalConfiguration: {} as SavedObject<ConfigurationAttributes>,
})
).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.update.mock.calls[0][2];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
});
});

View file

@ -118,7 +118,10 @@ export class CaseConfigureService {
}: PostCaseConfigureArgs): Promise<ConfigurationSavedObjectTransformed> {
try {
this.log.debug(`Attempting to POST a new case configuration`);
const esConfigInfo = transformAttributesToESModel(attributes);
const decodedAttributes = decodeOrThrow(ConfigurationTransformedAttributesRt)(attributes);
const esConfigInfo = transformAttributesToESModel(decodedAttributes);
const createdConfig =
await unsecuredSavedObjectsClient.create<ConfigurationPersistedAttributes>(
CASE_CONFIGURE_SAVED_OBJECT,
@ -144,7 +147,9 @@ export class CaseConfigureService {
> {
try {
this.log.debug(`Attempting to UPDATE case configuration ${configurationId}`);
const esUpdateInfo = transformAttributesToESModel(updatedAttributes);
const decodedAttributes = decodeOrThrow(ConfigurationPartialAttributesRt)(updatedAttributes);
const esUpdateInfo = transformAttributesToESModel(decodedAttributes);
const updatedConfiguration =
await unsecuredSavedObjectsClient.update<ConfigurationPersistedAttributes>(

View file

@ -0,0 +1,117 @@
/*
* 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 { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../common/constants';
import type { SavedObjectsUpdateResponse } from '@kbn/core-saved-objects-api-server';
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
import { loggerMock } from '@kbn/logging-mocks';
import { unset } from 'lodash';
import { ConnectorMappingsService } from '.';
import { mappings } from '../../mocks';
import type { ConnectorMappingsPersistedAttributes } from '../../common/types/connector_mappings';
describe('CaseConfigureService', () => {
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const mockLogger = loggerMock.create();
let service: ConnectorMappingsService;
beforeEach(() => {
jest.resetAllMocks();
service = new ConnectorMappingsService(mockLogger);
});
describe('Decoding requests', () => {
describe('post', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.create.mockResolvedValue({
attributes: { mappings, owner: 'cases' },
id: '1',
type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT,
references: [],
});
});
it('decodes correctly the requested attributes', async () => {
const attributes = { mappings, owner: 'cases' };
await expect(
service.post({
unsecuredSavedObjectsClient,
attributes,
references: [],
})
).resolves.not.toThrow();
});
it('throws if mappings is omitted', async () => {
const attributes = { mappings, owner: 'cases' };
unset(attributes, 'mappings');
await expect(
service.post({
unsecuredSavedObjectsClient,
attributes,
references: [],
})
).rejects.toThrow(`Invalid value "undefined" supplied to "mappings"`);
});
it('strips out excess attributes', async () => {
const attributes = { mappings, owner: 'cases', foo: 'bar' };
await expect(
service.post({
unsecuredSavedObjectsClient,
attributes,
references: [],
})
).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.create.mock.calls[0][1];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
describe('update', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.update.mockResolvedValue({
attributes: { mappings, owner: 'cases' },
} as SavedObjectsUpdateResponse<ConnectorMappingsPersistedAttributes>);
});
it('decodes correctly the requested attributes', async () => {
const updatedAttributes = { mappings, owner: 'cases' };
await expect(
service.update({
mappingId: '1',
unsecuredSavedObjectsClient,
attributes: updatedAttributes,
references: [],
})
).resolves.not.toThrow();
});
it('strips out excess attributes', async () => {
const updatedAttributes = { mappings, owner: 'cases', foo: 'bar' };
await expect(
service.update({
mappingId: '1',
unsecuredSavedObjectsClient,
attributes: updatedAttributes,
references: [],
})
).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.update.mock.calls[0][2];
expect(persistedAttributes).not.toHaveProperty('foo');
});
});
});
});

View file

@ -73,10 +73,13 @@ export class ConnectorMappingsService {
}: PostConnectorMappingsArgs): Promise<ConnectorMappingsSavedObjectTransformed> {
try {
this.log.debug(`Attempting to POST a new connector mappings`);
const decodedAttributes = decodeOrThrow(ConnectorMappingsAttributesTransformedRt)(attributes);
const connectorMappings =
await unsecuredSavedObjectsClient.create<ConnectorMappingsPersistedAttributes>(
CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT,
attributes,
decodedAttributes,
{
references,
refresh,
@ -105,11 +108,14 @@ export class ConnectorMappingsService {
> {
try {
this.log.debug(`Attempting to UPDATE connector mappings ${mappingId}`);
const decodedAttributes = decodeOrThrow(ConnectorMappingsAttributesPartialRt)(attributes);
const updatedMappings =
await unsecuredSavedObjectsClient.update<ConnectorMappingsPersistedAttributes>(
CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT,
mappingId,
attributes,
decodedAttributes,
{
references,
refresh,

View file

@ -15,10 +15,7 @@ import type {
SavedObjectsUpdateResponse,
} from '@kbn/core/server';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import type {
CaseAttributes,
CaseUserActionAttributesWithoutConnectorId,
} from '../../../common/api';
import type { CaseAttributes, CaseUserActionWithoutReferenceIds } from '../../../common/api';
import { Actions, ActionTypes, CaseSeverity, CaseStatuses } from '../../../common/api';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
@ -1654,9 +1651,7 @@ describe('CaseUserActionService', () => {
});
describe('getConnectorFieldsBeforeLatestPush', () => {
const getAggregations = (
userAction: SavedObject<CaseUserActionAttributesWithoutConnectorId>
) => {
const getAggregations = (userAction: SavedObject<CaseUserActionWithoutReferenceIds>) => {
const connectors = set({}, 'servicenow.mostRecent.hits.hits', [userAction]);
const aggregations = set({}, 'references.connectors.reverse.ids.buckets', connectors);
@ -1861,8 +1856,8 @@ describe('CaseUserActionService', () => {
describe('getCaseConnectorInformation', () => {
const getAggregations = (
userAction: SavedObject<CaseUserActionAttributesWithoutConnectorId>,
pushUserAction: SavedObject<CaseUserActionAttributesWithoutConnectorId>
userAction: SavedObject<CaseUserActionWithoutReferenceIds>,
pushUserAction: SavedObject<CaseUserActionWithoutReferenceIds>
) => {
const changeConnector = set({}, 'mostRecent.hits.hits', [userAction]);
const createCase = set({}, 'mostRecent.hits.hits', []);

View file

@ -0,0 +1,144 @@
/*
* 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 { CASE_USER_ACTION_SAVED_OBJECT } from '../../../../common/constants';
import { PersistableStateAttachmentTypeRegistry } from '../../../attachment_framework/persistable_state_registry';
import { createSavedObjectsSerializerMock } from '../../../client/mocks';
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
import { loggerMock } from '@kbn/logging-mocks';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { set, unset } from 'lodash';
import { createConnectorObject } from '../../test_utils';
import { UserActionPersister } from './create';
import { createUserActionSO } from '../test_utils';
import type { BulkCreateAttachmentUserAction, CreateUserActionClient } from '../types';
import type { UserActionPersistedAttributes } from '../../../common/types/user_actions';
import { CommentType } from '../../../../common';
describe('UserActionPersister', () => {
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const mockLogger = loggerMock.create();
const auditMockLocker = auditLoggerMock.create();
const persistableStateAttachmentTypeRegistry = new PersistableStateAttachmentTypeRegistry();
const savedObjectsSerializer = createSavedObjectsSerializerMock();
let persister: UserActionPersister;
beforeEach(() => {
jest.resetAllMocks();
persister = new UserActionPersister({
log: mockLogger,
unsecuredSavedObjectsClient,
persistableStateAttachmentTypeRegistry,
savedObjectsSerializer,
auditLogger: auditMockLocker,
});
});
const getRequest = () =>
({
action: 'update' as const,
type: 'connector' as const,
caseId: 'test',
payload: { connector: createConnectorObject().connector },
connectorId: '1',
owner: 'cases',
user: { email: '', full_name: '', username: '' },
} as CreateUserActionClient<'connector'>);
const getBulkCreateAttachmentRequest = (): BulkCreateAttachmentUserAction => ({
caseId: 'test',
attachments: [
{
id: '1',
owner: 'cases',
attachment: { comment: 'test', type: CommentType.user, owner: 'cases' },
},
],
user: { email: '', full_name: '', username: '' },
});
describe('Decoding requests', () => {
describe('createUserAction', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.create.mockResolvedValue({
attributes: createUserActionSO(),
id: '1',
type: CASE_USER_ACTION_SAVED_OBJECT,
references: [],
});
});
it('decodes correctly the requested attributes', async () => {
await expect(persister.createUserAction(getRequest())).resolves.not.toThrow();
});
it('throws if fields is omitted', async () => {
const req = getRequest();
unset(req, 'payload.connector.fields');
await expect(persister.createUserAction(req)).rejects.toThrow(
'Invalid value "undefined" supplied to "payload,connector,fields"'
);
});
it('strips out excess attributes', async () => {
const req = getRequest();
set(req, 'payload.foo', 'bar');
await expect(persister.createUserAction(req)).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.create.mock
.calls[0][1] as UserActionPersistedAttributes;
expect(persistedAttributes.payload).not.toHaveProperty('foo');
});
});
describe('bulkCreateAttachmentCreation', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
attributes: createUserActionSO(),
id: '1',
type: CASE_USER_ACTION_SAVED_OBJECT,
references: [],
},
],
});
});
it('decodes correctly the requested attributes', async () => {
await expect(
persister.bulkCreateAttachmentCreation(getBulkCreateAttachmentRequest())
).resolves.not.toThrow();
});
it('throws if owner is omitted', async () => {
const req = getBulkCreateAttachmentRequest();
unset(req, 'attachments[0].owner');
await expect(persister.bulkCreateAttachmentCreation(req)).rejects.toThrow(
'Invalid value "undefined" supplied to "owner"'
);
});
it('strips out excess attributes', async () => {
const req = getBulkCreateAttachmentRequest();
set(req, 'attachments[0].attachment.foo', 'bar');
await expect(persister.bulkCreateAttachmentCreation(req)).resolves.not.toThrow();
const persistedAttributes = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0]
.attributes as UserActionPersistedAttributes;
expect(persistedAttributes.payload).not.toHaveProperty('foo');
});
});
});
});

View file

@ -8,6 +8,7 @@
import type { SavedObject, SavedObjectsBulkResponse } from '@kbn/core/server';
import { get, isEmpty } from 'lodash';
import type { UserActionPersistedAttributes } from '../../../common/types/user_actions';
import { UserActionPersistedAttributesRt } from '../../../common/types/user_actions';
import { CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT } from '../../../../common/constants';
import { arraysDifference } from '../../../client/utils';
import { isUserActionType } from '../../../../common/utils/user_actions';
@ -17,7 +18,7 @@ import type {
CaseUserProfile,
ActionCategory,
} from '../../../../common/api';
import { Actions, ActionTypes } from '../../../../common/api';
import { Actions, ActionTypes, decodeOrThrow } from '../../../../common/api';
import { BuilderFactory } from '../builder_factory';
import type {
BuilderParameters,
@ -300,17 +301,24 @@ export class UserActionPersister {
}
try {
this.context.log.debug(`Attempting to POST a new case user action`);
this.context.log.debug(`Attempting to bulk create user actions`);
return await this.context.unsecuredSavedObjectsClient.bulkCreate<UserActionPersistedAttributes>(
actions.map((action) => ({
type: CASE_USER_ACTION_SAVED_OBJECT,
...action.parameters,
})),
actions.map((action) => {
const decodedAttributes = decodeOrThrow(UserActionPersistedAttributesRt)(
action.parameters.attributes
);
return {
type: CASE_USER_ACTION_SAVED_OBJECT,
attributes: decodedAttributes,
references: action.parameters.references,
};
}),
{ refresh }
);
} catch (error) {
this.context.log.error(`Error on POST a new case user action: ${error}`);
this.context.log.error(`Error on bulk creating user action: ${error}`);
throw error;
}
}
@ -367,9 +375,11 @@ export class UserActionPersister {
try {
this.context.log.debug(`Attempting to POST a new case user action`);
const decodedAttributes = decodeOrThrow(UserActionPersistedAttributesRt)(attributes);
return await this.context.unsecuredSavedObjectsClient.create<T>(
CASE_USER_ACTION_SAVED_OBJECT,
attributes,
decodedAttributes as unknown as T,
{
references: references ?? [],
refresh,

View file

@ -19,7 +19,7 @@ import {
SECURITY_SOLUTION_OWNER,
} from '../../../common/constants';
import type {
CaseUserActionAttributesWithoutConnectorId,
CaseUserActionWithoutReferenceIds,
ConnectorUserAction,
ActionCategory,
} from '../../../common/api';
@ -45,15 +45,15 @@ import type { PersistableStateAttachmentTypeRegistry } from '../../attachment_fr
import { transformFindResponseToExternalModel } from './transform';
export const createUserActionFindSO = (
userAction: SavedObject<CaseUserActionAttributesWithoutConnectorId>
): SavedObjectsFindResult<CaseUserActionAttributesWithoutConnectorId> => ({
userAction: SavedObject<CaseUserActionWithoutReferenceIds>
): SavedObjectsFindResult<CaseUserActionWithoutReferenceIds> => ({
...userAction,
score: 0,
});
export const createConnectorUserAction = (
overrides?: Partial<CaseUserActionAttributesWithoutConnectorId>
): SavedObject<CaseUserActionAttributesWithoutConnectorId> => {
overrides?: Partial<CaseUserActionWithoutReferenceIds>
): SavedObject<CaseUserActionWithoutReferenceIds> => {
const { id, ...restConnector } = createConnectorObject().connector;
return {
...createUserActionSO({
@ -79,12 +79,12 @@ export const createUserActionSO = ({
action?: ActionCategory;
type?: string;
payload?: Record<string, unknown>;
attributesOverrides?: Partial<CaseUserActionAttributesWithoutConnectorId>;
attributesOverrides?: Partial<CaseUserActionWithoutReferenceIds>;
commentId?: string;
connectorId?: string;
pushedConnectorId?: string;
references?: SavedObjectReference[];
} = {}): SavedObject<CaseUserActionAttributesWithoutConnectorId> => {
} = {}): SavedObject<CaseUserActionWithoutReferenceIds> => {
const defaultParams = {
action,
created_at: 'abc',
@ -140,14 +140,14 @@ export const createUserActionSO = ({
]
: []),
],
} as SavedObject<CaseUserActionAttributesWithoutConnectorId>;
} as SavedObject<CaseUserActionWithoutReferenceIds>;
};
export const updateConnectorUserAction = ({
overrides,
}: {
overrides?: Partial<CaseUserActionAttributesWithoutConnectorId>;
} = {}): SavedObject<CaseUserActionAttributesWithoutConnectorId> => {
overrides?: Partial<CaseUserActionWithoutReferenceIds>;
} = {}): SavedObject<CaseUserActionWithoutReferenceIds> => {
const { id, ...restConnector } = createJiraConnector();
return {
...createUserActionSO({
@ -163,8 +163,8 @@ export const updateConnectorUserAction = ({
export const pushConnectorUserAction = ({
overrides,
}: {
overrides?: Partial<CaseUserActionAttributesWithoutConnectorId>;
} = {}): SavedObject<CaseUserActionAttributesWithoutConnectorId> => {
overrides?: Partial<CaseUserActionWithoutReferenceIds>;
} = {}): SavedObject<CaseUserActionWithoutReferenceIds> => {
const { connector_id: connectorId, ...restExternalService } = createExternalService();
return {
...createUserActionSO({
@ -177,7 +177,7 @@ export const pushConnectorUserAction = ({
};
};
export const createCaseUserAction = (): SavedObject<CaseUserActionAttributesWithoutConnectorId> => {
export const createCaseUserAction = (): SavedObject<CaseUserActionWithoutReferenceIds> => {
const { id, ...restConnector } = createJiraConnector();
return {
...createUserActionSO({
@ -231,7 +231,7 @@ export const createExternalReferenceUserAction = () => {
export const testConnectorId = (
persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry,
userAction: SavedObject<CaseUserActionAttributesWithoutConnectorId>,
userAction: SavedObject<CaseUserActionWithoutReferenceIds>,
path: string,
expectedConnectorId = '1'
) => {
@ -252,9 +252,7 @@ export const testConnectorId = (
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(
invalidUserAction as SavedObject<CaseUserActionAttributesWithoutConnectorId>
),
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionWithoutReferenceIds>),
]),
persistableStateAttachmentTypeRegistry
);
@ -269,9 +267,7 @@ export const testConnectorId = (
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(
invalidUserAction as SavedObject<CaseUserActionAttributesWithoutConnectorId>
),
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionWithoutReferenceIds>),
]),
persistableStateAttachmentTypeRegistry
) as SavedObjectsFindResponse<ConnectorUserAction>;

View file

@ -23,7 +23,7 @@ import type {
CaseSettings,
CaseSeverity,
CaseStatuses,
CaseUserActionAttributesWithoutConnectorId,
CaseUserActionWithoutReferenceIds,
CommentRequest,
CommentUserAction,
ConnectorUserAction,
@ -174,7 +174,7 @@ export interface PushInfo {
}
export interface UserActionItem {
attributes: CaseUserActionAttributesWithoutConnectorId;
attributes: CaseUserActionWithoutReferenceIds;
references: SavedObjectReference[];
}

View file

@ -24,7 +24,7 @@ import {
CreateCaseUserAction,
CaseStatuses,
CaseSeverity,
CaseUserActionAttributesWithoutConnectorId,
CaseUserActionWithoutReferenceIds,
} from '@kbn/cases-plugin/common/api';
import {
CasePersistedSeverity,
@ -164,15 +164,11 @@ const expectImportToHaveOneCase = async (supertestService: supertest.SuperTest<s
expect(findResponse.cases[0].description).to.eql('super description');
};
const expectImportToHaveCreateCaseUserAction = (
userAction: CaseUserActionAttributesWithoutConnectorId
) => {
const expectImportToHaveCreateCaseUserAction = (userAction: CaseUserActionWithoutReferenceIds) => {
expect(userAction.action).to.eql('create');
};
const expectImportToHavePushUserAction = (
userAction: CaseUserActionAttributesWithoutConnectorId
) => {
const expectImportToHavePushUserAction = (userAction: CaseUserActionWithoutReferenceIds) => {
const pushedUserAction = userAction as PushedUserAction;
expect(userAction.action).to.eql('push_to_service');
expect(userAction.type).to.eql('pushed');
@ -183,9 +179,7 @@ const expectImportToHavePushUserAction = (
);
};
const expectImportToHaveUpdateConnector = (
userAction: CaseUserActionAttributesWithoutConnectorId
) => {
const expectImportToHaveUpdateConnector = (userAction: CaseUserActionWithoutReferenceIds) => {
const connectorUserAction = userAction as ConnectorUserAction;
expect(userAction.action).to.eql('update');
expect(userAction.type).to.eql('connector');
@ -220,7 +214,7 @@ const expectExportToHaveCaseSavedObject = (
};
const expectExportToHaveUserActions = (objects: SavedObject[], caseRequest: CasePostRequest) => {
const userActionSOs = findSavedObjectsByType<CaseUserActionAttributesWithoutConnectorId>(
const userActionSOs = findSavedObjectsByType<CaseUserActionWithoutReferenceIds>(
objects,
CASE_USER_ACTION_SAVED_OBJECT
);
@ -232,7 +226,7 @@ const expectExportToHaveUserActions = (objects: SavedObject[], caseRequest: Case
};
const expectCaseCreateUserAction = (
userActions: Array<SavedObject<CaseUserActionAttributesWithoutConnectorId>>,
userActions: Array<SavedObject<CaseUserActionWithoutReferenceIds>>,
caseRequest: CasePostRequest
) => {
const userActionForCaseCreate = findUserActionSavedObject(userActions, 'create', 'create_case');
@ -259,7 +253,7 @@ const expectCaseCreateUserAction = (
};
const expectCreateCommentUserAction = (
userActions: Array<SavedObject<CaseUserActionAttributesWithoutConnectorId>>
userActions: Array<SavedObject<CaseUserActionWithoutReferenceIds>>
) => {
const userActionForComment = findUserActionSavedObject(userActions, 'create', 'comment');
const createCommentUserAction = userActionForComment!.attributes as CommentUserAction;
@ -287,9 +281,9 @@ const findSavedObjectsByType = <ReturnType>(
};
const findUserActionSavedObject = (
savedObjects: Array<SavedObject<CaseUserActionAttributesWithoutConnectorId>>,
savedObjects: Array<SavedObject<CaseUserActionWithoutReferenceIds>>,
action: string,
type: string
): SavedObject<CaseUserActionAttributesWithoutConnectorId> | undefined => {
): SavedObject<CaseUserActionWithoutReferenceIds> | undefined => {
return savedObjects.find((so) => so.attributes.action === action && so.attributes.type === type);
};