[Threat Hunting Investigations] Use OpenAPI types in more timeline routes (#189977)

## Summary

Fixes: https://github.com/elastic/security-team/issues/10133

Migrates some timeline routes to use the newly generated OpenAPI types.
The changes mostly affect pinned event and note routes to keep the
changes small. Routes that actually accept and return timeline objects
will come in a next step.


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jan Monschke 2024-09-24 08:49:44 +02:00 committed by GitHub
parent 80f938e174
commit caad89426c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 681 additions and 566 deletions

View file

@ -15090,7 +15090,8 @@ paths:
type: string
required:
- noteId
- type: object
- nullable: true
type: object
properties:
noteIds:
items:
@ -15121,19 +15122,18 @@ paths:
parameters:
- in: query
name: documentIds
required: true
schema:
$ref: '#/components/schemas/Security_Solution_Timeline_API_DocumentIds'
- in: query
name: page
schema:
nullable: true
type: number
type: string
- in: query
name: perPage
schema:
nullable: true
type: number
type: string
- in: query
name: search
schema:
@ -15156,6 +15156,13 @@ paths:
type: string
responses:
'200':
content:
application/json; Elastic-Api-Version=2023-10-31:
schema:
oneOf:
- $ref: >-
#/components/schemas/Security_Solution_Timeline_API_GetNotesResult
- type: object
description: Indicates the requested notes were returned.
summary: Get notes
tags:
@ -15205,19 +15212,8 @@ paths:
type: object
properties:
persistNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: >-
#/components/schemas/Security_Solution_Timeline_API_Note
required:
- code
- message
- note
$ref: >-
#/components/schemas/Security_Solution_Timeline_API_ResponseNote
required:
- persistNote
required:
@ -15589,15 +15585,8 @@ paths:
type: object
properties:
persistPinnedEventOnTimeline:
allOf:
- $ref: >-
#/components/schemas/Security_Solution_Timeline_API_PinnedEvent
- type: object
properties:
code:
type: number
message:
type: string
$ref: >-
#/components/schemas/Security_Solution_Timeline_API_PersistPinnedEventResponse
required:
- persistPinnedEventOnTimeline
required:
@ -32400,7 +32389,6 @@ components:
nullable: true
type: string
timelineId:
nullable: true
type: string
updated:
nullable: true
@ -32410,6 +32398,28 @@ components:
type: string
required:
- timelineId
Security_Solution_Timeline_API_BarePinnedEvent:
type: object
properties:
created:
nullable: true
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
required:
- eventId
- timelineId
Security_Solution_Timeline_API_ColumnHeaderResult:
type: object
properties:
@ -32584,6 +32594,18 @@ components:
type: string
script:
type: string
Security_Solution_Timeline_API_GetNotesResult:
type: object
properties:
notes:
items:
$ref: '#/components/schemas/Security_Solution_Timeline_API_Note'
type: array
totalCount:
type: number
required:
- totalCount
- notes
Security_Solution_Timeline_API_ImportTimelineResult:
type: object
properties:
@ -32644,34 +32666,38 @@ components:
type: string
version:
type: string
required:
- noteId
- version
Security_Solution_Timeline_API_PersistPinnedEventResponse:
oneOf:
- allOf:
- $ref: '#/components/schemas/Security_Solution_Timeline_API_PinnedEvent'
- $ref: >-
#/components/schemas/Security_Solution_Timeline_API_PinnedEventBaseResponseBody
- nullable: true
type: object
Security_Solution_Timeline_API_PinnedEvent:
allOf:
- $ref: '#/components/schemas/Security_Solution_Timeline_API_BarePinnedEvent'
- type: object
properties:
pinnedEventId:
type: string
version:
type: string
required:
- pinnedEventId
- version
Security_Solution_Timeline_API_PinnedEventBaseResponseBody:
type: object
properties:
created:
nullable: true
code:
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
pinnedEventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
version:
message:
type: string
required:
- eventId
- pinnedEventId
- timelineId
- version
- code
Security_Solution_Timeline_API_QueryMatchResult:
type: object
properties:
@ -32716,6 +32742,19 @@ components:
type: object
readable:
type: boolean
Security_Solution_Timeline_API_ResponseNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: '#/components/schemas/Security_Solution_Timeline_API_Note'
required:
- code
- message
- note
Security_Solution_Timeline_API_RowRendererId:
enum:
- alert

View file

@ -18539,7 +18539,8 @@ paths:
type: string
required:
- noteId
- type: object
- nullable: true
type: object
properties:
noteIds:
items:
@ -18570,19 +18571,18 @@ paths:
parameters:
- in: query
name: documentIds
required: true
schema:
$ref: '#/components/schemas/Security_Solution_Timeline_API_DocumentIds'
- in: query
name: page
schema:
nullable: true
type: number
type: string
- in: query
name: perPage
schema:
nullable: true
type: number
type: string
- in: query
name: search
schema:
@ -18605,6 +18605,13 @@ paths:
type: string
responses:
'200':
content:
application/json; Elastic-Api-Version=2023-10-31:
schema:
oneOf:
- $ref: >-
#/components/schemas/Security_Solution_Timeline_API_GetNotesResult
- type: object
description: Indicates the requested notes were returned.
summary: Get notes
tags:
@ -18654,19 +18661,8 @@ paths:
type: object
properties:
persistNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: >-
#/components/schemas/Security_Solution_Timeline_API_Note
required:
- code
- message
- note
$ref: >-
#/components/schemas/Security_Solution_Timeline_API_ResponseNote
required:
- persistNote
required:
@ -19038,15 +19034,8 @@ paths:
type: object
properties:
persistPinnedEventOnTimeline:
allOf:
- $ref: >-
#/components/schemas/Security_Solution_Timeline_API_PinnedEvent
- type: object
properties:
code:
type: number
message:
type: string
$ref: >-
#/components/schemas/Security_Solution_Timeline_API_PersistPinnedEventResponse
required:
- persistPinnedEventOnTimeline
required:
@ -40429,7 +40418,6 @@ components:
nullable: true
type: string
timelineId:
nullable: true
type: string
updated:
nullable: true
@ -40439,6 +40427,28 @@ components:
type: string
required:
- timelineId
Security_Solution_Timeline_API_BarePinnedEvent:
type: object
properties:
created:
nullable: true
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
required:
- eventId
- timelineId
Security_Solution_Timeline_API_ColumnHeaderResult:
type: object
properties:
@ -40613,6 +40623,18 @@ components:
type: string
script:
type: string
Security_Solution_Timeline_API_GetNotesResult:
type: object
properties:
notes:
items:
$ref: '#/components/schemas/Security_Solution_Timeline_API_Note'
type: array
totalCount:
type: number
required:
- totalCount
- notes
Security_Solution_Timeline_API_ImportTimelineResult:
type: object
properties:
@ -40673,34 +40695,38 @@ components:
type: string
version:
type: string
required:
- noteId
- version
Security_Solution_Timeline_API_PersistPinnedEventResponse:
oneOf:
- allOf:
- $ref: '#/components/schemas/Security_Solution_Timeline_API_PinnedEvent'
- $ref: >-
#/components/schemas/Security_Solution_Timeline_API_PinnedEventBaseResponseBody
- nullable: true
type: object
Security_Solution_Timeline_API_PinnedEvent:
allOf:
- $ref: '#/components/schemas/Security_Solution_Timeline_API_BarePinnedEvent'
- type: object
properties:
pinnedEventId:
type: string
version:
type: string
required:
- pinnedEventId
- version
Security_Solution_Timeline_API_PinnedEventBaseResponseBody:
type: object
properties:
created:
nullable: true
code:
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
pinnedEventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
version:
message:
type: string
required:
- eventId
- pinnedEventId
- timelineId
- version
- code
Security_Solution_Timeline_API_QueryMatchResult:
type: object
properties:
@ -40745,6 +40771,19 @@ components:
type: object
readable:
type: boolean
Security_Solution_Timeline_API_ResponseNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: '#/components/schemas/Security_Solution_Timeline_API_Note'
required:
- code
- message
- note
Security_Solution_Timeline_API_RowRendererId:
enum:
- alert

View file

@ -315,7 +315,10 @@ import type {
GetDraftTimelinesRequestQueryInput,
GetDraftTimelinesResponse,
} from './timeline/get_draft_timelines/get_draft_timelines_route.gen';
import type { GetNotesRequestQueryInput } from './timeline/get_notes/get_notes_route.gen';
import type {
GetNotesRequestQueryInput,
GetNotesResponse,
} from './timeline/get_notes/get_notes_route.gen';
import type {
GetTimelineRequestQueryInput,
GetTimelineResponse,
@ -1254,7 +1257,7 @@ finalize it.
async getNotes(props: GetNotesProps) {
this.log.info(`${new Date().toISOString()} Calling API GetNotes`);
return this.kbnClient
.request({
.request<GetNotesResponse>({
path: '/api/note',
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',

View file

@ -1,14 +0,0 @@
/*
* 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 * as rt from 'io-ts';
import { TimelineTypeLiteralRt } from '../model/api';
export const cleanDraftTimelineSchema = rt.type({
timelineType: TimelineTypeLiteralRt,
});

View file

@ -23,9 +23,11 @@ export const DeleteNoteRequestBody = z.union([
noteId: z.string(),
})
.nullable(),
z.object({
noteIds: z.array(z.string()).nullable(),
}),
z
.object({
noteIds: z.array(z.string()).nullable(),
})
.nullable(),
]);
export type DeleteNoteRequestBodyInput = z.input<typeof DeleteNoteRequestBody>;

View file

@ -34,6 +34,7 @@ paths:
type: string
- type: object
required: [noteIds]
nullable: true
properties:
noteIds:
type: array

View file

@ -1,14 +0,0 @@
/*
* 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 * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
export const deleteNoteSchema = runtimeTypes.partial({
noteId: unionWithNullType(runtimeTypes.string),
noteIds: unionWithNullType(runtimeTypes.array(runtimeTypes.string)),
});

View file

@ -1,16 +0,0 @@
/*
* 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 * as rt from 'io-ts';
const searchId = rt.partial({ searchIds: rt.array(rt.string) });
const baseDeleteTimelinesSchema = rt.type({
savedObjectIds: rt.array(rt.string),
});
export const deleteTimelinesSchema = rt.intersection([baseDeleteTimelinesSchema, searchId]);

View file

@ -1,17 +0,0 @@
/*
* 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 * as rt from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
export const exportTimelinesQuerySchema = rt.type({
file_name: rt.string,
});
export const exportTimelinesRequestBodySchema = rt.partial({
ids: unionWithNullType(rt.array(rt.string)),
});

View file

@ -16,17 +16,28 @@
import { z } from '@kbn/zod';
import { Note } from '../model/components.gen';
export type DocumentIds = z.infer<typeof DocumentIds>;
export const DocumentIds = z.union([z.array(z.string()), z.string()]);
export type GetNotesResult = z.infer<typeof GetNotesResult>;
export const GetNotesResult = z.object({
totalCount: z.number(),
notes: z.array(Note),
});
export type GetNotesRequestQuery = z.infer<typeof GetNotesRequestQuery>;
export const GetNotesRequestQuery = z.object({
documentIds: DocumentIds,
page: z.coerce.number().optional(),
perPage: z.coerce.number().optional(),
documentIds: DocumentIds.optional(),
page: z.string().nullable().optional(),
perPage: z.string().nullable().optional(),
search: z.string().nullable().optional(),
sortField: z.string().nullable().optional(),
sortOrder: z.string().nullable().optional(),
filter: z.string().nullable().optional(),
});
export type GetNotesRequestQueryInput = z.input<typeof GetNotesRequestQuery>;
export type GetNotesResponse = z.infer<typeof GetNotesResponse>;
export const GetNotesResponse = z.union([GetNotesResult, z.object({})]);

View file

@ -22,18 +22,17 @@ paths:
parameters:
- name: documentIds
in: query
required: true
schema:
$ref: '#/components/schemas/DocumentIds'
- name: page
in: query
schema:
type: number
type: string
nullable: true
- name: perPage
in: query
schema:
type: number
type: string
nullable: true
- name: search
in: query
@ -58,6 +57,12 @@ paths:
responses:
'200':
description: Indicates the requested notes were returned.
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/GetNotesResult'
- type: object
components:
schemas:
@ -67,3 +72,13 @@ components:
items:
type: string
- type: string
GetNotesResult:
type: object
required: [totalCount, notes]
properties:
totalCount:
type: number
notes:
type: array
items:
$ref: '../model/components.schema.yaml#/components/schemas/Note'

View file

@ -1,19 +0,0 @@
/*
* 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 * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
export const getNotesSchema = runtimeTypes.partial({
documentIds: runtimeTypes.union([runtimeTypes.array(runtimeTypes.string), runtimeTypes.string]),
page: unionWithNullType(runtimeTypes.string),
perPage: unionWithNullType(runtimeTypes.string),
search: unionWithNullType(runtimeTypes.string),
sortField: unionWithNullType(runtimeTypes.string),
sortOrder: unionWithNullType(runtimeTypes.string),
filter: unionWithNullType(runtimeTypes.string),
});

View file

@ -10,7 +10,7 @@ import * as rt from 'io-ts';
import { BareNoteSchema, SavedTimelineRuntimeType } from '../model/api';
import { unionWithNullType } from '../../../utility_types';
import { pinnedEventIds } from '../pinned_events/pinned_events_route';
const pinnedEventIds = unionWithNullType(rt.array(rt.string));
export const eventNotes = unionWithNullType(rt.array(BareNoteSchema));
export const globalNotes = unionWithNullType(rt.array(BareNoteSchema));

View file

@ -6,19 +6,14 @@
*/
export * from './model/api';
export * from './clean_draft_timelines/clean_draft_timelines_route';
export * from './routes';
export * from './get_draft_timelines/get_draft_timelines_route';
export * from './create_timelines/create_timelines_route';
export * from './delete_note/delete_note_route';
export * from './delete_timelines/delete_timelines_route';
export * from './export_timelines/export_timelines_route';
export * from './get_timeline/get_timeline_route';
export * from './get_timelines/get_timelines_route';
export * from './import_timelines/import_timelines_route';
export * from './patch_timelines/patch_timelines_schema';
export * from './persist_favorite/persist_favorite_route';
export * from './persist_note/persist_note_route';
export * from './pinned_events/pinned_events_route';
export * from './install_prepackaged_timelines/install_prepackaged_timelines';
export * from './copy_timeline/copy_timeline_route';
export * from './get_notes/get_notes_route';

View file

@ -12,12 +12,17 @@ import { stringEnum, unionWithNullType } from '../../../utility_types';
import type { Maybe } from '../../../search_strategy';
import { Direction } from '../../../search_strategy';
import type { PinnedEvent } from '../pinned_events/pinned_events_route';
import { PinnedEventRuntimeType } from '../pinned_events/pinned_events_route';
import { ErrorSchema } from './error_schema';
import type { DataProviderType } from './components.gen';
import {
BareNote,
BarePinnedEvent,
DataProviderTypeEnum,
FavoriteTimelineResponse,
type FavoriteTimelineResult,
type Note,
PinnedEvent,
RowRendererId,
RowRendererIdEnum,
SortFieldTimeline,
@ -31,8 +36,13 @@ import {
} from './components.gen';
export {
BareNote,
BarePinnedEvent,
DataProviderType,
DataProviderTypeEnum,
FavoriteTimelineResponse,
Note,
PinnedEvent,
RowRendererId,
RowRendererIdEnum,
SortFieldTimeline,
@ -45,6 +55,8 @@ export {
TimelineTypeEnum,
};
export type BarePinnedEventWithoutExternalRefs = Omit<BarePinnedEvent, 'timelineId'>;
/**
* Outcome is a property of the saved object resolve api
* will tell us info about the rule after 8.0 migrations
@ -83,24 +95,12 @@ export const BareNoteSchema = runtimeTypes.intersection([
}),
]);
export type BareNote = runtimeTypes.TypeOf<typeof BareNoteSchema>;
/**
* This type represents a note type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type BareNoteWithoutExternalRefs = Omit<BareNote, 'timelineId'>;
export const BareNoteWithoutExternalRefsSchema = runtimeTypes.partial({
timelineId: unionWithNullType(runtimeTypes.string),
eventId: unionWithNullType(runtimeTypes.string),
note: unionWithNullType(runtimeTypes.string),
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
});
export const NoteRuntimeType = runtimeTypes.intersection([
BareNoteSchema,
runtimeTypes.type({
@ -109,16 +109,6 @@ export const NoteRuntimeType = runtimeTypes.intersection([
}),
]);
export type Note = runtimeTypes.TypeOf<typeof NoteRuntimeType>;
export interface ResponseNote {
code?: Maybe<number>;
message?: Maybe<string>;
note: Note;
}
/*
* ColumnHeader Types
*/
@ -489,27 +479,6 @@ export const importTimelineResultSchema = runtimeTypes.exact(
export type ImportTimelineResultSchema = runtimeTypes.TypeOf<typeof importTimelineResultSchema>;
const favoriteTimelineResult = runtimeTypes.partial({
fullName: unionWithNullType(runtimeTypes.string),
userName: unionWithNullType(runtimeTypes.string),
favoriteDate: unionWithNullType(runtimeTypes.number),
});
export type FavoriteTimelineResult = runtimeTypes.TypeOf<typeof favoriteTimelineResult>;
export const responseFavoriteTimeline = runtimeTypes.partial({
savedObjectId: runtimeTypes.string,
version: runtimeTypes.string,
code: unionWithNullType(runtimeTypes.number),
message: unionWithNullType(runtimeTypes.string),
templateTimelineId: unionWithNullType(runtimeTypes.string),
templateTimelineVersion: unionWithNullType(runtimeTypes.number),
timelineType: unionWithNullType(TimelineTypeLiteralRt),
favorite: unionWithNullType(runtimeTypes.array(favoriteTimelineResult)),
});
export type ResponseFavoriteTimeline = runtimeTypes.TypeOf<typeof responseFavoriteTimeline>;
export const pageInfoTimeline = runtimeTypes.type({
pageIndex: runtimeTypes.number,
pageSize: runtimeTypes.number,

View file

@ -217,7 +217,7 @@ export type BareNote = z.infer<typeof BareNote>;
export const BareNote = z.object({
eventId: z.string().nullable().optional(),
note: z.string().nullable().optional(),
timelineId: z.string().nullable(),
timelineId: z.string(),
created: z.number().nullable().optional(),
createdBy: z.string().nullable().optional(),
updated: z.number().nullable().optional(),
@ -227,23 +227,29 @@ export const BareNote = z.object({
export type Note = z.infer<typeof Note>;
export const Note = BareNote.merge(
z.object({
noteId: z.string().optional(),
version: z.string().optional(),
noteId: z.string(),
version: z.string(),
})
);
export type PinnedEvent = z.infer<typeof PinnedEvent>;
export const PinnedEvent = z.object({
pinnedEventId: z.string(),
export type BarePinnedEvent = z.infer<typeof BarePinnedEvent>;
export const BarePinnedEvent = z.object({
eventId: z.string(),
timelineId: z.string(),
created: z.number().nullable().optional(),
createdBy: z.string().nullable().optional(),
updated: z.number().nullable().optional(),
updatedBy: z.string().nullable().optional(),
version: z.string(),
});
export type PinnedEvent = z.infer<typeof PinnedEvent>;
export const PinnedEvent = BarePinnedEvent.merge(
z.object({
pinnedEventId: z.string(),
version: z.string(),
})
);
export type TimelineResponse = z.infer<typeof TimelineResponse>;
export const TimelineResponse = SavedTimeline.merge(
z.object({
@ -269,6 +275,17 @@ export const FavoriteTimelineResponse = z.object({
favorite: z.array(FavoriteTimelineResult).optional(),
});
export type BareNoteWithoutExternalRefs = z.infer<typeof BareNoteWithoutExternalRefs>;
export const BareNoteWithoutExternalRefs = z.object({
eventId: z.string().nullable().optional(),
note: z.string().nullable().optional(),
timelineId: z.string().nullable().optional(),
created: z.number().nullable().optional(),
createdBy: z.string().nullable().optional(),
updated: z.number().nullable().optional(),
updatedBy: z.string().nullable().optional(),
});
export type GlobalNote = z.infer<typeof GlobalNote>;
export const GlobalNote = z.object({
noteId: z.string().optional(),

View file

@ -305,9 +305,8 @@ components:
nullable: true
queryMatch:
$ref: '#/components/schemas/QueryMatchResult'
BareNote:
BareNoteWithoutExternalRefs:
type: object
required: [timelineId]
properties:
eventId:
type: string
@ -330,10 +329,35 @@ components:
updatedBy:
type: string
nullable: true
BareNote:
type: object
required: [timelineId]
properties:
eventId:
type: string
nullable: true
note:
type: string
nullable: true
timelineId:
type: string
created:
type: number
nullable: true
createdBy:
type: string
nullable: true
updated:
type: number
nullable: true
updatedBy:
type: string
nullable: true
Note:
allOf:
- $ref: '#/components/schemas/BareNote'
- type: object
required: [noteId, version]
properties:
noteId:
type: string
@ -451,12 +475,10 @@ components:
serializedQuery:
type: string
nullable: true
PinnedEvent:
BarePinnedEvent:
type: object
required: [eventId, pinnedEventId, timelineId, version]
required: [eventId, timelineId]
properties:
pinnedEventId:
type: string
eventId:
type: string
timelineId:
@ -473,8 +495,16 @@ components:
updatedBy:
type: string
nullable: true
version:
type: string
PinnedEvent:
allOf:
- $ref: '#/components/schemas/BarePinnedEvent'
- type: object
required: [pinnedEventId, version]
properties:
pinnedEventId:
type: string
version:
type: string
Sort:
oneOf:
- $ref: '#/components/schemas/SortObject'

View file

@ -1,18 +0,0 @@
/*
* 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 * as rt from 'io-ts';
import { TimelineTypeLiteralRt } from '../model/api';
import { unionWithNullType } from '../../../utility_types';
export const persistFavoriteSchema = rt.type({
timelineId: unionWithNullType(rt.string),
templateTimelineId: unionWithNullType(rt.string),
templateTimelineVersion: unionWithNullType(rt.number),
timelineType: unionWithNullType(TimelineTypeLiteralRt),
});

View file

@ -18,6 +18,13 @@ import { z } from '@kbn/zod';
import { BareNote, Note } from '../model/components.gen';
export type ResponseNote = z.infer<typeof ResponseNote>;
export const ResponseNote = z.object({
code: z.number(),
message: z.string(),
note: Note,
});
export type PersistNoteRouteRequestBody = z.infer<typeof PersistNoteRouteRequestBody>;
export const PersistNoteRouteRequestBody = z.object({
note: BareNote,
@ -33,10 +40,6 @@ export type PersistNoteRouteRequestBodyInput = z.input<typeof PersistNoteRouteRe
export type PersistNoteRouteResponse = z.infer<typeof PersistNoteRouteResponse>;
export const PersistNoteRouteResponse = z.object({
data: z.object({
persistNote: z.object({
code: z.number(),
message: z.string(),
note: Note,
}),
persistNote: ResponseNote,
}),
});

View file

@ -65,12 +65,16 @@ paths:
required: [persistNote]
properties:
persistNote:
type: object
required: [code, message, note]
properties:
code:
type: number
message:
type: string
note:
$ref: '../model/components.schema.yaml#/components/schemas/Note'
$ref: '#/components/schemas/ResponseNote'
components:
schemas:
ResponseNote:
type: object
required: [code, message, note]
properties:
code:
type: number
message:
type: string
note:
$ref: '../model/components.schema.yaml#/components/schemas/Note'

View file

@ -1,40 +0,0 @@
/*
* 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 * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
import { BareNoteSchema, BareNoteWithoutExternalRefsSchema } from '../model/api';
export const persistNoteWithRefSchema = runtimeTypes.intersection([
runtimeTypes.type({
note: BareNoteSchema,
}),
runtimeTypes.partial({
overrideOwner: unionWithNullType(runtimeTypes.boolean),
noteId: unionWithNullType(runtimeTypes.string),
version: unionWithNullType(runtimeTypes.string),
}),
]);
export const persistNoteWithoutRefSchema = runtimeTypes.intersection([
runtimeTypes.type({
note: BareNoteWithoutExternalRefsSchema,
}),
runtimeTypes.partial({
overrideOwner: unionWithNullType(runtimeTypes.boolean),
noteId: unionWithNullType(runtimeTypes.string),
version: unionWithNullType(runtimeTypes.string),
eventIngested: unionWithNullType(runtimeTypes.string),
eventTimestamp: unionWithNullType(runtimeTypes.string),
eventDataView: unionWithNullType(runtimeTypes.string),
}),
]);
export const persistNoteSchema = runtimeTypes.union([
persistNoteWithRefSchema,
persistNoteWithoutRefSchema,
]);

View file

@ -18,6 +18,18 @@ import { z } from '@kbn/zod';
import { PinnedEvent } from '../model/components.gen';
export type PinnedEventBaseResponseBody = z.infer<typeof PinnedEventBaseResponseBody>;
export const PinnedEventBaseResponseBody = z.object({
code: z.number(),
message: z.string().optional(),
});
export type PersistPinnedEventResponse = z.infer<typeof PersistPinnedEventResponse>;
export const PersistPinnedEventResponse = z.union([
PinnedEvent.merge(PinnedEventBaseResponseBody),
z.object({}).nullable(),
]);
export type PersistPinnedEventRouteRequestBody = z.infer<typeof PersistPinnedEventRouteRequestBody>;
export const PersistPinnedEventRouteRequestBody = z.object({
eventId: z.string(),
@ -31,11 +43,6 @@ export type PersistPinnedEventRouteRequestBodyInput = z.input<
export type PersistPinnedEventRouteResponse = z.infer<typeof PersistPinnedEventRouteResponse>;
export const PersistPinnedEventRouteResponse = z.object({
data: z.object({
persistPinnedEventOnTimeline: PinnedEvent.merge(
z.object({
code: z.number().optional(),
message: z.string().optional(),
})
),
persistPinnedEventOnTimeline: PersistPinnedEventResponse,
}),
});

View file

@ -52,11 +52,22 @@ paths:
required: [persistPinnedEventOnTimeline]
properties:
persistPinnedEventOnTimeline:
allOf:
- $ref: '../model/components.schema.yaml#/components/schemas/PinnedEvent'
- type: object
properties:
code:
type: number
message:
type: string
$ref: '#/components/schemas/PersistPinnedEventResponse'
components:
schemas:
PersistPinnedEventResponse:
oneOf:
- allOf:
- $ref: '../model/components.schema.yaml#/components/schemas/PinnedEvent'
- $ref: '#/components/schemas/PinnedEventBaseResponseBody'
- type: object
nullable: true
PinnedEventBaseResponseBody:
type: object
required: [code]
properties:
code:
type: number
message:
type: string

View file

@ -5,26 +5,14 @@
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
export const pinnedEventIds = unionWithNullType(runtimeTypes.array(runtimeTypes.string));
export const persistPinnedEventSchema = runtimeTypes.intersection([
runtimeTypes.type({
eventId: runtimeTypes.string,
timelineId: runtimeTypes.string,
}),
runtimeTypes.partial({
pinnedEventId: unionWithNullType(runtimeTypes.string),
}),
]);
/*
* Note Types
* Pinned Event Types
* TODO: remove these when the timeline types are moved to zod
*/
export const BarePinnedEventType = runtimeTypes.intersection([
const BarePinnedEventType = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: runtimeTypes.string,
eventId: runtimeTypes.string,
@ -37,14 +25,6 @@ export const BarePinnedEventType = runtimeTypes.intersection([
}),
]);
export interface BarePinnedEvent extends runtimeTypes.TypeOf<typeof BarePinnedEventType> {}
/**
* This type represents a pinned event type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type BarePinnedEventWithoutExternalRefs = Omit<BarePinnedEvent, 'timelineId'>;
export const PinnedEventRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
pinnedEventId: runtimeTypes.string,
@ -52,7 +32,3 @@ export const PinnedEventRuntimeType = runtimeTypes.intersection([
}),
BarePinnedEventType,
]);
export interface PinnedEvent extends runtimeTypes.TypeOf<typeof PinnedEventRuntimeType> {}
export type PinnedEventResponse = PinnedEvent & { code: number; message?: string };

View file

@ -0,0 +1,42 @@
/*
* 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.
*/
export {
DeleteTimelinesRequestBody,
DeleteTimelinesResponse,
} from './delete_timelines/delete_timelines_route.gen';
export {
PersistNoteRouteRequestBody,
PersistNoteRouteResponse,
ResponseNote,
} from './persist_note/persist_note_route.gen';
export { DeleteNoteRequestBody, DeleteNoteResponse } from './delete_note/delete_note_route.gen';
export { CleanDraftTimelinesRequestBody } from './clean_draft_timelines/clean_draft_timelines_route.gen';
export {
ExportTimelinesRequestQuery,
ExportTimelinesRequestBody,
} from './export_timelines/export_timelines_route.gen';
export {
PersistFavoriteRouteResponse,
PersistFavoriteRouteRequestBody,
} from './persist_favorite/persist_favorite_route.gen';
export {
PersistPinnedEventRouteRequestBody,
PersistPinnedEventResponse,
PersistPinnedEventRouteResponse,
} from './pinned_events/pinned_events_route.gen';
export {
GetNotesRequestQuery,
GetNotesResponse,
GetNotesResult,
} from './get_notes/get_notes_route.gen';

View file

@ -29,7 +29,8 @@ paths:
type: string
required:
- noteId
- type: object
- nullable: true
type: object
properties:
noteIds:
items:
@ -60,19 +61,18 @@ paths:
parameters:
- in: query
name: documentIds
required: true
schema:
$ref: '#/components/schemas/DocumentIds'
- in: query
name: page
schema:
nullable: true
type: number
type: string
- in: query
name: perPage
schema:
nullable: true
type: number
type: string
- in: query
name: search
schema:
@ -95,6 +95,12 @@ paths:
type: string
responses:
'200':
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/GetNotesResult'
- type: object
description: Indicates the requested notes were returned.
summary: Get notes
tags:
@ -144,18 +150,7 @@ paths:
type: object
properties:
persistNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: '#/components/schemas/Note'
required:
- code
- message
- note
$ref: '#/components/schemas/ResponseNote'
required:
- persistNote
required:
@ -198,14 +193,7 @@ paths:
type: object
properties:
persistPinnedEventOnTimeline:
allOf:
- $ref: '#/components/schemas/PinnedEvent'
- type: object
properties:
code:
type: number
message:
type: string
$ref: '#/components/schemas/PersistPinnedEventResponse'
required:
- persistPinnedEventOnTimeline
required:
@ -1002,7 +990,6 @@ components:
nullable: true
type: string
timelineId:
nullable: true
type: string
updated:
nullable: true
@ -1012,6 +999,28 @@ components:
type: string
required:
- timelineId
BarePinnedEvent:
type: object
properties:
created:
nullable: true
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
required:
- eventId
- timelineId
ColumnHeaderResult:
type: object
properties:
@ -1184,6 +1193,18 @@ components:
type: string
script:
type: string
GetNotesResult:
type: object
properties:
notes:
items:
$ref: '#/components/schemas/Note'
type: array
totalCount:
type: number
required:
- totalCount
- notes
ImportTimelineResult:
type: object
properties:
@ -1244,34 +1265,37 @@ components:
type: string
version:
type: string
required:
- noteId
- version
PersistPinnedEventResponse:
oneOf:
- allOf:
- $ref: '#/components/schemas/PinnedEvent'
- $ref: '#/components/schemas/PinnedEventBaseResponseBody'
- nullable: true
type: object
PinnedEvent:
allOf:
- $ref: '#/components/schemas/BarePinnedEvent'
- type: object
properties:
pinnedEventId:
type: string
version:
type: string
required:
- pinnedEventId
- version
PinnedEventBaseResponseBody:
type: object
properties:
created:
nullable: true
code:
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
pinnedEventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
version:
message:
type: string
required:
- eventId
- pinnedEventId
- timelineId
- version
- code
QueryMatchResult:
type: object
properties:
@ -1316,6 +1340,19 @@ components:
type: object
readable:
type: boolean
ResponseNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: '#/components/schemas/Note'
required:
- code
- message
- note
RowRendererId:
enum:
- alert

View file

@ -29,7 +29,8 @@ paths:
type: string
required:
- noteId
- type: object
- nullable: true
type: object
properties:
noteIds:
items:
@ -60,19 +61,18 @@ paths:
parameters:
- in: query
name: documentIds
required: true
schema:
$ref: '#/components/schemas/DocumentIds'
- in: query
name: page
schema:
nullable: true
type: number
type: string
- in: query
name: perPage
schema:
nullable: true
type: number
type: string
- in: query
name: search
schema:
@ -95,6 +95,12 @@ paths:
type: string
responses:
'200':
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/GetNotesResult'
- type: object
description: Indicates the requested notes were returned.
summary: Get notes
tags:
@ -144,18 +150,7 @@ paths:
type: object
properties:
persistNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: '#/components/schemas/Note'
required:
- code
- message
- note
$ref: '#/components/schemas/ResponseNote'
required:
- persistNote
required:
@ -198,14 +193,7 @@ paths:
type: object
properties:
persistPinnedEventOnTimeline:
allOf:
- $ref: '#/components/schemas/PinnedEvent'
- type: object
properties:
code:
type: number
message:
type: string
$ref: '#/components/schemas/PersistPinnedEventResponse'
required:
- persistPinnedEventOnTimeline
required:
@ -1002,7 +990,6 @@ components:
nullable: true
type: string
timelineId:
nullable: true
type: string
updated:
nullable: true
@ -1012,6 +999,28 @@ components:
type: string
required:
- timelineId
BarePinnedEvent:
type: object
properties:
created:
nullable: true
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
required:
- eventId
- timelineId
ColumnHeaderResult:
type: object
properties:
@ -1184,6 +1193,18 @@ components:
type: string
script:
type: string
GetNotesResult:
type: object
properties:
notes:
items:
$ref: '#/components/schemas/Note'
type: array
totalCount:
type: number
required:
- totalCount
- notes
ImportTimelineResult:
type: object
properties:
@ -1244,34 +1265,37 @@ components:
type: string
version:
type: string
required:
- noteId
- version
PersistPinnedEventResponse:
oneOf:
- allOf:
- $ref: '#/components/schemas/PinnedEvent'
- $ref: '#/components/schemas/PinnedEventBaseResponseBody'
- nullable: true
type: object
PinnedEvent:
allOf:
- $ref: '#/components/schemas/BarePinnedEvent'
- type: object
properties:
pinnedEventId:
type: string
version:
type: string
required:
- pinnedEventId
- version
PinnedEventBaseResponseBody:
type: object
properties:
created:
nullable: true
code:
type: number
createdBy:
nullable: true
type: string
eventId:
type: string
pinnedEventId:
type: string
timelineId:
type: string
updated:
nullable: true
type: number
updatedBy:
nullable: true
type: string
version:
message:
type: string
required:
- eventId
- pinnedEventId
- timelineId
- version
- code
QueryMatchResult:
type: object
properties:
@ -1316,6 +1340,19 @@ components:
type: object
readable:
type: boolean
ResponseNote:
type: object
properties:
code:
type: number
message:
type: string
note:
$ref: '#/components/schemas/Note'
required:
- code
- message
- note
RowRendererId:
enum:
- alert

View file

@ -17,7 +17,6 @@ import type {
TimelineResponse,
TimelineErrorResponse,
ImportTimelineResultSchema,
ResponseFavoriteTimeline,
AllTimelinesResponse,
SingleTimelineResponse,
SingleTimelineResolveResponse,
@ -29,7 +28,7 @@ import {
TimelineErrorResponseType,
importTimelineResultSchema,
allTimelinesResponse,
responseFavoriteTimeline,
PersistFavoriteRouteResponse,
SingleTimelineResponseType,
type TimelineType,
TimelineTypeEnum,
@ -105,11 +104,8 @@ const decodePrepackedTimelineResponse = (respTimeline?: ImportTimelineResultSche
fold(throwErrors(createToasterPlainError), identity)
);
const decodeResponseFavoriteTimeline = (respTimeline?: ResponseFavoriteTimeline) =>
pipe(
responseFavoriteTimeline.decode(respTimeline),
fold(throwErrors(createToasterPlainError), identity)
);
const decodeResponseFavoriteTimeline = (respTimeline?: PersistFavoriteRouteResponse) =>
PersistFavoriteRouteResponse.parse(respTimeline);
const postTimeline = async ({
timeline,
@ -469,7 +465,7 @@ export const persistFavorite = async ({
return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`));
}
const response = await KibanaServices.get().http.patch<ResponseFavoriteTimeline>(
const response = await KibanaServices.get().http.patch<PersistFavoriteRouteResponse>(
TIMELINE_FAVORITE_URL,
{
method: 'PATCH',

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { PINNED_EVENT_URL } from '../../../../common/constants';
import type { PinnedEvent } from '../../../../common/api/timeline';
import type { PersistPinnedEventRouteResponse } from '../../../../common/api/timeline';
import { KibanaServices } from '../../../common/lib/kibana';
export const persistPinnedEvent = async ({
@ -23,10 +23,13 @@ export const persistPinnedEvent = async ({
} catch (err) {
return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`));
}
const response = await KibanaServices.get().http.patch<PinnedEvent | null>(PINNED_EVENT_URL, {
method: 'PATCH',
body: requestBody,
version: '2023-10-31',
});
const response = await KibanaServices.get().http.patch<PersistPinnedEventRouteResponse>(
PINNED_EVENT_URL,
{
method: 'PATCH',
body: requestBody,
version: '2023-10-31',
}
);
return response;
};

View file

@ -17,7 +17,7 @@ import {
startTimelineSaving,
showCallOutUnauthorizedMsg,
} from '../actions';
import type { ResponseFavoriteTimeline } from '../../../../common/api/timeline';
import type { FavoriteTimelineResponse } from '../../../../common/api/timeline';
import { TimelineTypeEnum } from '../../../../common/api/timeline';
import { persistFavorite } from '../../containers/api';
import { selectTimelineById } from '../selectors';
@ -49,7 +49,7 @@ export const favoriteTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, S
timelineType: timeline.timelineType ?? TimelineTypeEnum.default,
});
const response: ResponseFavoriteTimeline = get('data.persistFavorite', result);
const response: FavoriteTimelineResponse = get('data.persistFavorite', result);
if (response.code === 403) {
store.dispatch(showCallOutUnauthorizedMsg());

View file

@ -5,14 +5,13 @@
* 2.0.
*/
import { get, omit } from 'lodash/fp';
import { omit } from 'lodash/fp';
import type { Action, Middleware } from 'redux';
import type { CoreStart } from '@kbn/core/public';
import type { State } from '../../../common/store/types';
import { selectTimelineById } from '../selectors';
import * as i18n from '../../pages/translations';
import type { PinnedEventResponse } from '../../../../common/api/timeline';
import {
pinEvent,
endTimelineSaving,
@ -65,17 +64,17 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa
timelineId: timeline.savedObjectId,
});
const response: PinnedEventResponse = get('data.persistPinnedEventOnTimeline', result);
if (response && response.code === 403) {
const response = result.data.persistPinnedEventOnTimeline;
if (response && 'code' in response && response.code === 403) {
store.dispatch(showCallOutUnauthorizedMsg());
}
refreshTimelines(store.getState());
const currentTimeline = selectTimelineById(store.getState(), action.payload.id);
// The response is null in case we unpinned an event.
// The response is null or empty in case we unpinned an event.
// In that case we want to remove the locally pinned event.
if (!response) {
if (!response || !('code' in response)) {
return store.dispatch(
updateTimeline({
id: action.payload.id,

View file

@ -7,13 +7,13 @@
import { v4 as uuidv4 } from 'uuid';
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SecuritySolutionPluginRouter } from '../../../../../types';
import type { ConfigType } from '../../../../..';
import { buildSiemResponse } from '../../../../detection_engine/routes/utils';
import { TIMELINE_DRAFT_URL } from '../../../../../../common/constants';
import { buildFrameworkRequest } from '../../../utils/common';
import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation';
import {
getDraftTimeline,
resetTimeline,
@ -21,7 +21,10 @@ import {
persistTimeline,
} from '../../../saved_object/timelines';
import { draftTimelineDefaults } from '../../../utils/default_timeline';
import { cleanDraftTimelineSchema, TimelineTypeEnum } from '../../../../../../common/api/timeline';
import {
CleanDraftTimelinesRequestBody,
TimelineTypeEnum,
} from '../../../../../../common/api/timeline';
export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => {
router.versioned
@ -35,7 +38,7 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _
.addVersion(
{
validate: {
request: { body: buildRouteValidationWithExcess(cleanDraftTimelineSchema) },
request: { body: buildRouteValidationWithZod(CleanDraftTimelinesRequestBody) },
},
version: '2023-10-31',
},

View file

@ -6,17 +6,17 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { NOTE_URL } from '../../../../../common/constants';
import { buildRouteValidationWithExcess } from '../../../../utils/build_validation/route_validation';
import type { ConfigType } from '../../../..';
import { buildSiemResponse } from '../../../detection_engine/routes/utils';
import { buildFrameworkRequest } from '../../utils/common';
import { deleteNoteSchema } from '../../../../../common/api/timeline';
import { DeleteNoteRequestBody, type DeleteNoteResponse } from '../../../../../common/api/timeline';
import { deleteNote } from '../../saved_object/notes';
export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => {
@ -31,7 +31,7 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: Co
.addVersion(
{
validate: {
request: { body: buildRouteValidationWithExcess(deleteNoteSchema) },
request: { body: buildRouteValidationWithZod(DeleteNoteRequestBody) },
},
version: '2023-10-31',
},
@ -40,27 +40,25 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: Co
try {
const frameworkRequest = await buildFrameworkRequest(context, request);
const noteId = request.body?.noteId ?? '';
const noteIds = request.body?.noteIds ?? null;
if (noteIds != null) {
await deleteNote({
request: frameworkRequest,
noteIds,
});
return response.ok({
body: { data: {} },
});
} else {
await deleteNote({
request: frameworkRequest,
noteIds: [noteId],
});
return response.ok({
body: { data: {} },
});
if (!request.body) {
throw new Error('Missing request body');
}
let noteIds: string[] = [];
if ('noteId' in request.body) {
noteIds = [request.body.noteId];
} else if ('noteIds' in request.body && Array.isArray(request.body.noteIds)) {
noteIds = request.body.noteIds;
}
await deleteNote({
request: frameworkRequest,
noteIds,
});
const body: DeleteNoteResponse = { data: {} };
return response.ok({
body,
});
} catch (err) {
const error = transformError(err);
return siemResponse.error({

View file

@ -13,10 +13,10 @@ import {
requestMock,
} from '../../../detection_engine/routes/__mocks__';
import { NOTE_URL } from '../../../../../common/constants';
import type { getNotesSchema } from '../../../../../common/api/timeline';
import type { GetNotesRequestQuery } from '../../../../../common/api/timeline';
import { mockGetCurrentUser } from '../../__mocks__/import_timelines';
const getAllNotesRequest = (query?: typeof getNotesSchema) =>
const getAllNotesRequest = (query?: GetNotesRequestQuery) =>
requestMock.create({
method: 'get',
path: NOTE_URL,

View file

@ -7,6 +7,7 @@
import { transformError } from '@kbn/securitysolution-es-utils';
import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { NOTE_URL } from '../../../../../common/constants';
@ -14,10 +15,9 @@ import type { ConfigType } from '../../../..';
import { buildSiemResponse } from '../../../detection_engine/routes/utils';
import { buildFrameworkRequest } from '../../utils/common';
import { getNotesSchema } from '../../../../../common/api/timeline';
import { buildRouteValidationWithExcess } from '../../../../utils/build_validation/route_validation';
import { getAllSavedNote, MAX_UNASSOCIATED_NOTES } from '../../saved_object/notes';
import { noteSavedObjectType } from '../../saved_object_mappings/notes';
import { GetNotesRequestQuery, type GetNotesResponse } from '../../../../../common/api/timeline';
export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => {
router.versioned
@ -31,7 +31,7 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp
.addVersion(
{
validate: {
request: { query: buildRouteValidationWithExcess(getNotesSchema) },
request: { query: buildRouteValidationWithZod(GetNotesRequestQuery) },
},
version: '2023-10-31',
},
@ -50,8 +50,8 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp
perPage: MAX_UNASSOCIATED_NOTES,
};
const res = await getAllSavedNote(frameworkRequest, options);
return response.ok({ body: res ?? {} });
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
} else {
const options = {
type: noteSavedObjectType,
@ -60,8 +60,8 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp
perPage: MAX_UNASSOCIATED_NOTES,
};
const res = await getAllSavedNote(frameworkRequest, options);
return response.ok({ body: res ?? {} });
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
}
} else {
const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10;
@ -80,7 +80,8 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp
filter,
};
const res = await getAllSavedNote(frameworkRequest, options);
return response.ok({ body: res ?? {} });
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
}
} catch (err) {
const error = transformError(err);

View file

@ -6,17 +6,20 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { NOTE_URL } from '../../../../../common/constants';
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
import type { ConfigType } from '../../../..';
import { buildSiemResponse } from '../../../detection_engine/routes/utils';
import { buildFrameworkRequest } from '../../utils/common';
import { persistNoteWithoutRefSchema } from '../../../../../common/api/timeline';
import {
PersistNoteRouteRequestBody,
type PersistNoteRouteResponse,
} from '../../../../../common/api/timeline';
import { persistNote } from '../../saved_object/notes';
export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => {
@ -31,7 +34,7 @@ export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: Config
.addVersion(
{
validate: {
request: { body: buildRouteValidation(persistNoteWithoutRefSchema) },
request: { body: buildRouteValidationWithZod(PersistNoteRouteRequestBody) },
},
version: '2023-10-31',
},
@ -49,9 +52,10 @@ export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: Config
note,
overrideOwner: true,
});
const body: PersistNoteRouteResponse = { data: { persistNote: res } };
return response.ok({
body: { data: { persistNote: res } },
body,
});
} catch (err) {
const error = transformError(err);

View file

@ -6,17 +6,21 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { PINNED_EVENT_URL } from '../../../../../common/constants';
import { buildRouteValidationWithExcess } from '../../../../utils/build_validation/route_validation';
import type { ConfigType } from '../../../..';
import { buildSiemResponse } from '../../../detection_engine/routes/utils';
import { buildFrameworkRequest } from '../../utils/common';
import { persistPinnedEventSchema } from '../../../../../common/api/timeline';
import {
type PersistPinnedEventRouteResponse,
PersistPinnedEventRouteRequestBody,
} from '../../../../../common/api/timeline';
import { persistPinnedEventOnTimeline } from '../../saved_object/pinned_events';
export const persistPinnedEventRoute = (
@ -34,7 +38,7 @@ export const persistPinnedEventRoute = (
.addVersion(
{
validate: {
request: { body: buildRouteValidationWithExcess(persistPinnedEventSchema) },
request: { body: buildRouteValidationWithZod(PersistPinnedEventRouteRequestBody) },
},
version: '2023-10-31',
},
@ -54,8 +58,12 @@ export const persistPinnedEventRoute = (
timelineId
);
const body: PersistPinnedEventRouteResponse = {
data: { persistPinnedEventOnTimeline: res },
};
return response.ok({
body: { data: { persistPinnedEventOnTimeline: res } },
body,
});
} catch (err) {
const error = transformError(err);

View file

@ -6,9 +6,12 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { ConfigType } from '../../../../..';
import { deleteTimelinesSchema } from '../../../../../../common/api/timeline';
import {
DeleteTimelinesRequestBody,
type DeleteTimelinesResponse,
} from '../../../../../../common/api/timeline';
import type { SecuritySolutionPluginRouter } from '../../../../../types';
import { TIMELINE_URL } from '../../../../../../common/constants';
import { buildSiemResponse } from '../../../../detection_engine/routes/utils';
@ -29,7 +32,7 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter, confi
{
version: '2023-10-31',
validate: {
request: { body: buildRouteValidationWithExcess(deleteTimelinesSchema) },
request: { body: buildRouteValidationWithZod(DeleteTimelinesRequestBody) },
},
},
async (context, request, response) => {
@ -40,7 +43,8 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter, confi
const { savedObjectIds, searchIds } = request.body;
await deleteTimeline(frameworkRequest, savedObjectIds, searchIds);
return response.ok({ body: { data: { deleteTimeline: true } } });
const body: DeleteTimelinesResponse = { data: { deleteTimeline: true } };
return response.ok({ body });
} catch (err) {
const error = transformError(err);
return siemResponse.error({

View file

@ -93,9 +93,7 @@ describe('export timelines', () => {
});
const result = server.validate(request);
expect(result.badRequest.mock.calls[0][0]).toEqual(
'Invalid value {"id":"someId"}, excess properties: ["id"]'
);
expect(result.badRequest.mock.calls[0][0]).toEqual('file_name: Required');
});
test('return validation error for request params', async () => {
@ -107,9 +105,7 @@ describe('export timelines', () => {
});
const result = server.validate(request);
expect(result.badRequest.mock.calls[0][0]).toEqual(
'Invalid value "someId" supplied to "ids"'
);
expect(result.badRequest.mock.calls[0][0]).toEqual('ids: Expected array, received string');
});
});
});

View file

@ -6,16 +6,16 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import { TIMELINE_EXPORT_URL } from '../../../../../../common/constants';
import type { SecuritySolutionPluginRouter } from '../../../../../types';
import type { ConfigType } from '../../../../../config';
import { buildSiemResponse } from '../../../../detection_engine/routes/utils';
import {
exportTimelinesQuerySchema,
exportTimelinesRequestBodySchema,
ExportTimelinesRequestQuery,
ExportTimelinesRequestBody,
} from '../../../../../../common/api/timeline';
import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation';
import { buildFrameworkRequest } from '../../../utils/common';
import { getExportTimelineByObjectIds } from './helpers';
@ -35,8 +35,8 @@ export const exportTimelinesRoute = (router: SecuritySolutionPluginRouter, confi
{
validate: {
request: {
query: buildRouteValidationWithExcess(exportTimelinesQuerySchema),
body: buildRouteValidationWithExcess(exportTimelinesRequestBodySchema),
query: buildRouteValidationWithZod(ExportTimelinesRequestQuery),
body: buildRouteValidationWithZod(ExportTimelinesRequestBody),
},
},
version: '2023-10-31',

View file

@ -6,18 +6,22 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SecuritySolutionPluginRouter } from '../../../../../types';
import { TIMELINE_FAVORITE_URL } from '../../../../../../common/constants';
import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation';
import type { ConfigType } from '../../../../..';
import { buildSiemResponse } from '../../../../detection_engine/routes/utils';
import { buildFrameworkRequest } from '../../../utils/common';
import { persistFavorite } from '../../../saved_object/timelines';
import { TimelineTypeEnum, persistFavoriteSchema } from '../../../../../../common/api/timeline';
import {
type PersistFavoriteRouteResponse,
PersistFavoriteRouteRequestBody,
TimelineTypeEnum,
} from '../../../../../../common/api/timeline';
export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => {
router.versioned
@ -32,7 +36,7 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: Co
{
version: '2023-10-31',
validate: {
request: { body: buildRouteValidationWithExcess(persistFavoriteSchema) },
request: { body: buildRouteValidationWithZod(PersistFavoriteRouteRequestBody) },
},
},
async (context, request, response) => {
@ -51,12 +55,14 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: Co
timelineType || TimelineTypeEnum.default
);
return response.ok({
body: {
data: {
persistFavorite: timeline,
},
const body: PersistFavoriteRouteResponse = {
data: {
persistFavorite: timeline,
},
};
return response.ok({
body,
});
} catch (err) {
const error = transformError(err);

View file

@ -22,6 +22,7 @@ import type {
BareNote,
BareNoteWithoutExternalRefs,
ResponseNote,
GetNotesResult,
} from '../../../../../common/api/timeline';
import { SavedObjectNoteRuntimeType } from '../../../../../common/types/timeline/note/saved_object';
import type { SavedObjectNoteWithoutExternalRefs } from '../../../../../common/types/timeline/note/saved_object';
@ -133,7 +134,7 @@ export const createNote = async ({
noteId: string | null;
note: BareNote | BareNoteWithoutExternalRefs;
overrideOwner?: boolean;
}) => {
}): Promise<ResponseNote> => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const userInfo = request.user;
@ -201,7 +202,7 @@ export const updateNote = async ({
noteId: string;
note: BareNote | BareNoteWithoutExternalRefs;
overrideOwner?: boolean;
}) => {
}): Promise<ResponseNote> => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const userInfo = request.user;
@ -261,7 +262,7 @@ const getSavedNote = async (request: FrameworkRequest, NoteId: string) => {
export const getAllSavedNote = async (
request: FrameworkRequest,
options: SavedObjectsFindOptions
) => {
): Promise<GetNotesResult> => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const savedObjects = await savedObjectsClient.find<SavedObjectNoteWithoutExternalRefs>(options);

View file

@ -17,8 +17,7 @@ import { UNAUTHENTICATED_USER } from '../../../../../common/constants';
import type {
BarePinnedEvent,
PinnedEvent,
PinnedEventResponse,
BarePinnedEventWithoutExternalRefs,
PersistPinnedEventResponse,
} from '../../../../../common/api/timeline';
import { SavedObjectPinnedEventRuntimeType } from '../../../../../common/types/timeline/pinned_event/saved_object';
import type { SavedObjectPinnedEventWithoutExternalRefs } from '../../../../../common/types/timeline/pinned_event/saved_object';
@ -77,7 +76,7 @@ export const persistPinnedEventOnTimeline = async (
pinnedEventId: string | null, // pinned event saved object id
eventId: string,
timelineId: string
): Promise<PinnedEventResponse | null> => {
): Promise<PersistPinnedEventResponse> => {
try {
if (pinnedEventId != null) {
// Delete Pinned Event on Timeline
@ -111,9 +110,6 @@ export const persistPinnedEventOnTimeline = async (
code: 403,
message: err.message,
pinnedEventId: eventId,
timelineId: '',
version: '',
eventId: '',
}
: null;
}
@ -140,7 +136,7 @@ const createPinnedEvent = async ({
request: FrameworkRequest;
eventId: string;
timelineId: string;
}): Promise<PinnedEventResponse> => {
}): Promise<PersistPinnedEventResponse> => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const savedPinnedEvent: BarePinnedEvent = {
@ -151,7 +147,7 @@ const createPinnedEvent = async ({
const pinnedEventWithCreator = pickSavedPinnedEvent(null, savedPinnedEvent, request.user);
const { transformedFields: migratedAttributes, references } =
pinnedEventFieldsMigrator.extractFieldsToReferences<BarePinnedEventWithoutExternalRefs>({
pinnedEventFieldsMigrator.extractFieldsToReferences<Omit<BarePinnedEvent, 'timelineId'>>({
data: pinnedEventWithCreator,
});

View file

@ -23,7 +23,7 @@ import type {
ExportTimelineNotFoundError,
PageInfoTimeline,
ResponseTimelines,
ResponseFavoriteTimeline,
FavoriteTimelineResponse,
ResponseTimeline,
SortTimeline,
TimelineResult,
@ -312,7 +312,7 @@ export const persistFavorite = async (
templateTimelineId: string | null,
templateTimelineVersion: number | null,
timelineType: TimelineType
): Promise<ResponseFavoriteTimeline> => {
): Promise<FavoriteTimelineResponse> => {
const userName = request.user?.username ?? UNAUTHENTICATED_USER;
const fullName = request.user?.full_name ?? '';
try {

View file

@ -39,7 +39,7 @@ export default function ({ getService }: FtrProviderContext) {
});
describe('Unpinned an event', () => {
it('return null', async () => {
it('returns null', async () => {
const response = await supertest
.patch('/api/pinned_event')
.set('elastic-api-version', '2023-10-31')