From 00789609ad663efffd7b3997ca773fe3ea5511e2 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Mon, 30 Sep 2024 20:55:23 +0200 Subject: [PATCH] [Threat Hunting Investigations] Migrate all timeline routes to OpenAPI types (#190238) ## Summary fixes: https://github.com/elastic/security-team/issues/10235 fixes: https://github.com/elastic/security-team/issues/10237 This is the final PR for migrating over all timeline-related schemas and types to the new generated zod schemas from our OpenAPI specs. (see https://github.com/elastic/security-team/issues/10110) On top of moving to the new schemas/types, this PR also cleans up usage of now outdated types. I'm aware of the size of this PR but rest assured, the changes are easy to review and for most teams, only a handful of files need to be reviewed: ```markdown ### elastic/security-defend-workflows * x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts ### elastic/security-detection-rule-management * x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts * x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts ### elastic/security-detections-response * x-pack/test/security_solution_cypress/cypress/objects/timeline.ts ### elastic/security-engineering-productivity * x-pack/test/security_solution_cypress/cypress/objects/timeline.ts * x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts ``` ### 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 --- .github/CODEOWNERS | 1 + .../output/kibana.serverless.staging.yaml | 425 +++++++------ oas_docs/output/kibana.staging.yaml | 425 +++++++------ .../common/api/quickstart_client.gen.ts | 24 + .../clean_draft_timelines_route.gen.ts | 10 +- .../clean_draft_timelines_route.schema.yaml | 21 +- .../copy_timeline/copy_timeline_route.gen.ts | 29 + .../copy_timeline_route.schema.yaml | 34 + .../copy_timeline/copy_timeline_route.ts | 15 - .../create_timelines_route.gen.ts | 14 +- .../create_timelines_route.schema.yaml | 27 +- .../create_timelines_route.ts | 36 -- .../delete_note/delete_note_route.schema.yaml | 7 - .../delete_timelines_route.schema.yaml | 7 - .../export_timelines_route.schema.yaml | 7 - .../get_draft_timelines_route.gen.ts | 10 +- .../get_draft_timelines_route.schema.yaml | 21 +- .../get_draft_timelines_route.ts | 14 - .../get_notes/get_notes_route.schema.yaml | 7 - .../get_timeline/get_timeline_route.gen.ts | 11 +- .../get_timeline_route.schema.yaml | 27 +- .../get_timeline/get_timeline_route.ts | 15 - .../get_timelines/get_timelines_route.gen.ts | 16 +- .../get_timelines_route.schema.yaml | 57 +- .../get_timelines/get_timelines_route.ts | 28 - .../import_timelines_route.gen.ts | 17 +- .../import_timelines_route.schema.yaml | 39 +- .../import_timelines_route.ts | 60 -- .../common/api/timeline/index.ts | 10 - .../install_prepackaged_timelines.ts | 19 - ...install_prepackaged_timelines_route.gen.ts | 12 +- ...ll_prepackaged_timelines_route.schema.yaml | 16 +- .../common/api/timeline/model/api.ts | 593 ++---------------- .../api/timeline/model/components.gen.ts | 172 +++-- .../api/timeline/model/components.schema.yaml | 220 +++++-- .../patch_timeline_route.gen.ts | 10 +- .../patch_timeline_route.schema.yaml | 21 +- .../patch_timelines/patch_timelines_schema.ts | 24 - .../persist_favorite_route.schema.yaml | 7 - .../persist_note_route.schema.yaml | 7 - .../pinned_events_route.schema.yaml | 7 - .../pinned_events/pinned_events_route.ts | 34 - .../resolve_timeline_route.gen.ts | 11 +- .../resolve_timeline_route.schema.yaml | 24 +- .../common/api/timeline/routes.ts | 50 +- .../common/search_strategy/timeline/index.ts | 124 +--- .../common/timelines/zod_errors.ts | 29 + ...imeline_api_2023_10_31.bundled.schema.yaml | 404 ++++++------ ...imeline_api_2023_10_31.bundled.schema.yaml | 404 ++++++------ .../public/common/mock/timeline_results.ts | 9 +- .../components/alerts_table/actions.tsx | 14 +- .../components/open_timeline/helpers.test.ts | 22 +- .../components/open_timeline/helpers.ts | 24 +- .../components/open_timeline/types.ts | 8 +- .../public/timelines/containers/all/index.tsx | 22 +- .../public/timelines/containers/api.ts | 157 ++--- .../public/timelines/containers/helpers.ts | 4 +- .../middlewares/timeline_pinned_event.test.ts | 1 + .../middlewares/timeline_pinned_event.ts | 2 +- .../store/middlewares/timeline_save.test.ts | 4 +- .../store/middlewares/timeline_save.ts | 23 +- ...ebuilt_rules_and_timelines_status_route.ts | 9 +- .../logic/perform_timelines_installation.ts | 10 +- .../timeline/__mocks__/request_responses.ts | 13 +- .../timeline/__mocks__/resolve_timeline.ts | 4 +- .../clean_draft_timelines/index.ts | 7 +- .../get_draft_timelines/index.ts | 15 +- .../server/lib/timeline/routes/index.ts | 28 +- .../lib/timeline/routes/notes/delete_note.ts | 10 +- .../lib/timeline/routes/notes/get_notes.ts | 10 +- .../lib/timeline/routes/notes/persist_note.ts | 7 +- .../pinned_events/persist_pinned_event.ts | 22 +- .../helpers.test.ts | 6 +- .../install_prepackaged_timelines/helpers.ts | 6 +- .../install_prepackaged_timelines/index.ts | 32 +- .../routes/timelines/copy_timeline/index.ts | 16 +- .../timelines/create_timelines/helpers.ts | 7 +- .../timelines/create_timelines/index.ts | 14 +- .../timelines/delete_timelines/index.ts | 6 +- .../timelines/export_timelines/helpers.ts | 6 +- .../timelines/get_timeline/index.test.ts | 8 +- .../routes/timelines/get_timeline/index.ts | 23 +- .../timelines/get_timelines/index.test.ts | 8 +- .../routes/timelines/get_timelines/index.ts | 45 +- .../create_timelines_stream_from_ndjson.ts | 24 +- .../timelines/import_timelines/helpers.ts | 20 +- .../timelines/import_timelines/index.test.ts | 47 +- .../timelines/import_timelines/index.ts | 32 +- .../routes/timelines/patch_timelines/index.ts | 16 +- .../timelines/persist_favorite/index.ts | 11 +- .../timelines/resolve_timeline/index.ts | 24 +- .../saved_object/notes/persist_notes.ts | 2 +- .../convert_saved_object_to_savedtimeline.ts | 4 +- .../saved_object/timelines/index.test.ts | 8 +- .../timeline/saved_object/timelines/index.ts | 87 +-- .../timelines/pick_saved_timeline.ts | 6 +- .../timeline/utils/check_timelines_status.ts | 24 +- .../server/lib/timeline/utils/common.ts | 9 +- .../lib/timeline/utils/failure_cases.test.ts | 40 +- .../lib/timeline/utils/failure_cases.ts | 38 +- .../lib/timeline/utils/timeline_object.ts | 4 +- .../services/security_solution_api.gen.ts | 16 + .../trial_license_complete_tier/timeline.ts | 6 +- .../cypress/objects/timeline.ts | 8 +- .../cypress/tasks/api_calls/timelines.ts | 13 +- .../endpoint_solution_integrations.ts | 4 +- .../services/timeline/index.ts | 28 +- 107 files changed, 2014 insertions(+), 2661 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts create mode 100644 x-pack/plugins/security_solution/common/timelines/zod_errors.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0988c59ab7d23..7811e4409d905 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1532,6 +1532,7 @@ x-pack/test/security_solution_api_integration/test_suites/sources @elastic/secur x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout @elastic/security-threat-hunting-investigations x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/common/timelines @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/alerts_viewer @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_action @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/event_details @elastic/security-threat-hunting-investigations diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index f4ed7a9767924..621efdc026eea 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -16356,19 +16356,21 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -16405,23 +16407,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -16483,21 +16470,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates the Timeline was successfully created. '405': content: @@ -16514,6 +16488,37 @@ paths: tags: - Security Timeline API - access:securitySolution + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - access:securitySolution /api/timeline/_draft: get: description: >- @@ -16532,23 +16537,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates that the draft Timeline was successfully retrieved. '403': content: @@ -16610,23 +16600,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -16782,28 +16757,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Security_Timeline_API_Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The Timelines to import as a readable stream. required: true responses: @@ -16811,13 +16772,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the import of Timelines was successful. '400': content: @@ -16876,7 +16832,9 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + nullable: true type: array timelinesToInstall: items: @@ -16899,13 +16857,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the installation of prepackaged Timelines was successful. '500': content: @@ -16943,19 +16896,16 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: + data: $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + #/components/schemas/Security_Timeline_API_ResolvedTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -17024,36 +16974,26 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) Timelines were found and returned. '400': content: @@ -31335,30 +31275,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string Security_Timeline_API_DataProviderQueryMatch: type: object @@ -31380,6 +31329,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/Security_Timeline_API_QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/Security_Timeline_API_DataProviderType' + nullable: true Security_Timeline_API_DataProviderResult: type: object properties: @@ -31467,41 +31420,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string Security_Timeline_API_GetNotesResult: type: object @@ -31566,6 +31537,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Security_Timeline_API_Note: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BareNote' @@ -31586,6 +31563,23 @@ components: #/components/schemas/Security_Timeline_API_PinnedEventBaseResponseBody - nullable: true type: object + Security_Timeline_API_PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data Security_Timeline_API_PinnedEvent: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BarePinnedEvent' @@ -31623,34 +31617,29 @@ components: nullable: true type: string value: - nullable: true - type: string - Security_Timeline_API_Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + Security_Timeline_API_ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: >- + #/components/schemas/Security_Timeline_API_SavedObjectResolveAliasPurpose + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/Security_Timeline_API_SavedObjectResolveOutcome' + timeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + required: + - timeline + - outcome Security_Timeline_API_ResponseNote: type: object properties: @@ -31685,6 +31674,17 @@ components: - threat_match - zeek type: string + Security_Timeline_API_SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + Security_Timeline_API_SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string Security_Timeline_API_SavedTimeline: type: object properties: @@ -31713,12 +31713,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -31808,6 +31812,18 @@ components: updatedBy: nullable: true type: string + Security_Timeline_API_SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version Security_Timeline_API_SerializedFilterQueryResult: type: object properties: @@ -31857,27 +31873,64 @@ components: Security_Timeline_API_TimelineResponse: allOf: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - $ref: >- + #/components/schemas/Security_Timeline_API_SavedTimelineWithSavedObjectId - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true + type: array + Security_Timeline_API_TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 618bd42ab1f72..79960c7287336 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -20445,19 +20445,21 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -20494,23 +20496,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -20572,21 +20559,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates the Timeline was successfully created. '405': content: @@ -20603,6 +20577,37 @@ paths: tags: - Security Timeline API - access:securitySolution + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - access:securitySolution /api/timeline/_draft: get: description: >- @@ -20621,23 +20626,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates that the draft Timeline was successfully retrieved. '403': content: @@ -20699,23 +20689,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -20871,28 +20846,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Security_Timeline_API_Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The Timelines to import as a readable stream. required: true responses: @@ -20900,13 +20861,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the import of Timelines was successful. '400': content: @@ -20965,7 +20921,9 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + nullable: true type: array timelinesToInstall: items: @@ -20988,13 +20946,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the installation of prepackaged Timelines was successful. '500': content: @@ -21032,19 +20985,16 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: + data: $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + #/components/schemas/Security_Timeline_API_ResolvedTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -21113,36 +21063,26 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) Timelines were found and returned. '400': content: @@ -39344,30 +39284,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string Security_Timeline_API_DataProviderQueryMatch: type: object @@ -39389,6 +39338,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/Security_Timeline_API_QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/Security_Timeline_API_DataProviderType' + nullable: true Security_Timeline_API_DataProviderResult: type: object properties: @@ -39476,41 +39429,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string Security_Timeline_API_GetNotesResult: type: object @@ -39575,6 +39546,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Security_Timeline_API_Note: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BareNote' @@ -39595,6 +39572,23 @@ components: #/components/schemas/Security_Timeline_API_PinnedEventBaseResponseBody - nullable: true type: object + Security_Timeline_API_PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data Security_Timeline_API_PinnedEvent: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BarePinnedEvent' @@ -39632,34 +39626,29 @@ components: nullable: true type: string value: - nullable: true - type: string - Security_Timeline_API_Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + Security_Timeline_API_ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: >- + #/components/schemas/Security_Timeline_API_SavedObjectResolveAliasPurpose + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/Security_Timeline_API_SavedObjectResolveOutcome' + timeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + required: + - timeline + - outcome Security_Timeline_API_ResponseNote: type: object properties: @@ -39694,6 +39683,17 @@ components: - threat_match - zeek type: string + Security_Timeline_API_SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + Security_Timeline_API_SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string Security_Timeline_API_SavedTimeline: type: object properties: @@ -39722,12 +39722,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -39817,6 +39821,18 @@ components: updatedBy: nullable: true type: string + Security_Timeline_API_SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version Security_Timeline_API_SerializedFilterQueryResult: type: object properties: @@ -39866,27 +39882,64 @@ components: Security_Timeline_API_TimelineResponse: allOf: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - $ref: >- + #/components/schemas/Security_Timeline_API_SavedTimelineWithSavedObjectId - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true + type: array + Security_Timeline_API_TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index 9b057bb19d7e2..255b56c485b9b 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -296,6 +296,10 @@ import type { CleanDraftTimelinesRequestBodyInput, CleanDraftTimelinesResponse, } from './timeline/clean_draft_timelines/clean_draft_timelines_route.gen'; +import type { + CopyTimelineRequestBodyInput, + CopyTimelineResponse, +} from './timeline/copy_timeline/copy_timeline_route.gen'; import type { CreateTimelinesRequestBodyInput, CreateTimelinesResponse, @@ -556,6 +560,23 @@ If asset criticality records already exist for the specified entities, those rec }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Copies and returns a timeline or timeline template. + + */ + async copyTimeline(props: CopyTimelineProps) { + this.log.info(`${new Date().toISOString()} Calling API CopyTimeline`); + return this.kbnClient + .request({ + path: '/api/timeline/_copy', + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', + }, + method: 'GET', + body: props.body, + }) + .catch(catchAxiosErrorFormatAndThrow); + } async createAlertsIndex() { this.log.info(`${new Date().toISOString()} Calling API CreateAlertsIndex`); return this.kbnClient @@ -1993,6 +2014,9 @@ export interface BulkUpsertAssetCriticalityRecordsProps { export interface CleanDraftTimelinesProps { body: CleanDraftTimelinesRequestBodyInput; } +export interface CopyTimelineProps { + body: CopyTimelineRequestBodyInput; +} export interface CreateAlertsMigrationProps { body: CreateAlertsMigrationRequestBodyInput; } diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts index 514a06ceec328..f3858b1cb8f34 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { TimelineType, TimelineResponse } from '../model/components.gen'; +import { TimelineType, PersistTimelineResponse } from '../model/components.gen'; export type CleanDraftTimelinesRequestBody = z.infer; export const CleanDraftTimelinesRequestBody = z.object({ @@ -25,10 +25,4 @@ export const CleanDraftTimelinesRequestBody = z.object({ export type CleanDraftTimelinesRequestBodyInput = z.input; export type CleanDraftTimelinesResponse = z.infer; -export const CleanDraftTimelinesResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const CleanDraftTimelinesResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml index 5e1fbff1f296d..7ff1e397c1d18 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Draft Timeline API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_draft: post: @@ -39,19 +32,7 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '403': description: Indicates that the user does not have the required permissions to create a draft Timeline. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts new file mode 100644 index 0000000000000..6e75f0baaf7ef --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Copy Timeline API + * version: 2023-10-31 + */ + +import { z } from '@kbn/zod'; + +import { SavedTimeline, PersistTimelineResponse } from '../model/components.gen'; + +export type CopyTimelineRequestBody = z.infer; +export const CopyTimelineRequestBody = z.object({ + timeline: SavedTimeline, + timelineIdToCopy: z.string(), +}); +export type CopyTimelineRequestBodyInput = z.input; + +export type CopyTimelineResponse = z.infer; +export const CopyTimelineResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml new file mode 100644 index 0000000000000..743de4c334fe5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml @@ -0,0 +1,34 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Copy Timeline API + version: '2023-10-31' +paths: + /api/timeline/_copy: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: CopyTimeline + summary: Copies timeline or timeline template + description: | + Copies and returns a timeline or timeline template. + tags: + - access:securitySolution + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [timeline, timelineIdToCopy] + properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + timelineIdToCopy: + type: string + responses: + '200': + description: Indicates that the timeline has been successfully copied. + content: + application/json: + schema: + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' diff --git a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts deleted file mode 100644 index 1b7dc1d4c3566..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts +++ /dev/null @@ -1,15 +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 { SavedTimelineRuntimeType } from '../model/api'; - -export const copyTimelineSchema = rt.type({ - timeline: SavedTimelineRuntimeType, - timelineIdToCopy: rt.string, -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts index 51af791fbc3f0..33fc1855f390a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts @@ -17,29 +17,23 @@ import { z } from '@kbn/zod'; import { + SavedTimeline, TimelineStatus, TimelineType, - SavedTimeline, - TimelineResponse, + PersistTimelineResponse, } from '../model/components.gen'; export type CreateTimelinesRequestBody = z.infer; export const CreateTimelinesRequestBody = z.object({ + timeline: SavedTimeline, status: TimelineStatus.nullable().optional(), timelineId: z.string().nullable().optional(), templateTimelineId: z.string().nullable().optional(), templateTimelineVersion: z.number().nullable().optional(), timelineType: TimelineType.nullable().optional(), version: z.string().nullable().optional(), - timeline: SavedTimeline, }); export type CreateTimelinesRequestBodyInput = z.input; export type CreateTimelinesResponse = z.infer; -export const CreateTimelinesResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse.optional(), - }), - }), -}); +export const CreateTimelinesResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml index 9dce2658694ab..8f369fde6e9e9 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-create.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: post: @@ -29,9 +22,10 @@ paths: application/json: schema: type: object - required: - - timeline + required: [timeline] properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' status: $ref: '../model/components.schema.yaml#/components/schemas/TimelineStatus' nullable: true @@ -50,26 +44,13 @@ paths: version: type: string nullable: true - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' responses: '200': description: Indicates the Timeline was successfully created. content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '405': description: Indicates that there was an error in the Timeline creation. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts deleted file mode 100644 index 1911df3941a35..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts +++ /dev/null @@ -1,36 +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 type { ResponseTimeline } from '../model/api'; -import { - SavedTimelineRuntimeType, - TimelineStatusLiteralRt, - TimelineTypeLiteralRt, -} from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -export const createTimelineSchema = rt.intersection([ - rt.type({ - timeline: SavedTimelineRuntimeType, - }), - rt.partial({ - status: unionWithNullType(TimelineStatusLiteralRt), - timelineId: unionWithNullType(rt.string), - templateTimelineId: unionWithNullType(rt.string), - templateTimelineVersion: unionWithNullType(rt.number), - timelineType: unionWithNullType(TimelineTypeLiteralRt), - version: unionWithNullType(rt.string), - }), -]); - -export interface CreateTimelinesResponse { - data: { - persistTimeline: ResponseTimeline; - }; -} diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml index 380029bff8070..e79cb9aab65ac 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Notes API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/note: delete: diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml index 7a0a168747d47..bb6674fa65877 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-delete.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: delete: diff --git a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml index 44ef7d0f3abe0..24387adf1f2a5 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_export: post: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts index edbbc37744d03..327f02a8fb6f5 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { TimelineType, TimelineResponse } from '../model/components.gen'; +import { TimelineType, PersistTimelineResponse } from '../model/components.gen'; export type GetDraftTimelinesRequestQuery = z.infer; export const GetDraftTimelinesRequestQuery = z.object({ @@ -25,10 +25,4 @@ export const GetDraftTimelinesRequestQuery = z.object({ export type GetDraftTimelinesRequestQueryInput = z.input; export type GetDraftTimelinesResponse = z.infer; -export const GetDraftTimelinesResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const GetDraftTimelinesResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml index 21cabb87cd335..4a8af1c77f40d 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Get Draft Timelines API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_draft: get: @@ -31,19 +24,7 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '403': description: If a draft Timeline was not found and we attempted to create one, it indicates that the user does not have the required permissions to create a draft Timeline. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts deleted file mode 100644 index 13625715289f6..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts +++ /dev/null @@ -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 getDraftTimelineSchema = rt.type({ - timelineType: TimelineTypeLiteralRt, -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml index 5942fd76c5d51..793eeac5e7c71 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Notes API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/note: get: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts index 622545e22b37a..7a41788077524 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts @@ -32,8 +32,11 @@ export const GetTimelineRequestQuery = z.object({ export type GetTimelineRequestQueryInput = z.input; export type GetTimelineResponse = z.infer; -export const GetTimelineResponse = z.object({ - data: z.object({ - getOneTimeline: TimelineResponse.nullable(), +export const GetTimelineResponse = z.union([ + z.object({ + data: z.object({ + getOneTimeline: TimelineResponse, + }), }), -}); + z.object({}).strict(), +]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml index d0944bc0fcb10..9b5d3fedfd59e 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/_get_timeline_or_timeline_template_by_savedobjectid.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: get: @@ -39,13 +32,15 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [getOneTimeline] + oneOf: + - type: object + required: [data] properties: - getOneTimeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - nullable: true + data: + type: object + required: [getOneTimeline] + properties: + getOneTimeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + - type: object + additionalProperties: false diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts deleted file mode 100644 index cca6886f42025..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts +++ /dev/null @@ -1,15 +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'; - -export const getTimelineQuerySchema = rt.partial({ - template_timeline_id: rt.string, - id: rt.string, -}); - -export type GetTimelineQuery = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts index 5ce3256ae48be..714746875599b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts @@ -41,13 +41,11 @@ export type GetTimelinesRequestQueryInput = z.input; export const GetTimelinesResponse = z.object({ - data: z.object({ - timelines: z.array(TimelineResponse), - totalCount: z.number(), - defaultTimelineCount: z.number(), - templateTimelineCount: z.number(), - favoriteCount: z.number(), - elasticTemplateTimelineCount: z.number(), - customTemplateTimelineCount: z.number(), - }), + timeline: z.array(TimelineResponse), + totalCount: z.number(), + defaultTimelineCount: z.number().optional(), + templateTimelineCount: z.number().optional(), + favoriteCount: z.number().optional(), + elasticTemplateTimelineCount: z.number().optional(), + customTemplateTimelineCount: z.number().optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml index f889af73e10d7..36a7b853b3823 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-get.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timelines: get: @@ -75,37 +68,27 @@ paths: application/json: schema: type: object - required: [data] + required: [ + timeline, + totalCount, + ] properties: - data: - type: object - required: - [ - timelines, - totalCount, - defaultTimelineCount, - templateTimelineCount, - favoriteCount, - elasticTemplateTimelineCount, - customTemplateTimelineCount, - ] - properties: - timelines: - type: array - items: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - totalCount: - type: number - defaultTimelineCount: - type: number - templateTimelineCount: - type: number - favoriteCount: - type: number - elasticTemplateTimelineCount: - type: number - customTemplateTimelineCount: - type: number + timeline: + type: array + items: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + totalCount: + type: number + defaultTimelineCount: + type: number + templateTimelineCount: + type: number + favoriteCount: + type: number + elasticTemplateTimelineCount: + type: number + customTemplateTimelineCount: + type: number '400': description: Bad request. The user supplied invalid data. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts deleted file mode 100644 index 9f35197358aea..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts +++ /dev/null @@ -1,28 +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 { - direction, - sortFieldTimeline, - TimelineStatusLiteralRt, - TimelineTypeLiteralRt, -} from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -const BoolFromString = rt.union([rt.literal('true'), rt.literal('false')]); - -export const getTimelinesQuerySchema = rt.partial({ - only_user_favorite: unionWithNullType(BoolFromString), - page_index: unionWithNullType(rt.string), - page_size: unionWithNullType(rt.string), - search: unionWithNullType(rt.string), - sort_field: sortFieldTimeline, - sort_order: direction, - status: unionWithNullType(TimelineStatusLiteralRt), - timeline_type: unionWithNullType(TimelineTypeLiteralRt), -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts index 4624ffd713430..660f8bc287665 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts @@ -16,23 +16,14 @@ import { z } from '@kbn/zod'; -import { Readable, ImportTimelineResult } from '../model/components.gen'; +import { ImportTimelineResult } from '../model/components.gen'; export type ImportTimelinesRequestBody = z.infer; export const ImportTimelinesRequestBody = z.object({ - file: Readable.merge( - z.object({ - hapi: z.object({ - filename: z.string(), - headers: z.object({}), - isImmutable: z.enum(['true', 'false']).optional(), - }), - }) - ), + isImmutable: z.enum(['true', 'false']).optional(), + file: z.unknown(), }); export type ImportTimelinesRequestBodyInput = z.input; export type ImportTimelinesResponse = z.infer; -export const ImportTimelinesResponse = z.object({ - data: ImportTimelineResult, -}); +export const ImportTimelinesResponse = ImportTimelineResult; diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml index 34a35aa59db0e..d0d2691f2ac7e 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_import: post: @@ -29,37 +22,21 @@ paths: application/json: schema: type: object + required: [file] properties: - file: - allOf: - - $ref: '../model/components.schema.yaml#/components/schemas/Readable' - - type: object - required: [hapi] - properties: - hapi: - type: object - required: [filename, headers] - properties: - filename: - type: string - headers: - type: object - isImmutable: - type: string - enum: - - 'true' - - 'false' + isImmutable: + type: string + enum: + - 'true' + - 'false' + file: {} responses: '200': description: Indicates the import of Timelines was successful. content: application/json: schema: - type: object - required: [data] - properties: - data: - $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' '400': description: Indicates the import of Timelines was unsuccessful because of an invalid file extension. diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts deleted file mode 100644 index 2ad6f3f8c7333..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts +++ /dev/null @@ -1,60 +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 { BareNoteSchema, SavedTimelineRuntimeType } from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -const pinnedEventIds = unionWithNullType(rt.array(rt.string)); - -export const eventNotes = unionWithNullType(rt.array(BareNoteSchema)); -export const globalNotes = unionWithNullType(rt.array(BareNoteSchema)); - -export const ImportTimelinesSchemaRt = rt.intersection([ - SavedTimelineRuntimeType, - rt.type({ - savedObjectId: unionWithNullType(rt.string), - version: unionWithNullType(rt.string), - }), - rt.type({ - globalNotes, - eventNotes, - pinnedEventIds, - }), -]); - -export type ImportTimelinesSchema = rt.TypeOf; - -const ReadableRt = rt.partial({ - _maxListeners: rt.unknown, - _readableState: rt.unknown, - _read: rt.unknown, - readable: rt.boolean, - _events: rt.unknown, - _eventsCount: rt.number, - _data: rt.unknown, - _position: rt.number, - _encoding: rt.string, -}); - -const booleanInString = rt.union([rt.literal('true'), rt.literal('false')]); - -export const ImportTimelinesPayloadSchemaRt = rt.intersection([ - rt.type({ - file: rt.intersection([ - ReadableRt, - rt.type({ - hapi: rt.type({ - filename: rt.string, - headers: rt.unknown, - }), - }), - ]), - }), - rt.partial({ isImmutable: booleanInString }), -]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/index.ts b/x-pack/plugins/security_solution/common/api/timeline/index.ts index 806c0c8539d97..0a148bcc59797 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/index.ts @@ -7,13 +7,3 @@ export * from './model/api'; export * from './routes'; - -export * from './get_draft_timelines/get_draft_timelines_route'; -export * from './create_timelines/create_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 './pinned_events/pinned_events_route'; -export * from './install_prepackaged_timelines/install_prepackaged_timelines'; -export * from './copy_timeline/copy_timeline_route'; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts deleted file mode 100644 index 0d9b2d3e81121..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts +++ /dev/null @@ -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 rt from 'io-ts'; - -import { unionWithNullType } from '../../../utility_types'; -import { ImportTimelinesSchemaRt, TimelineSavedToReturnObjectRuntimeType } from '..'; - -export const checkTimelineStatusRt = rt.type({ - timelinesToInstall: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), - timelinesToUpdate: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), - prepackagedTimelines: rt.array(unionWithNullType(TimelineSavedToReturnObjectRuntimeType)), -}); - -export type CheckTimelineStatusRt = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts index add1e2ffa94b8..f2f09c0257e87 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts @@ -16,7 +16,11 @@ import { z } from '@kbn/zod'; -import { ImportTimelines, SavedTimeline, ImportTimelineResult } from '../model/components.gen'; +import { + ImportTimelines, + TimelineSavedToReturnObject, + ImportTimelineResult, +} from '../model/components.gen'; export type InstallPrepackedTimelinesRequestBody = z.infer< typeof InstallPrepackedTimelinesRequestBody @@ -24,13 +28,11 @@ export type InstallPrepackedTimelinesRequestBody = z.infer< export const InstallPrepackedTimelinesRequestBody = z.object({ timelinesToInstall: z.array(ImportTimelines.nullable()), timelinesToUpdate: z.array(ImportTimelines.nullable()), - prepackagedTimelines: z.array(SavedTimeline), + prepackagedTimelines: z.array(TimelineSavedToReturnObject.nullable()), }); export type InstallPrepackedTimelinesRequestBodyInput = z.input< typeof InstallPrepackedTimelinesRequestBody >; export type InstallPrepackedTimelinesResponse = z.infer; -export const InstallPrepackedTimelinesResponse = z.object({ - data: ImportTimelineResult, -}); +export const InstallPrepackedTimelinesResponse = ImportTimelineResult; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml index 7dfa5afbbe5ed..876bf499e385b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Install Prepackaged Timelines API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_prepackaged: post: @@ -41,18 +34,15 @@ paths: prepackagedTimelines: type: array items: - $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + $ref: '../model/components.schema.yaml#/components/schemas/TimelineSavedToReturnObject' + nullable: true responses: '200': description: Indicates the installation of prepackaged Timelines was successful. content: application/json: schema: - type: object - required: [data] - properties: - data: - $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' '500': description: Indicates the installation of prepackaged Timelines was unsuccessful. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts index ff6707b700626..250daaf3077eb 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts @@ -5,30 +5,35 @@ * 2.0. */ -import * as runtimeTypes from 'io-ts'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; - -import { stringEnum, unionWithNullType } from '../../../utility_types'; - -import type { Maybe } from '../../../search_strategy'; -import { Direction } from '../../../search_strategy'; -import { PinnedEventRuntimeType } from '../pinned_events/pinned_events_route'; -import { ErrorSchema } from './error_schema'; import type { DataProviderType } from './components.gen'; import { BareNote, BarePinnedEvent, + ColumnHeaderResult, DataProviderTypeEnum, + DataProviderResult, FavoriteTimelineResponse, - type FavoriteTimelineResult, + FilterTimelineResult, + ImportTimelineResult, + ImportTimelines, type Note, PinnedEvent, + PersistTimelineResponse, + QueryMatchResult, + ResolvedTimeline, RowRendererId, RowRendererIdEnum, + SavedTimeline, + SavedTimelineWithSavedObjectId, + Sort, + SortDirection, SortFieldTimeline, SortFieldTimelineEnum, TemplateTimelineType, TemplateTimelineTypeEnum, + TimelineErrorResponse, + TimelineResponse, + TimelineSavedToReturnObject, TimelineStatus, TimelineStatusEnum, TimelineType, @@ -38,62 +43,45 @@ import { export { BareNote, BarePinnedEvent, + ColumnHeaderResult, + DataProviderResult, DataProviderType, DataProviderTypeEnum, FavoriteTimelineResponse, + FilterTimelineResult, + ImportTimelineResult, + ImportTimelines, Note, PinnedEvent, + PersistTimelineResponse, + QueryMatchResult, + ResolvedTimeline, RowRendererId, RowRendererIdEnum, + SavedTimeline, + SavedTimelineWithSavedObjectId, + Sort, + SortDirection, SortFieldTimeline, SortFieldTimelineEnum, TemplateTimelineType, + TimelineErrorResponse, + TimelineResponse, TemplateTimelineTypeEnum, + TimelineSavedToReturnObject, TimelineStatus, TimelineStatusEnum, TimelineType, TimelineTypeEnum, }; -export type BarePinnedEventWithoutExternalRefs = Omit; - /** - * Outcome is a property of the saved object resolve api - * will tell us info about the rule after 8.0 migrations + * This type represents a timeline type stored in a saved object that does not include any fields that reference + * other saved objects. */ -export type SavedObjectResolveOutcome = runtimeTypes.TypeOf; -export const SavedObjectResolveOutcome = runtimeTypes.union([ - runtimeTypes.literal('exactMatch'), - runtimeTypes.literal('aliasMatch'), - runtimeTypes.literal('conflict'), -]); - -export type SavedObjectResolveAliasTargetId = runtimeTypes.TypeOf< - typeof SavedObjectResolveAliasTargetId ->; -export const SavedObjectResolveAliasTargetId = runtimeTypes.string; - -export type SavedObjectResolveAliasPurpose = runtimeTypes.TypeOf< - typeof SavedObjectResolveAliasPurpose ->; -export const SavedObjectResolveAliasPurpose = runtimeTypes.union([ - runtimeTypes.literal('savedObjectConversion'), - runtimeTypes.literal('savedObjectImport'), -]); +export type TimelineWithoutExternalRefs = Omit; -export const BareNoteSchema = runtimeTypes.intersection([ - runtimeTypes.type({ - timelineId: runtimeTypes.string, - }), - runtimeTypes.partial({ - 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 type BarePinnedEventWithoutExternalRefs = Omit; /** * This type represents a note type stored in a saved object that does not include any fields that reference @@ -101,350 +89,9 @@ export const BareNoteSchema = runtimeTypes.intersection([ */ export type BareNoteWithoutExternalRefs = Omit; -export const NoteRuntimeType = runtimeTypes.intersection([ - BareNoteSchema, - runtimeTypes.type({ - noteId: runtimeTypes.string, - version: runtimeTypes.string, - }), -]); - -/* - * ColumnHeader Types - */ -const SavedColumnHeaderRuntimeType = runtimeTypes.partial({ - aggregatable: unionWithNullType(runtimeTypes.boolean), - category: unionWithNullType(runtimeTypes.string), - columnHeaderType: unionWithNullType(runtimeTypes.string), - description: unionWithNullType(runtimeTypes.string), - example: unionWithNullType(runtimeTypes.string), - indexes: unionWithNullType(runtimeTypes.array(runtimeTypes.string)), - id: unionWithNullType(runtimeTypes.string), - name: unionWithNullType(runtimeTypes.string), - placeholder: unionWithNullType(runtimeTypes.string), - searchable: unionWithNullType(runtimeTypes.boolean), - type: unionWithNullType(runtimeTypes.string), -}); - -/* - * DataProvider Types - */ -const SavedDataProviderQueryMatchBasicRuntimeType = runtimeTypes.partial({ - field: unionWithNullType(runtimeTypes.string), - displayField: unionWithNullType(runtimeTypes.string), - value: runtimeTypes.union([ - runtimeTypes.null, - runtimeTypes.string, - runtimeTypes.array(runtimeTypes.string), - ]), - displayValue: unionWithNullType(runtimeTypes.string), - operator: unionWithNullType(runtimeTypes.string), -}); - -const SavedDataProviderQueryMatchRuntimeType = runtimeTypes.partial({ - id: unionWithNullType(runtimeTypes.string), - name: unionWithNullType(runtimeTypes.string), - enabled: unionWithNullType(runtimeTypes.boolean), - excluded: unionWithNullType(runtimeTypes.boolean), - kqlQuery: unionWithNullType(runtimeTypes.string), - queryMatch: unionWithNullType(SavedDataProviderQueryMatchBasicRuntimeType), -}); - -export const DataProviderTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(DataProviderTypeEnum.default), - runtimeTypes.literal(DataProviderTypeEnum.template), -]); - -const SavedDataProviderRuntimeType = runtimeTypes.partial({ - id: unionWithNullType(runtimeTypes.string), - name: unionWithNullType(runtimeTypes.string), - enabled: unionWithNullType(runtimeTypes.boolean), - excluded: unionWithNullType(runtimeTypes.boolean), - kqlQuery: unionWithNullType(runtimeTypes.string), - queryMatch: unionWithNullType(SavedDataProviderQueryMatchBasicRuntimeType), - and: unionWithNullType(runtimeTypes.array(SavedDataProviderQueryMatchRuntimeType)), - type: unionWithNullType(DataProviderTypeLiteralRt), -}); - -/* - * Filters Types - */ -const SavedFilterMetaRuntimeType = runtimeTypes.partial({ - alias: unionWithNullType(runtimeTypes.string), - controlledBy: unionWithNullType(runtimeTypes.string), - disabled: unionWithNullType(runtimeTypes.boolean), - field: unionWithNullType(runtimeTypes.string), - formattedValue: unionWithNullType(runtimeTypes.string), - index: unionWithNullType(runtimeTypes.string), - key: unionWithNullType(runtimeTypes.string), - negate: unionWithNullType(runtimeTypes.boolean), - params: unionWithNullType(runtimeTypes.string), - type: unionWithNullType(runtimeTypes.string), - value: unionWithNullType(runtimeTypes.string), -}); - -const SavedFilterRuntimeType = runtimeTypes.partial({ - exists: unionWithNullType(runtimeTypes.string), - meta: unionWithNullType(SavedFilterMetaRuntimeType), - match_all: unionWithNullType(runtimeTypes.string), - missing: unionWithNullType(runtimeTypes.string), - query: unionWithNullType(runtimeTypes.string), - range: unionWithNullType(runtimeTypes.string), - script: unionWithNullType(runtimeTypes.string), -}); - -/* - * eqlOptionsQuery -> filterQuery Types - */ -const EqlOptionsRuntimeType = runtimeTypes.partial({ - eventCategoryField: unionWithNullType(runtimeTypes.string), - query: unionWithNullType(runtimeTypes.string), - tiebreakerField: unionWithNullType(runtimeTypes.string), - timestampField: unionWithNullType(runtimeTypes.string), - size: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])), -}); - -/* - * kqlQuery -> filterQuery Types - */ -const SavedKueryFilterQueryRuntimeType = runtimeTypes.partial({ - kind: unionWithNullType(runtimeTypes.string), - expression: unionWithNullType(runtimeTypes.string), -}); - -const SavedSerializedFilterQueryQueryRuntimeType = runtimeTypes.partial({ - kuery: unionWithNullType(SavedKueryFilterQueryRuntimeType), - serializedQuery: unionWithNullType(runtimeTypes.string), -}); - -const SavedFilterQueryQueryRuntimeType = runtimeTypes.partial({ - filterQuery: unionWithNullType(SavedSerializedFilterQueryQueryRuntimeType), -}); - -/* - * DatePicker Range Types - */ -const SavedDateRangePickerRuntimeType = runtimeTypes.partial({ - /* Before the change of all timestamp to ISO string the values of start and from - * attributes where a number. Specifically UNIX timestamps. - * To support old timeline's saved object we need to add the number io-ts type - */ - start: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])), - end: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])), -}); - -/* - * Favorite Types - */ -const SavedFavoriteRuntimeType = runtimeTypes.partial({ - keySearch: unionWithNullType(runtimeTypes.string), - favoriteDate: unionWithNullType(runtimeTypes.number), - fullName: unionWithNullType(runtimeTypes.string), - userName: unionWithNullType(runtimeTypes.string), -}); - -/* - * Sort Types - */ - -const SavedSortObject = runtimeTypes.partial({ - columnId: unionWithNullType(runtimeTypes.string), - columnType: unionWithNullType(runtimeTypes.string), - sortDirection: unionWithNullType(runtimeTypes.string), -}); -const SavedSortRuntimeType = runtimeTypes.union([ - runtimeTypes.array(SavedSortObject), - SavedSortObject, -]); - -export type Sort = runtimeTypes.TypeOf; - -/* - * Timeline Statuses - */ - -export const TimelineStatusLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TimelineStatusEnum.active), - runtimeTypes.literal(TimelineStatusEnum.draft), - runtimeTypes.literal(TimelineStatusEnum.immutable), -]); - export const RowRendererCount = Object.keys(RowRendererIdEnum).length; export const RowRendererValues = Object.values(RowRendererId.Values); -const RowRendererIdRuntimeType = stringEnum(RowRendererIdEnum, 'RowRendererId'); - -/** - * Timeline types - */ - -export const TimelineTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TimelineTypeEnum.template), - runtimeTypes.literal(TimelineTypeEnum.default), -]); - -/** - * This is the response type - */ -export const SavedTimelineRuntimeType = runtimeTypes.partial({ - columns: unionWithNullType(runtimeTypes.array(SavedColumnHeaderRuntimeType)), - dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)), - dataViewId: unionWithNullType(runtimeTypes.string), - description: unionWithNullType(runtimeTypes.string), - eqlOptions: unionWithNullType(EqlOptionsRuntimeType), - eventType: unionWithNullType(runtimeTypes.string), - excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)), - favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)), - filters: unionWithNullType(runtimeTypes.array(SavedFilterRuntimeType)), - indexNames: unionWithNullType(runtimeTypes.array(runtimeTypes.string)), - kqlMode: unionWithNullType(runtimeTypes.string), - kqlQuery: unionWithNullType(SavedFilterQueryQueryRuntimeType), - title: unionWithNullType(runtimeTypes.string), - templateTimelineId: unionWithNullType(runtimeTypes.string), - templateTimelineVersion: unionWithNullType(runtimeTypes.number), - timelineType: unionWithNullType(TimelineTypeLiteralRt), - dateRange: unionWithNullType(SavedDateRangePickerRuntimeType), - savedQueryId: unionWithNullType(runtimeTypes.string), - sort: unionWithNullType(SavedSortRuntimeType), - status: unionWithNullType(TimelineStatusLiteralRt), - created: unionWithNullType(runtimeTypes.number), - createdBy: unionWithNullType(runtimeTypes.string), - updated: unionWithNullType(runtimeTypes.number), - updatedBy: unionWithNullType(runtimeTypes.string), - savedSearchId: unionWithNullType(runtimeTypes.string), -}); - -export type SavedTimeline = runtimeTypes.TypeOf; - -export type SavedTimelineWithSavedObjectId = SavedTimeline & { - savedObjectId?: string | null; -}; - -/** - * This type represents a timeline type stored in a saved object that does not include any fields that reference - * other saved objects. - */ -export type TimelineWithoutExternalRefs = Omit; - -export const TimelineSavedToReturnObjectRuntimeType = runtimeTypes.intersection([ - SavedTimelineRuntimeType, - runtimeTypes.type({ - savedObjectId: runtimeTypes.string, - version: runtimeTypes.string, - }), - runtimeTypes.partial({ - eventIdToNoteIds: runtimeTypes.array(NoteRuntimeType), - noteIds: runtimeTypes.array(runtimeTypes.string), - notes: runtimeTypes.array(NoteRuntimeType), - pinnedEventIds: runtimeTypes.array(runtimeTypes.string), - pinnedEventsSaveObject: runtimeTypes.array(PinnedEventRuntimeType), - }), -]); - -export type TimelineSavedObject = runtimeTypes.TypeOf< - typeof TimelineSavedToReturnObjectRuntimeType ->; - -export const SingleTimelineResponseType = runtimeTypes.type({ - data: runtimeTypes.type({ - getOneTimeline: TimelineSavedToReturnObjectRuntimeType, - }), -}); - -export type SingleTimelineResponse = runtimeTypes.TypeOf; - -/** Resolved Timeline Response */ -export const ResolvedTimelineSavedObjectToReturnObjectRuntimeType = runtimeTypes.intersection([ - runtimeTypes.type({ - timeline: TimelineSavedToReturnObjectRuntimeType, - outcome: SavedObjectResolveOutcome, - }), - runtimeTypes.partial({ - alias_target_id: SavedObjectResolveAliasTargetId, - alias_purpose: SavedObjectResolveAliasPurpose, - }), -]); - -export type ResolvedTimelineWithOutcomeSavedObject = runtimeTypes.TypeOf< - typeof ResolvedTimelineSavedObjectToReturnObjectRuntimeType ->; - -export const ResolvedSingleTimelineResponseType = runtimeTypes.type({ - data: ResolvedTimelineSavedObjectToReturnObjectRuntimeType, -}); - -export type SingleTimelineResolveResponse = runtimeTypes.TypeOf< - typeof ResolvedSingleTimelineResponseType ->; - -const responseTimelines = runtimeTypes.type({ - timeline: runtimeTypes.array(TimelineSavedToReturnObjectRuntimeType), - totalCount: runtimeTypes.number, -}); - -export type ResponseTimelines = runtimeTypes.TypeOf; - -export const allTimelinesResponse = runtimeTypes.intersection([ - responseTimelines, - runtimeTypes.type({ - defaultTimelineCount: runtimeTypes.number, - templateTimelineCount: runtimeTypes.number, - elasticTemplateTimelineCount: runtimeTypes.number, - customTemplateTimelineCount: runtimeTypes.number, - favoriteCount: runtimeTypes.number, - }), -]); - -export type AllTimelinesResponse = runtimeTypes.TypeOf; - -/** - * All Timeline Saved object type with metadata - */ -export const TimelineResponseType = runtimeTypes.type({ - data: runtimeTypes.type({ - persistTimeline: runtimeTypes.intersection([ - runtimeTypes.partial({ - code: unionWithNullType(runtimeTypes.number), - message: unionWithNullType(runtimeTypes.string), - }), - runtimeTypes.type({ - timeline: TimelineSavedToReturnObjectRuntimeType, - }), - ]), - }), -}); - -export const TimelineErrorResponseType = runtimeTypes.union([ - runtimeTypes.type({ - status_code: runtimeTypes.number, - message: runtimeTypes.string, - }), - runtimeTypes.type({ - statusCode: runtimeTypes.number, - message: runtimeTypes.string, - }), -]); - -export type TimelineErrorResponse = runtimeTypes.TypeOf; -export type TimelineResponse = runtimeTypes.TypeOf; - -export const sortFieldTimeline = runtimeTypes.union([ - runtimeTypes.literal(SortFieldTimelineEnum.title), - runtimeTypes.literal(SortFieldTimelineEnum.description), - runtimeTypes.literal(SortFieldTimelineEnum.updated), - runtimeTypes.literal(SortFieldTimelineEnum.created), -]); - -export const direction = runtimeTypes.union([ - runtimeTypes.literal(Direction.asc), - runtimeTypes.literal(Direction.desc), -]); - -export const sortTimeline = runtimeTypes.type({ - sortField: sortFieldTimeline, - sortOrder: direction, -}); - /** * Import/export timelines */ @@ -457,187 +104,17 @@ export interface ExportedNotes { globalNotes: ExportedGlobalNotes; } -export type ExportedTimelines = SavedTimeline & - ExportedNotes & { - pinnedEventIds: string[]; - }; - export interface ExportTimelineNotFoundError { statusCode: number; message: string; } -export const importTimelineResultSchema = runtimeTypes.exact( - runtimeTypes.type({ - success: runtimeTypes.boolean, - success_count: PositiveInteger, - timelines_installed: PositiveInteger, - timelines_updated: PositiveInteger, - errors: runtimeTypes.array(ErrorSchema), - }) -); - -export type ImportTimelineResultSchema = runtimeTypes.TypeOf; - -export const pageInfoTimeline = runtimeTypes.type({ - pageIndex: runtimeTypes.number, - pageSize: runtimeTypes.number, -}); - export interface PageInfoTimeline { pageIndex: number; pageSize: number; } -export const getTimelinesArgs = runtimeTypes.partial({ - onlyUserFavorite: unionWithNullType(runtimeTypes.boolean), - pageInfo: unionWithNullType(pageInfoTimeline), - search: unionWithNullType(runtimeTypes.string), - sort: unionWithNullType(sortTimeline), - status: unionWithNullType(TimelineStatusLiteralRt), - timelineType: unionWithNullType(TimelineTypeLiteralRt), -}); - -export type GetTimelinesArgs = runtimeTypes.TypeOf; - -export interface ColumnHeaderResult { - aggregatable?: Maybe; - category?: Maybe; - columnHeaderType?: Maybe; - description?: Maybe; - example?: Maybe; - indexes?: Maybe; - id?: Maybe; - name?: Maybe; - placeholder?: Maybe; - searchable?: Maybe; - type?: Maybe; -} - -export interface DataProviderResult { - id?: Maybe; - name?: Maybe; - enabled?: Maybe; - excluded?: Maybe; - kqlQuery?: Maybe; - queryMatch?: Maybe; - type?: Maybe; - and?: Maybe; -} - -export interface QueryMatchResult { - field?: Maybe; - displayField?: Maybe; - value?: Maybe; - displayValue?: Maybe; - operator?: Maybe; -} - -export interface DateRangePickerResult { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - start?: Maybe; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - end?: Maybe; -} - -export interface EqlOptionsResult { - eventCategoryField?: Maybe; - tiebreakerField?: Maybe; - timestampField?: Maybe; - query?: Maybe; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - size?: Maybe; -} - -export interface FilterTimelineResult { - exists?: Maybe; - meta?: Maybe; - match_all?: Maybe; - missing?: Maybe; - query?: Maybe; - range?: Maybe; - script?: Maybe; -} - -export interface FilterMetaTimelineResult { - alias?: Maybe; - controlledBy?: Maybe; - disabled?: Maybe; - field?: Maybe; - formattedValue?: Maybe; - index?: Maybe; - key?: Maybe; - negate?: Maybe; - params?: Maybe; - type?: Maybe; - value?: Maybe; -} - -export interface SerializedFilterQueryResult { - filterQuery?: Maybe; -} - -export interface KueryFilterQueryResult { - kind?: Maybe; - expression?: Maybe; -} - -export interface SerializedKueryQueryResult { - kuery?: Maybe; - serializedQuery?: Maybe; -} - -export interface TimelineResult { - columns?: Maybe; - created?: Maybe; - createdBy?: Maybe; - dataProviders?: Maybe; - dataViewId?: Maybe; - dateRange?: Maybe; - description?: Maybe; - eqlOptions?: Maybe; - eventIdToNoteIds?: Maybe; - eventType?: Maybe; - excludedRowRendererIds?: Maybe; - favorite?: Maybe; - filters?: Maybe; - kqlMode?: Maybe; - kqlQuery?: Maybe; - indexNames?: Maybe; - notes?: Maybe; - noteIds?: Maybe; - pinnedEventIds?: Maybe; - pinnedEventsSaveObject?: Maybe; - savedQueryId?: Maybe; - savedObjectId: string; - sort?: Maybe; - status?: Maybe; - title?: Maybe; - templateTimelineId?: Maybe; - templateTimelineVersion?: Maybe; - timelineType?: Maybe; - updated?: Maybe; - updatedBy?: Maybe; - version: string; - savedSearchId?: Maybe; -} - -export interface ResponseTimeline { - code?: Maybe; - message?: Maybe; - timeline: TimelineResult; -} - export interface SortTimeline { sortField: SortFieldTimeline; - sortOrder: Direction; -} - -export interface GetAllTimelineVariables { - pageInfo: PageInfoTimeline; - search?: Maybe; - sort?: Maybe; - onlyUserFavorite?: Maybe; - timelineType?: Maybe; - status?: Maybe; + sortOrder: SortDirection; } diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts index 990b19d6f3bab..93d627f53263b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts @@ -42,24 +42,24 @@ export const TemplateTimelineTypeEnum = TemplateTimelineType.enum; export type ColumnHeaderResult = z.infer; export const ColumnHeaderResult = z.object({ - aggregatable: z.boolean().optional(), - category: z.string().optional(), - columnHeaderType: z.string().optional(), - description: z.string().optional(), - example: z.union([z.string(), z.number()]).optional(), - indexes: z.array(z.string()).optional(), - id: z.string().optional(), - name: z.string().optional(), - placeholder: z.string().optional(), - searchable: z.boolean().optional(), - type: z.string().optional(), + aggregatable: z.boolean().nullable().optional(), + category: z.string().nullable().optional(), + columnHeaderType: z.string().nullable().optional(), + description: z.string().nullable().optional(), + example: z.string().nullable().optional(), + indexes: z.array(z.string()).nullable().optional(), + id: z.string().nullable().optional(), + name: z.string().nullable().optional(), + placeholder: z.string().nullable().optional(), + searchable: z.boolean().nullable().optional(), + type: z.string().nullable().optional(), }); export type QueryMatchResult = z.infer; export const QueryMatchResult = z.object({ field: z.string().nullable().optional(), displayField: z.string().nullable().optional(), - value: z.string().nullable().optional(), + value: z.union([z.string().nullable(), z.array(z.string()).nullable()]).optional(), displayValue: z.string().nullable().optional(), operator: z.string().nullable().optional(), }); @@ -71,7 +71,8 @@ export const DataProviderQueryMatch = z.object({ id: z.string().nullable().optional(), kqlQuery: z.string().nullable().optional(), name: z.string().nullable().optional(), - queryMatch: QueryMatchResult.optional(), + queryMatch: QueryMatchResult.nullable().optional(), + type: DataProviderType.nullable().optional(), }); export type DataProviderResult = z.infer; @@ -119,27 +120,28 @@ export const FavoriteTimelineResult = z.object({ export type FilterTimelineResult = z.infer; export const FilterTimelineResult = z.object({ - exists: z.boolean().optional(), + exists: z.string().nullable().optional(), meta: z .object({ - alias: z.string().optional(), - controlledBy: z.string().optional(), - disabled: z.boolean().optional(), - field: z.string().optional(), - formattedValue: z.string().optional(), - index: z.string().optional(), - key: z.string().optional(), - negate: z.boolean().optional(), - params: z.string().optional(), - type: z.string().optional(), - value: z.string().optional(), + alias: z.string().nullable().optional(), + controlledBy: z.string().nullable().optional(), + disabled: z.boolean().nullable().optional(), + field: z.string().nullable().optional(), + formattedValue: z.string().nullable().optional(), + index: z.string().nullable().optional(), + key: z.string().nullable().optional(), + negate: z.boolean().nullable().optional(), + params: z.string().nullable().optional(), + type: z.string().nullable().optional(), + value: z.string().nullable().optional(), }) + .nullable() .optional(), - match_all: z.string().optional(), - missing: z.string().optional(), - query: z.string().optional(), - range: z.string().optional(), - script: z.string().optional(), + match_all: z.string().nullable().optional(), + missing: z.string().nullable().optional(), + query: z.string().nullable().optional(), + range: z.string().nullable().optional(), + script: z.string().nullable().optional(), }); export type SerializedFilterQueryResult = z.infer; @@ -178,8 +180,8 @@ export const SavedTimeline = z.object({ dataViewId: z.string().nullable().optional(), dateRange: z .object({ - end: z.union([z.string(), z.number()]).optional(), - start: z.union([z.string(), z.number()]).optional(), + end: z.union([z.string().nullable(), z.number().nullable()]).optional(), + start: z.union([z.string().nullable(), z.number().nullable()]).optional(), }) .nullable() .optional(), @@ -213,6 +215,14 @@ export const SavedTimeline = z.object({ updatedBy: z.string().nullable().optional(), }); +export type SavedTimelineWithSavedObjectId = z.infer; +export const SavedTimelineWithSavedObjectId = SavedTimeline.merge( + z.object({ + savedObjectId: z.string(), + version: z.string(), + }) +); + export type BareNote = z.infer; export const BareNote = z.object({ eventId: z.string().nullable().optional(), @@ -251,18 +261,50 @@ export const PinnedEvent = BarePinnedEvent.merge( ); export type TimelineResponse = z.infer; -export const TimelineResponse = SavedTimeline.merge( +export const TimelineResponse = SavedTimeline.merge(SavedTimelineWithSavedObjectId).merge( + z.object({ + eventIdToNoteIds: z.array(Note).nullable().optional(), + notes: z.array(Note).nullable().optional(), + noteIds: z.array(z.string()).nullable().optional(), + pinnedEventIds: z.array(z.string()).nullable().optional(), + pinnedEventsSaveObject: z.array(PinnedEvent).nullable().optional(), + }) +); + +export type TimelineSavedToReturnObject = z.infer; +export const TimelineSavedToReturnObject = SavedTimeline.merge( z.object({ - eventIdToNoteIds: z.array(Note).optional(), - notes: z.array(Note).optional(), - noteIds: z.array(z.string()).optional(), - pinnedEventIds: z.array(z.string()).optional(), - pinnedEventsSaveObject: z.array(PinnedEvent).optional(), savedObjectId: z.string(), version: z.string(), + eventIdToNoteIds: z.array(Note).nullable().optional(), + notes: z.array(Note).nullable().optional(), + noteIds: z.array(z.string()).nullable().optional(), + pinnedEventIds: z.array(z.string()).nullable().optional(), + pinnedEventsSaveObject: z.array(PinnedEvent).nullable().optional(), }) ); +export type SavedObjectResolveOutcome = z.infer; +export const SavedObjectResolveOutcome = z.enum(['exactMatch', 'aliasMatch', 'conflict']); +export type SavedObjectResolveOutcomeEnum = typeof SavedObjectResolveOutcome.enum; +export const SavedObjectResolveOutcomeEnum = SavedObjectResolveOutcome.enum; + +export type SavedObjectResolveAliasPurpose = z.infer; +export const SavedObjectResolveAliasPurpose = z.enum([ + 'savedObjectConversion', + 'savedObjectImport', +]); +export type SavedObjectResolveAliasPurposeEnum = typeof SavedObjectResolveAliasPurpose.enum; +export const SavedObjectResolveAliasPurposeEnum = SavedObjectResolveAliasPurpose.enum; + +export type ResolvedTimeline = z.infer; +export const ResolvedTimeline = z.object({ + timeline: TimelineSavedToReturnObject, + outcome: SavedObjectResolveOutcome, + alias_target_id: z.string().optional(), + alias_purpose: SavedObjectResolveAliasPurpose.optional(), +}); + export type FavoriteTimelineResponse = z.infer; export const FavoriteTimelineResponse = z.object({ savedObjectId: z.string(), @@ -275,6 +317,15 @@ export const FavoriteTimelineResponse = z.object({ favorite: z.array(FavoriteTimelineResult).optional(), }); +export type PersistTimelineResponse = z.infer; +export const PersistTimelineResponse = z.object({ + data: z.object({ + persistTimeline: z.object({ + timeline: TimelineResponse, + }), + }), +}); + export type BareNoteWithoutExternalRefs = z.infer; export const BareNoteWithoutExternalRefs = z.object({ eventId: z.string().nullable().optional(), @@ -306,6 +357,11 @@ export const SortFieldTimeline = z.enum(['title', 'description', 'updated', 'cre export type SortFieldTimelineEnum = typeof SortFieldTimeline.enum; export const SortFieldTimelineEnum = SortFieldTimeline.enum; +export type SortDirection = z.infer; +export const SortDirection = z.enum(['asc', 'desc']); +export type SortDirectionEnum = typeof SortDirection.enum; +export const SortDirectionEnum = SortDirection.enum; + /** * The status of the timeline. Valid values are `active`, `draft`, and `immutable`. */ @@ -317,11 +373,11 @@ export const TimelineStatusEnum = TimelineStatus.enum; export type ImportTimelines = z.infer; export const ImportTimelines = SavedTimeline.merge( z.object({ - savedObjectId: z.string().nullable().optional(), - version: z.string().nullable().optional(), - globalNotes: z.array(BareNote).nullable().optional(), - eventNotes: z.array(BareNote).nullable().optional(), - pinnedEventIds: z.array(z.string()).nullable().optional(), + savedObjectId: z.string().nullable(), + version: z.string().nullable(), + pinnedEventIds: z.array(z.string()).nullable(), + eventNotes: z.array(BareNote).nullable(), + globalNotes: z.array(BareNote).nullable(), }) ); @@ -346,24 +402,14 @@ export const ImportTimelineResult = z.object({ .optional(), }); -export type ExportedTimelines = z.infer; -export const ExportedTimelines = SavedTimeline.merge( +export type TimelineErrorResponse = z.infer; +export const TimelineErrorResponse = z.union([ z.object({ - globalNotes: z.array(Note).optional(), - eventNotes: z.array(Note).optional(), - pinnedEventIds: z.array(z.string()).optional(), - }) -); - -export type Readable = z.infer; -export const Readable = z.object({ - _maxListeners: z.object({}).catchall(z.unknown()).optional(), - _readableState: z.object({}).catchall(z.unknown()).optional(), - _read: z.object({}).catchall(z.unknown()).optional(), - readable: z.boolean().optional(), - _events: z.object({}).catchall(z.unknown()).optional(), - _eventsCount: z.number().optional(), - _data: z.object({}).catchall(z.unknown()).optional(), - _position: z.number().optional(), - _encoding: z.string().optional(), -}); + message: z.string(), + status_code: z.number(), + }), + z.object({ + message: z.string(), + statusCode: z.number(), + }), +]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml index c8ba2e6019f16..568eec1975769 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml @@ -56,11 +56,15 @@ components: end: oneOf: - type: string + nullable: true - type: number + nullable: true start: oneOf: - type: string + nullable: true - type: number + nullable: true description: type: string nullable: true @@ -149,43 +153,73 @@ components: updatedBy: type: string nullable: true + SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + required: [savedObjectId, version] + properties: + savedObjectId: + type: string + version: + type: string TimelineResponse: allOf: - $ref: '#/components/schemas/SavedTimeline' + - $ref: '#/components/schemas/SavedTimelineWithSavedObjectId' - type: object - required: - - savedObjectId - - version properties: eventIdToNoteIds: type: array + nullable: true items: $ref: '#/components/schemas/Note' notes: type: array + nullable: true items: $ref: '#/components/schemas/Note' noteIds: type: array + nullable: true items: type: string pinnedEventIds: type: array + nullable: true items: type: string pinnedEventsSaveObject: type: array + nullable: true items: $ref: '#/components/schemas/PinnedEvent' - savedObjectId: - type: string - version: - type: string + ResolvedTimeline: + type: object + required: [timeline, outcome] + properties: + timeline: + $ref: '#/components/schemas/TimelineSavedToReturnObject' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + alias_target_id: + type: string + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + SavedObjectResolveOutcome: + type: string + enum: + - exactMatch + - aliasMatch + - conflict + SavedObjectResolveAliasPurpose: + type: string + enum: + - savedObjectConversion + - savedObjectImport FavoriteTimelineResponse: type: object - required: - - savedObjectId - - version + required: [savedObjectId, version] properties: savedObjectId: type: string @@ -209,35 +243,58 @@ components: type: array items: $ref: '#/components/schemas/FavoriteTimelineResult' + PersistTimelineResponse: + type: object + required: [data] + properties: + data: + type: object + required: [persistTimeline] + properties: + persistTimeline: + type: object + required: [timeline] + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' ColumnHeaderResult: type: object properties: aggregatable: type: boolean + nullable: true category: type: string + nullable: true columnHeaderType: type: string + nullable: true description: type: string + nullable: true example: - oneOf: - - type: string - - type: number + type: string + nullable: true indexes: type: array + nullable: true items: type: string id: type: string + nullable: true name: type: string + nullable: true placeholder: type: string + nullable: true searchable: type: boolean + nullable: true type: type: string + nullable: true QueryMatchResult: type: object properties: @@ -248,8 +305,13 @@ components: type: string nullable: true value: - type: string - nullable: true + oneOf: + - type: string + nullable: true + - type: array + nullable: true + items: + type: string displayValue: type: string nullable: true @@ -305,6 +367,10 @@ components: nullable: true queryMatch: $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true BareNoteWithoutExternalRefs: type: object properties: @@ -419,42 +485,60 @@ components: type: object properties: exists: - type: boolean + type: string + nullable: true meta: type: object + nullable: true properties: alias: type: string + nullable: true controlledBy: type: string + nullable: true disabled: type: boolean + nullable: true field: type: string + nullable: true formattedValue: type: string + nullable: true index: type: string + nullable: true key: type: string + nullable: true negate: type: boolean + nullable: true params: type: string + nullable: true type: type: string + nullable: true value: type: string + nullable: true match_all: type: string + nullable: true missing: type: string + nullable: true query: type: string + nullable: true range: type: string + nullable: true script: type: string + nullable: true SerializedFilterQueryResult: type: object properties: @@ -531,6 +615,11 @@ components: - description - updated - created + SortDirection: + type: string + enum: + - asc + - desc TimelineStatus: type: string enum: @@ -544,6 +633,7 @@ components: allOf: - $ref: '#/components/schemas/SavedTimeline' - type: object + required: [savedObjectId, version, pinnedEventIds, eventNotes, globalNotes] properties: savedObjectId: type: string @@ -551,21 +641,56 @@ components: version: type: string nullable: true - globalNotes: - nullable: true + pinnedEventIds: type: array + nullable: true items: - $ref: '#/components/schemas/BareNote' + type: string eventNotes: + type: array nullable: true + items: + $ref: '#/components/schemas/BareNote' + globalNotes: type: array + nullable: true items: $ref: '#/components/schemas/BareNote' - pinnedEventIds: + TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + required: [savedObjectId, version] + properties: + savedObjectId: + type: string + version: + type: string + eventIdToNoteIds: + type: array nullable: true + items: + $ref: '#/components/schemas/Note' + notes: + type: array + nullable: true + items: + $ref: '#/components/schemas/Note' + noteIds: + type: array + nullable: true + items: + type: string + pinnedEventIds: type: array + nullable: true items: type: string + pinnedEventsSaveObject: + type: array + nullable: true + items: + $ref: '#/components/schemas/PinnedEvent' ImportTimelineResult: type: object properties: @@ -591,46 +716,19 @@ components: type: string status_code: type: number - ExportedTimelines: - allOf: - - $ref: '#/components/schemas/SavedTimeline' + TimelineErrorResponse: + oneOf: - type: object + required: [message, status_code] properties: - globalNotes: - type: array - items: - $ref: '#/components/schemas/Note' - eventNotes: - type: array - items: - $ref: '#/components/schemas/Note' - pinnedEventIds: - type: array - items: - type: string - Readable: - type: object - properties: - _maxListeners: - type: object - additionalProperties: true - _readableState: - type: object - additionalProperties: true - _read: - type: object - additionalProperties: true - readable: - type: boolean - _events: - type: object - additionalProperties: true - _eventsCount: - type: number - _data: - type: object - additionalProperties: true - _position: - type: number - _encoding: - type: string + message: + type: string + status_code: + type: number + - type: object + required: [message, statusCode] + properties: + message: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts index d0f2befe6b338..622c56312e21a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { SavedTimeline, TimelineResponse } from '../model/components.gen'; +import { SavedTimeline, PersistTimelineResponse } from '../model/components.gen'; export type PatchTimelineRequestBody = z.infer; export const PatchTimelineRequestBody = z.object({ @@ -27,10 +27,4 @@ export const PatchTimelineRequestBody = z.object({ export type PatchTimelineRequestBodyInput = z.input; export type PatchTimelineResponse = z.infer; -export const PatchTimelineResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const PatchTimelineResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml index 31ff7d8312673..6b1c05d32995c 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Patch Timeline API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: patch: @@ -42,19 +35,7 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '405': description: Indicates that the user does not have the required access to create a draft Timeline. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts deleted file mode 100644 index 149dc24480d2d..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts +++ /dev/null @@ -1,24 +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 type { ResponseTimeline } from '../model/api'; -import { SavedTimelineRuntimeType } from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -export const patchTimelineSchema = rt.type({ - timeline: SavedTimelineRuntimeType, - timelineId: unionWithNullType(rt.string), - version: unionWithNullType(rt.string), -}); - -export interface PatchTimelinesResponse { - data: { - persistTimeline: ResponseTimeline; - }; -} diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml index 9ad4d03901d70..3a57dd066d3b3 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Favorite API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_favorite: patch: diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml index 4ca14d2b15b13..640e75171c613 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-update.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/note: patch: diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml index 4ef83ebe04183..3b697e957ad89 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/_pin_an_event_to_an_existing_timeline.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/pinned_event: patch: diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts deleted file mode 100644 index 31c3233e9b8ca..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts +++ /dev/null @@ -1,34 +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'; - -/* - * Pinned Event Types - * TODO: remove these when the timeline types are moved to zod - */ -const BarePinnedEventType = runtimeTypes.intersection([ - runtimeTypes.type({ - timelineId: runtimeTypes.string, - eventId: runtimeTypes.string, - }), - runtimeTypes.partial({ - created: unionWithNullType(runtimeTypes.number), - createdBy: unionWithNullType(runtimeTypes.string), - updated: unionWithNullType(runtimeTypes.number), - updatedBy: unionWithNullType(runtimeTypes.string), - }), -]); - -export const PinnedEventRuntimeType = runtimeTypes.intersection([ - runtimeTypes.type({ - pinnedEventId: runtimeTypes.string, - version: runtimeTypes.string, - }), - BarePinnedEventType, -]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts index 2c3b966bfb1b4..d4c79eec50b26 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { TimelineResponse } from '../model/components.gen'; +import { ResolvedTimeline } from '../model/components.gen'; export type ResolveTimelineRequestQuery = z.infer; export const ResolveTimelineRequestQuery = z.object({ @@ -32,8 +32,9 @@ export const ResolveTimelineRequestQuery = z.object({ export type ResolveTimelineRequestQueryInput = z.input; export type ResolveTimelineResponse = z.infer; -export const ResolveTimelineResponse = z.object({ - data: z.object({ - getOneTimeline: TimelineResponse.nullable(), +export const ResolveTimelineResponse = z.union([ + z.object({ + data: ResolvedTimeline, }), -}); + z.object({}).strict(), +]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml index ee26ac4a97896..b06969e28cad4 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Resolve Timeline API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/resolve: get: @@ -35,16 +28,15 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [getOneTimeline] + oneOf: + - type: object + required: [data] properties: - getOneTimeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - nullable: true + data: + $ref: '../model/components.schema.yaml#/components/schemas/ResolvedTimeline' + - type: object + additionalProperties: false + '400': description: The request is missing parameters '404': diff --git a/x-pack/plugins/security_solution/common/api/timeline/routes.ts b/x-pack/plugins/security_solution/common/api/timeline/routes.ts index 9d3aec839a5c1..70b339c92f197 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/routes.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/routes.ts @@ -17,7 +17,10 @@ export { } 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 { + CleanDraftTimelinesResponse, + CleanDraftTimelinesRequestBody, +} from './clean_draft_timelines/clean_draft_timelines_route.gen'; export { ExportTimelinesRequestQuery, @@ -40,3 +43,48 @@ export { GetNotesResponse, GetNotesResult, } from './get_notes/get_notes_route.gen'; + +export { + CopyTimelineRequestBody, + CopyTimelineResponse, +} from './copy_timeline/copy_timeline_route.gen'; + +export { + CreateTimelinesRequestBody, + CreateTimelinesResponse, +} from './create_timelines/create_timelines_route.gen'; + +export { + PatchTimelineRequestBody, + PatchTimelineResponse, +} from './patch_timelines/patch_timeline_route.gen'; + +export { + ImportTimelinesRequestBody, + ImportTimelinesResponse, +} from './import_timelines/import_timelines_route.gen'; + +export { + InstallPrepackedTimelinesRequestBody, + InstallPrepackedTimelinesResponse, +} from './install_prepackaged_timelines/install_prepackaged_timelines_route.gen'; + +export { + GetDraftTimelinesRequestQuery, + GetDraftTimelinesResponse, +} from './get_draft_timelines/get_draft_timelines_route.gen'; + +export { + ResolveTimelineRequestQuery, + ResolveTimelineResponse, +} from './resolve_timeline/resolve_timeline_route.gen'; + +export { + GetTimelineRequestQuery, + GetTimelineResponse, +} from './get_timeline/get_timeline_route.gen'; + +export { + GetTimelinesRequestQuery, + GetTimelinesResponse, +} from './get_timelines/get_timelines_route.gen'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index ff9613503e8e3..7fe70febe4e2c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -5,13 +5,7 @@ * 2.0. */ -import type { SortField, Maybe } from '../common'; -import type { - DataProviderType, - TimelineType, - TimelineStatus, - RowRendererId, -} from '../../api/timeline'; +import type { SortField } from '../common'; export * from './events'; @@ -20,122 +14,6 @@ export interface TimelineRequestSortField extends SortField; - category?: Maybe; - columnHeaderType?: Maybe; - description?: Maybe; - example?: Maybe; - indexes?: Maybe; - id?: Maybe; - name?: Maybe; - placeholder?: Maybe; - searchable?: Maybe; - type?: Maybe; -} - -export interface QueryMatchInput { - field?: Maybe; - - displayField?: Maybe; - - value?: Maybe; - - displayValue?: Maybe; - - operator?: Maybe; -} - -export interface DataProviderInput { - id?: Maybe; - name?: Maybe; - enabled?: Maybe; - excluded?: Maybe; - kqlQuery?: Maybe; - queryMatch?: Maybe; - and?: Maybe; - type?: Maybe; -} - -export interface EqlOptionsInput { - eventCategoryField?: Maybe; - tiebreakerField?: Maybe; - timestampField?: Maybe; - query?: Maybe; - size?: Maybe; -} - -export interface FilterMetaTimelineInput { - alias?: Maybe; - controlledBy?: Maybe; - disabled?: Maybe; - field?: Maybe; - formattedValue?: Maybe; - index?: Maybe; - key?: Maybe; - negate?: Maybe; - params?: Maybe; - type?: Maybe; - value?: Maybe; -} - -export interface FilterTimelineInput { - exists?: Maybe; - meta?: Maybe; - match_all?: Maybe; - missing?: Maybe; - query?: Maybe; - range?: Maybe; - script?: Maybe; -} - -export interface SerializedFilterQueryInput { - filterQuery?: Maybe; -} - -export interface SerializedKueryQueryInput { - kuery?: Maybe; - serializedQuery?: Maybe; -} - -export interface KueryFilterQueryInput { - kind?: Maybe; - expression?: Maybe; -} - -export interface DateRangePickerInput { - start?: Maybe; - end?: Maybe; -} - -export interface SortTimelineInput { - columnId?: Maybe; - sortDirection?: Maybe; -} - -export interface TimelineInput { - columns?: Maybe; - dataProviders?: Maybe; - dataViewId?: Maybe; - description?: Maybe; - eqlOptions?: Maybe; - eventType?: Maybe; - excludedRowRendererIds?: Maybe; - filters?: Maybe; - kqlMode?: Maybe; - kqlQuery?: Maybe; - indexNames?: Maybe; - title?: Maybe; - templateTimelineId?: Maybe; - templateTimelineVersion?: Maybe; - timelineType?: Maybe; - dateRange?: Maybe; - savedQueryId?: Maybe; - sort?: Maybe; - status?: Maybe; - savedSearchId: Maybe; -} - export enum FlowDirection { uniDirectional = 'uniDirectional', biDirectional = 'biDirectional', diff --git a/x-pack/plugins/security_solution/common/timelines/zod_errors.ts b/x-pack/plugins/security_solution/common/timelines/zod_errors.ts new file mode 100644 index 0000000000000..7ef5606a4dcfb --- /dev/null +++ b/x-pack/plugins/security_solution/common/timelines/zod_errors.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ZodError, ZodType } from '@kbn/zod'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import { type Either, fold, left, right } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; + +type ErrorFactory = (message: string) => Error; + +const throwErrors = (createError: ErrorFactory) => (errors: ZodError) => { + throw createError(stringifyZodError(errors)); +}; + +const parseRuntimeType = + (zodType: ZodType) => + (v: unknown): Either, T> => { + const result = zodType.safeParse(v); + return result.success ? right(result.data) : left(result.error); + }; + +export const parseOrThrowErrorFactory = + (createError: ErrorFactory) => (runtimeType: ZodType) => (inputValue: unknown) => + pipe(parseRuntimeType(runtimeType)(inputValue), fold(throwErrors(createError), identity)); diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 2adc4e7009b1d..e36af57a2b3e9 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -268,18 +268,20 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -316,22 +318,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -393,20 +380,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates the Timeline was successfully created. '405': content: @@ -423,6 +397,36 @@ paths: tags: - Security Timeline API - access:securitySolution + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PersistTimelineResponse' + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - access:securitySolution /api/timeline/_draft: get: description: >- @@ -441,22 +445,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates that the draft Timeline was successfully retrieved. '403': content: @@ -518,22 +507,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -688,28 +662,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The Timelines to import as a readable stream. required: true responses: @@ -717,12 +677,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the import of Timelines was successful. '400': content: @@ -781,7 +736,8 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/SavedTimeline' + $ref: '#/components/schemas/TimelineSavedToReturnObject' + nullable: true type: array timelinesToInstall: items: @@ -804,12 +760,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the installation of prepackaged Timelines was successful. '500': content: @@ -847,18 +798,15 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + $ref: '#/components/schemas/ResolvedTimeline' required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -927,35 +875,25 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: '#/components/schemas/TimelineResponse' - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: '#/components/schemas/TimelineResponse' + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) Timelines were found and returned. '400': content: @@ -1025,30 +963,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string DataProviderQueryMatch: type: object @@ -1070,6 +1017,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true DataProviderResult: type: object properties: @@ -1157,41 +1108,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string GetNotesResult: type: object @@ -1256,6 +1225,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Note: allOf: - $ref: '#/components/schemas/BareNote' @@ -1275,6 +1250,23 @@ components: - $ref: '#/components/schemas/PinnedEventBaseResponseBody' - nullable: true type: object + PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data PinnedEvent: allOf: - $ref: '#/components/schemas/BarePinnedEvent' @@ -1312,34 +1304,27 @@ components: nullable: true type: string value: - nullable: true - type: string - Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + timeline: + $ref: '#/components/schemas/TimelineSavedToReturnObject' + required: + - timeline + - outcome ResponseNote: type: object properties: @@ -1374,6 +1359,17 @@ components: - threat_match - zeek type: string + SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string SavedTimeline: type: object properties: @@ -1402,12 +1398,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -1496,6 +1496,18 @@ components: updatedBy: nullable: true type: string + SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version SerializedFilterQueryResult: type: object properties: @@ -1545,27 +1557,63 @@ components: TimelineResponse: allOf: - $ref: '#/components/schemas/SavedTimeline' + - $ref: '#/components/schemas/SavedTimelineWithSavedObjectId' - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/PinnedEvent' + nullable: true + type: array + TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 633a0fd041969..818b83d7b85dd 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -268,18 +268,20 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -316,22 +318,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -393,20 +380,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates the Timeline was successfully created. '405': content: @@ -423,6 +397,36 @@ paths: tags: - Security Timeline API - access:securitySolution + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PersistTimelineResponse' + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - access:securitySolution /api/timeline/_draft: get: description: >- @@ -441,22 +445,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates that the draft Timeline was successfully retrieved. '403': content: @@ -518,22 +507,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft Timeline was successfully created. In the event the user already has a draft Timeline, the existing draft @@ -688,28 +662,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The Timelines to import as a readable stream. required: true responses: @@ -717,12 +677,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the import of Timelines was successful. '400': content: @@ -781,7 +736,8 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/SavedTimeline' + $ref: '#/components/schemas/TimelineSavedToReturnObject' + nullable: true type: array timelinesToInstall: items: @@ -804,12 +760,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the installation of prepackaged Timelines was successful. '500': content: @@ -847,18 +798,15 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + $ref: '#/components/schemas/ResolvedTimeline' required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -927,35 +875,25 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: '#/components/schemas/TimelineResponse' - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: '#/components/schemas/TimelineResponse' + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) Timelines were found and returned. '400': content: @@ -1025,30 +963,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string DataProviderQueryMatch: type: object @@ -1070,6 +1017,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true DataProviderResult: type: object properties: @@ -1157,41 +1108,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string GetNotesResult: type: object @@ -1256,6 +1225,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Note: allOf: - $ref: '#/components/schemas/BareNote' @@ -1275,6 +1250,23 @@ components: - $ref: '#/components/schemas/PinnedEventBaseResponseBody' - nullable: true type: object + PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data PinnedEvent: allOf: - $ref: '#/components/schemas/BarePinnedEvent' @@ -1312,34 +1304,27 @@ components: nullable: true type: string value: - nullable: true - type: string - Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + timeline: + $ref: '#/components/schemas/TimelineSavedToReturnObject' + required: + - timeline + - outcome ResponseNote: type: object properties: @@ -1374,6 +1359,17 @@ components: - threat_match - zeek type: string + SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string SavedTimeline: type: object properties: @@ -1402,12 +1398,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -1496,6 +1496,18 @@ components: updatedBy: nullable: true type: string + SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version SerializedFilterQueryResult: type: object properties: @@ -1545,27 +1557,63 @@ components: TimelineResponse: allOf: - $ref: '#/components/schemas/SavedTimeline' + - $ref: '#/components/schemas/SavedTimelineWithSavedObjectId' - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/PinnedEvent' + nullable: true + type: array + TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 427285c5f9233..d32891333c7f0 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -9,9 +9,10 @@ import { FilterStateStore } from '@kbn/es-query'; import type { DataTableModel } from '@kbn/securitysolution-data-table'; import { VIEW_SELECTION } from '../../../common/constants'; -import type { TimelineResult } from '../../../common/api/timeline'; import { TimelineId, TimelineTabs } from '../../../common/types/timeline'; +import type { TimelineResponse } from '../../../common/api/timeline'; import { + type ColumnHeaderResult, RowRendererIdEnum, TimelineTypeEnum, TimelineStatusEnum, @@ -1986,9 +1987,11 @@ export const mockDataTableModel: DataTableModel = { }, }; -export const mockGetOneTimelineResult: TimelineResult = { +export const mockGetOneTimelineResult: TimelineResponse = { savedObjectId: 'ef579e40-jibber-jabber', - columns: timelineDefaults.columns.filter((column) => column.id !== 'event.action'), + columns: timelineDefaults.columns.filter( + (column) => column.id !== 'event.action' + ) as ColumnHeaderResult[], dateRange: { start: '2020-03-18T13:46:38.929Z', end: '2020-03-18T13:52:38.929Z' }, description: 'This is a sample rule description', eventType: 'all', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index c83fa2bf22211..a2dfef2c43e9f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -50,8 +50,8 @@ import { isNewTermsRule, isThresholdRule, } from '../../../../common/detection_engine/utils'; -import type { TimelineResult } from '../../../../common/api/timeline'; import { TimelineId } from '../../../../common/types/timeline'; +import type { TimelineResponse } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import type { SendAlertToTimelineActionProps, @@ -69,7 +69,7 @@ import { TimelineEventsQueries } from '../../../../common/search_strategy/timeli import { timelineDefaults } from '../../../timelines/store/defaults'; import { omitTypenameInTimeline, - formatTimelineResultToModel, + formatTimelineResponseToModel, } from '../../../timelines/components/open_timeline/helpers'; import { convertKueryToElasticSearchQuery } from '../../../common/lib/kuery'; import { getField, getFieldKey } from '../../../helpers'; @@ -983,11 +983,15 @@ export const sendAlertToTimelineAction = async ({ ), ]); - const resultingTimeline: TimelineResult = getOr({}, 'data.getOneTimeline', responseTimeline); + const resultingTimeline: TimelineResponse = getOr( + {}, + 'data.getOneTimeline', + responseTimeline + ); const eventData: TimelineEventsDetailsItem[] = eventDataResp.data ?? []; if (!isEmpty(resultingTimeline)) { - const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); - const { timeline, notes } = formatTimelineResultToModel( + const timelineTemplate = omitTypenameInTimeline(resultingTimeline); + const { timeline, notes } = formatTimelineResponseToModel( timelineTemplate, true, timelineTemplate.timelineType ?? TimelineTypeEnum.default diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 397e632a5c04c..a3428ae6f2e1d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -20,12 +20,16 @@ import { isUntitled, omitTypenameInTimeline, useQueryTimelineById, - formatTimelineResultToModel, + formatTimelineResponseToModel, } from './helpers'; import type { OpenTimelineResult } from './types'; import { TimelineId } from '../../../../common/types/timeline'; -import type { RowRendererId } from '../../../../common/api/timeline'; -import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; +import { + TimelineTypeEnum, + TimelineStatusEnum, + type ColumnHeaderResult, + type RowRendererId, +} from '../../../../common/api/timeline'; import { mockTimeline as mockSelectedTimeline, mockTemplate as mockSelectedTemplate, @@ -379,7 +383,7 @@ describe('helpers', () => { ); const timeline = { savedObjectId: 'savedObject-1', - columns: columnsWithoutEventAction, + columns: columnsWithoutEventAction as ColumnHeaderResult[], version: '1', }; @@ -396,7 +400,7 @@ describe('helpers', () => { ); const timeline = { savedObjectId: 'savedObject-1', - columns: columnsWithoutEventAction, + columns: columnsWithoutEventAction as ColumnHeaderResult[], filters: [ { meta: { @@ -568,7 +572,7 @@ describe('helpers', () => { version: '1', status: TimelineStatusEnum.active, timelineType: TimelineTypeEnum.default, - columns: customColumns, + columns: customColumns as ColumnHeaderResult[], }; const newTimeline = defaultTimelineToTimelineModel( @@ -691,7 +695,7 @@ describe('helpers', () => { }); test('Do not override daterange if TimelineStatus is active', () => { - const { timeline } = formatTimelineResultToModel( + const { timeline } = formatTimelineResponseToModel( omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType @@ -744,7 +748,7 @@ describe('helpers', () => { }); test('should not override daterange if TimelineStatus is active', () => { - const { timeline } = formatTimelineResultToModel( + const { timeline } = formatTimelineResponseToModel( omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType @@ -818,7 +822,7 @@ describe('helpers', () => { }); test('override daterange if TimelineStatus is immutable', () => { - const { timeline } = formatTimelineResultToModel( + const { timeline } = formatTimelineResponseToModel( omitTypenameInTimeline(getOr({}, 'data.timeline', template)), args.duplicate, args.timelineType diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 81114901c216d..000a7b226561e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -14,8 +14,8 @@ import { useCallback } from 'react'; import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context'; import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; import type { - TimelineResult, - SingleTimelineResolveResponse, + TimelineResponse, + ResolvedTimeline, ColumnHeaderResult, FilterTimelineResult, DataProviderResult, @@ -80,7 +80,7 @@ export const isUntitled = ({ title }: OpenTimelineResult): boolean => const omitTypename = (key: string, value: keyof TimelineModel) => key === '__typename' ? undefined : value; -export const omitTypenameInTimeline = (timeline: TimelineResult): TimelineResult => +export const omitTypenameInTimeline = (timeline: TimelineResponse): TimelineResponse => JSON.parse(JSON.stringify(timeline), omitTypename); const parseString = (params: string) => { @@ -164,7 +164,7 @@ const setPinnedEventIds = (duplicate: boolean, pinnedEventIds: string[] | null | : {}; const getTemplateTimelineId = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, targetTimelineType?: TimelineType ) => { @@ -200,7 +200,7 @@ const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) => const getDataProviders = ( duplicate: boolean, - dataProviders: TimelineResult['dataProviders'], + dataProviders: TimelineResponse['dataProviders'], timelineType?: TimelineType ) => { if (duplicate && dataProviders && timelineType === TimelineTypeEnum.default) { @@ -214,7 +214,7 @@ const getDataProviders = ( }; export const getTimelineTitle = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, timelineType?: TimelineType ) => { @@ -225,7 +225,7 @@ export const getTimelineTitle = ( }; export const getTimelineStatus = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, timelineType?: TimelineType ) => { @@ -236,7 +236,7 @@ export const getTimelineStatus = ( }; export const defaultTimelineToTimelineModel = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, timelineType?: TimelineType, unifiedComponentsInTimelineDisabled?: boolean @@ -291,8 +291,8 @@ export const defaultTimelineToTimelineModel = ( ); }; -export const formatTimelineResultToModel = ( - timelineToOpen: TimelineResult, +export const formatTimelineResponseToModel = ( + timelineToOpen: TimelineResponse, duplicate: boolean = false, timelineType?: TimelineType, unifiedComponentsInTimelineDisabled?: boolean @@ -376,12 +376,12 @@ export const useQueryTimelineById = () => { } else { return Promise.resolve(resolveTimeline(timelineId)) .then((result) => { - const data: SingleTimelineResolveResponse['data'] | null = getOr(null, 'data', result); + const data: ResolvedTimeline | null = getOr(null, 'data', result); if (!data) return; const timelineToOpen = omitTypenameInTimeline(data.timeline); - const { timeline, notes } = formatTimelineResultToModel( + const { timeline, notes } = formatTimelineResponseToModel( timelineToOpen, duplicate, timelineType, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 1a47c22d0fb61..14ddedf5b9688 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -10,7 +10,7 @@ import type { IconType } from '@elastic/eui'; import type { TimelineModel } from '../../store/model'; import type { RowRendererId, - SingleTimelineResolveResponse, + ResolvedTimeline, TimelineType, TimelineStatus, TemplateTimelineType, @@ -210,9 +210,9 @@ export interface OpenTimelineProps { } export interface ResolveTimelineConfig { - alias_target_id: SingleTimelineResolveResponse['data']['alias_target_id']; - outcome: SingleTimelineResolveResponse['data']['outcome']; - alias_purpose: SingleTimelineResolveResponse['data']['alias_purpose']; + alias_target_id: ResolvedTimeline['alias_target_id']; + outcome: ResolvedTimeline['outcome']; + alias_purpose: ResolvedTimeline['alias_purpose']; } export interface UpdateTimeline { duplicate: boolean; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx index 7a2b3eca0f74b..9db2a0b8924d9 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx @@ -21,9 +21,9 @@ import type { TimelineType, TimelineStatus, PageInfoTimeline, - TimelineResult, + TimelineResponse, SortTimeline, - GetAllTimelineVariables, + GetTimelinesRequestQuery, } from '../../../../common/api/timeline'; import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { getAllTimelines } from '../api'; @@ -59,7 +59,7 @@ export interface AllTimelinesVariables { export const ALL_TIMELINE_QUERY_ID = 'FETCH_ALL_TIMELINES'; export const getAllTimeline = memoizeOne( - (_variables: string, timelines: TimelineResult[]): OpenTimelineResult[] => + (_variables: string, timelines: TimelineResponse[]): OpenTimelineResult[] => timelines.map((timeline) => ({ created: timeline.created, description: timeline.description, @@ -132,13 +132,15 @@ export const useGetAllTimeline = (): AllTimelinesArgs => { loading: true, })); - const variables: GetAllTimelineVariables = { - onlyUserFavorite, - pageInfo, + const variables: GetTimelinesRequestQuery = { + only_user_favorite: onlyUserFavorite ? 'true' : 'false', + page_size: pageInfo.pageSize.toString(), + page_index: pageInfo.pageIndex.toString(), search, - sort, - status, - timelineType, + sort_field: sort.sortField, + sort_order: sort.sortOrder, + status: status || undefined, + timeline_type: timelineType, }; const getAllTimelineResponse = await getAllTimelines(variables, abortCtrl.signal); const totalCount = getAllTimelineResponse?.totalCount ?? 0; @@ -163,7 +165,7 @@ export const useGetAllTimeline = (): AllTimelinesArgs => { setAllTimelines({ loading: false, totalCount, - timelines: getAllTimeline(JSON.stringify(variables), timelines as TimelineResult[]), + timelines: getAllTimeline(JSON.stringify(variables), timelines as TimelineResponse[]), customTemplateTimelineCount, defaultTimelineCount, elasticTemplateTimelineCount, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index 155d95c5acef2..fa006293719d5 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -5,34 +5,30 @@ * 2.0. */ -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { pipe } from 'fp-ts/lib/pipeable'; import { isEmpty } from 'lodash'; -import { throwErrors } from '@kbn/cases-plugin/common'; import type { SavedSearch } from '@kbn/saved-search-plugin/common'; import type { - TimelineResponse, - TimelineErrorResponse, - ImportTimelineResultSchema, - AllTimelinesResponse, - SingleTimelineResponse, - SingleTimelineResolveResponse, - GetTimelinesArgs, + CleanDraftTimelinesResponse, + TimelineType, + PatchTimelineResponse, + CreateTimelinesResponse, + CopyTimelineResponse, + GetDraftTimelinesResponse, + GetTimelinesRequestQuery, + SavedTimeline, } from '../../../common/api/timeline'; import { - TimelineResponseType, + ImportTimelineResult, + TimelineErrorResponse, TimelineStatusEnum, - TimelineErrorResponseType, - importTimelineResultSchema, - allTimelinesResponse, PersistFavoriteRouteResponse, - SingleTimelineResponseType, - type TimelineType, TimelineTypeEnum, - ResolvedSingleTimelineResponseType, + GetTimelineResponse, + ResolveTimelineResponse, + GetTimelinesResponse, + PersistTimelineResponse, } from '../../../common/api/timeline'; import { TIMELINE_URL, @@ -48,15 +44,15 @@ import { import { KibanaServices } from '../../common/lib/kibana'; import { ToasterError } from '../../common/components/toasters'; +import { parseOrThrowErrorFactory } from '../../../common/timelines/zod_errors'; import type { ExportDocumentsProps, ImportDataProps, ImportDataResponse, } from '../../detection_engine/rule_management/logic'; -import type { TimelineInput } from '../../../common/search_strategy'; interface RequestPostTimeline { - timeline: TimelineInput; + timeline: SavedTimeline; signal?: AbortSignal; } @@ -68,48 +64,33 @@ interface RequestPatchTimeline extends RequestPostTimeline { type RequestPersistTimeline = RequestPostTimeline & Partial>; const createToasterPlainError = (message: string) => new ToasterError([message]); -const decodeTimelineResponse = (respTimeline?: TimelineResponse | TimelineErrorResponse) => - pipe( - TimelineResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); -const decodeSingleTimelineResponse = (respTimeline?: SingleTimelineResponse) => - pipe( - SingleTimelineResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const parseOrThrow = parseOrThrowErrorFactory(createToasterPlainError); -const decodeResolvedSingleTimelineResponse = (respTimeline?: SingleTimelineResolveResponse) => - pipe( - ResolvedSingleTimelineResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const decodeTimelineResponse = (respTimeline?: PersistTimelineResponse | TimelineErrorResponse) => + parseOrThrow(PersistTimelineResponse)(respTimeline); -const decodeAllTimelinesResponse = (respTimeline: AllTimelinesResponse) => - pipe( - allTimelinesResponse.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const decodeSingleTimelineResponse = (respTimeline?: GetTimelineResponse) => + parseOrThrow(GetTimelineResponse)(respTimeline); + +const decodeResolvedSingleTimelineResponse = (respTimeline?: ResolveTimelineResponse) => + parseOrThrow(ResolveTimelineResponse)(respTimeline); + +const decodeGetTimelinesResponse = (respTimeline: GetTimelinesResponse) => + parseOrThrow(GetTimelinesResponse)(respTimeline); const decodeTimelineErrorResponse = (respTimeline?: TimelineErrorResponse) => - pipe( - TimelineErrorResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); + parseOrThrow(TimelineErrorResponse)(respTimeline); -const decodePrepackedTimelineResponse = (respTimeline?: ImportTimelineResultSchema) => - pipe( - importTimelineResultSchema.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const decodePrepackedTimelineResponse = (respTimeline?: ImportTimelineResult) => + parseOrThrow(ImportTimelineResult)(respTimeline); const decodeResponseFavoriteTimeline = (respTimeline?: PersistFavoriteRouteResponse) => - PersistFavoriteRouteResponse.parse(respTimeline); + parseOrThrow(PersistFavoriteRouteResponse)(respTimeline); const postTimeline = async ({ timeline, -}: RequestPostTimeline): Promise => { +}: RequestPostTimeline): Promise => { let requestBody; try { requestBody = JSON.stringify({ timeline }); @@ -117,7 +98,7 @@ const postTimeline = async ({ return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`)); } - const response = await KibanaServices.get().http.post(TIMELINE_URL, { + const response = await KibanaServices.get().http.post(TIMELINE_URL, { method: 'POST', body: requestBody, version: '2023-10-31', @@ -131,7 +112,7 @@ const patchTimeline = async ({ timeline, version, savedSearch, -}: RequestPatchTimeline): Promise => { +}: RequestPatchTimeline): Promise => { let response = null; let requestBody = null; try { @@ -153,7 +134,7 @@ const patchTimeline = async ({ } try { - response = await KibanaServices.get().http.patch(TIMELINE_URL, { + response = await KibanaServices.get().http.patch(TIMELINE_URL, { method: 'PATCH', body: requestBody, version: '2023-10-31', @@ -175,7 +156,7 @@ export const copyTimeline = async ({ timelineId, timeline, savedSearch, -}: RequestPersistTimeline): Promise => { +}: RequestPersistTimeline): Promise => { let response = null; let requestBody = null; let newSavedSearchId = null; @@ -205,7 +186,7 @@ export const copyTimeline = async ({ } try { - response = await KibanaServices.get().http.post(TIMELINE_COPY_URL, { + response = await KibanaServices.get().http.post(TIMELINE_COPY_URL, { method: 'POST', body: requestBody, version: '1', @@ -224,10 +205,10 @@ export const persistTimeline = async ({ timeline, version, savedSearch, -}: RequestPersistTimeline): Promise => { +}: RequestPersistTimeline): Promise => { try { if (isEmpty(timelineId) && timeline.status === TimelineStatusEnum.draft && timeline) { - const temp: TimelineResponse | TimelineErrorResponse = await cleanDraftTimeline({ + const temp: CleanDraftTimelinesResponse | TimelineErrorResponse = await cleanDraftTimeline({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion timelineType: timeline.timelineType!, templateTimelineId: timeline.templateTimelineId ?? undefined, @@ -330,13 +311,16 @@ export const getDraftTimeline = async ({ timelineType, }: { timelineType: TimelineType; -}): Promise => { - const response = await KibanaServices.get().http.get(TIMELINE_DRAFT_URL, { - query: { - timelineType, - }, - version: '2023-10-31', - }); +}): Promise => { + const response = await KibanaServices.get().http.get( + TIMELINE_DRAFT_URL, + { + query: { + timelineType, + }, + version: '2023-10-31', + } + ); return decodeTimelineResponse(response); }; @@ -349,7 +333,7 @@ export const cleanDraftTimeline = async ({ timelineType: TimelineType; templateTimelineId?: string; templateTimelineVersion?: number; -}): Promise => { +}): Promise => { let requestBody; const templateTimelineInfo = timelineType === TimelineTypeEnum.template @@ -366,16 +350,19 @@ export const cleanDraftTimeline = async ({ } catch (err) { return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`)); } - const response = await KibanaServices.get().http.post(TIMELINE_DRAFT_URL, { - body: requestBody, - version: '2023-10-31', - }); + const response = await KibanaServices.get().http.post( + TIMELINE_DRAFT_URL, + { + body: requestBody, + version: '2023-10-31', + } + ); return decodeTimelineResponse(response); }; -export const installPrepackedTimelines = async (): Promise => { - const response = await KibanaServices.get().http.post( +export const installPrepackedTimelines = async (): Promise => { + const response = await KibanaServices.get().http.post( TIMELINE_PREPACKAGED_URL, { version: '2023-10-31', @@ -386,7 +373,7 @@ export const installPrepackedTimelines = async (): Promise { - const response = await KibanaServices.get().http.get(TIMELINE_URL, { + const response = await KibanaServices.get().http.get(TIMELINE_URL, { query: { id, }, @@ -397,7 +384,7 @@ export const getTimeline = async (id: string) => { }; export const resolveTimeline = async (id: string) => { - const response = await KibanaServices.get().http.get( + const response = await KibanaServices.get().http.get( TIMELINE_RESOLVE_URL, { query: { @@ -411,7 +398,7 @@ export const resolveTimeline = async (id: string) => { }; export const getTimelineTemplate = async (templateTimelineId: string) => { - const response = await KibanaServices.get().http.get(TIMELINE_URL, { + const response = await KibanaServices.get().http.get(TIMELINE_URL, { query: { template_timeline_id: templateTimelineId, }, @@ -421,24 +408,18 @@ export const getTimelineTemplate = async (templateTimelineId: string) => { return decodeSingleTimelineResponse(response); }; -export const getAllTimelines = async (args: GetTimelinesArgs, abortSignal: AbortSignal) => { - const response = await KibanaServices.get().http.fetch(TIMELINES_URL, { +export const getAllTimelines = async ( + query: GetTimelinesRequestQuery, + abortSignal: AbortSignal +) => { + const response = await KibanaServices.get().http.fetch(TIMELINES_URL, { method: 'GET', - query: { - ...(args.onlyUserFavorite ? { only_user_favorite: args.onlyUserFavorite } : {}), - ...(args?.pageInfo?.pageSize ? { page_size: args.pageInfo.pageSize } : {}), - ...(args?.pageInfo?.pageIndex ? { page_index: args.pageInfo.pageIndex } : {}), - ...(args.search ? { search: args.search } : {}), - ...(args?.sort?.sortField ? { sort_field: args?.sort?.sortField } : {}), - ...(args?.sort?.sortOrder ? { sort_order: args?.sort?.sortOrder } : {}), - ...(args.status ? { status: args.status } : {}), - ...(args.timelineType ? { timeline_type: args.timelineType } : {}), - }, + query, signal: abortSignal, version: '2023-10-31', }); - return decodeAllTimelinesResponse(response); + return decodeGetTimelinesResponse(response); }; export const persistFavorite = async ({ diff --git a/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts b/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts index 17d0050488f5b..fef5fa22f0bfb 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts @@ -6,10 +6,10 @@ */ import { TableId } from '@kbn/securitysolution-data-table'; -import type { TimelineResult } from '../../../common/api/timeline'; +import type { TimelineResponse } from '../../../common/api/timeline'; import { DEFAULT_ALERTS_INDEX } from '../../../common/constants'; -export const getTimelineQueryTypes = (timeline: TimelineResult) => ({ +export const getTimelineQueryTypes = (timeline: TimelineResponse) => ({ hasQuery: (timeline.kqlQuery != null && timeline.kqlQuery.filterQuery != null && diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts index 4c303b1414515..67374a66019db 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts @@ -66,6 +66,7 @@ describe('Timeline pinned event middleware', () => { data: { persistPinnedEventOnTimeline: { code: 200, + eventId: testEventId, }, }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts index 8461ed3c2fc17..38e00af3f5f8e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts @@ -74,7 +74,7 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa const currentTimeline = selectTimelineById(store.getState(), action.payload.id); // 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 || !('code' in response)) { + if (!response || !('eventId' in response)) { return store.dispatch( updateTimeline({ id: action.payload.id, diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts index 6051c9c1bb4e5..3c8bcf4b55f58 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts @@ -167,7 +167,7 @@ describe('Timeline save middleware', () => { }); it('should show an error message when the call is unauthorized', async () => { - (persistTimeline as jest.Mock).mockResolvedValue({ data: { persistTimeline: { code: 403 } } }); + (persistTimeline as jest.Mock).mockResolvedValue({ status_code: 403 }); await store.dispatch(saveTimeline({ id: TimelineId.test, saveAsNew: false })); expect(refreshTimelines as unknown as jest.Mock).not.toHaveBeenCalled(); @@ -175,7 +175,7 @@ describe('Timeline save middleware', () => { }); describe('#convertTimelineAsInput ', () => { - test('should return a TimelineInput instead of TimelineModel ', () => { + test('should return a SavedTimeline instead of TimelineModel ', () => { const columns: TimelineModel['columns'] = [ { columnHeaderType: 'not-filtered', diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts index dcbbc5cadb6d0..58e8aced4470b 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts @@ -34,8 +34,11 @@ import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; import type { inputsModel } from '../../../common/store/inputs'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; -import type { TimelineErrorResponse, TimelineResponse } from '../../../../common/api/timeline'; -import type { TimelineInput } from '../../../../common/search_strategy'; +import type { + TimelineErrorResponse, + PersistTimelineResponse, + SavedTimeline, +} from '../../../../common/api/timeline'; import type { TimelineModel } from '../model'; import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; import { refreshTimelines } from './helpers'; @@ -83,6 +86,9 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State if (isTimelineErrorResponse(result)) { const error = getErrorFromResponse(result); switch (error?.errorCode) { + case 403: + store.dispatch(showCallOutUnauthorizedMsg()); + break; // conflict case 409: kibana.notifications.toasts.addDanger({ @@ -108,11 +114,6 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State return; } - if (response && response.code === 403) { - store.dispatch(showCallOutUnauthorizedMsg()); - return; - } - refreshTimelines(store.getState()); store.dispatch( @@ -155,7 +156,7 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State return ret; }; -const timelineInput: TimelineInput = { +const timelineInput: SavedTimeline = { columns: null, dataProviders: null, dataViewId: null, @@ -181,8 +182,8 @@ const timelineInput: TimelineInput = { export const convertTimelineAsInput = ( timeline: TimelineModel, timelineTimeRange: inputsModel.TimeRange -): TimelineInput => - Object.keys(timelineInput).reduce((acc, key) => { +): SavedTimeline => + Object.keys(timelineInput).reduce((acc, key) => { if (has(key, timeline)) { if (key === 'kqlQuery') { return set(`${key}.filterQuery`, get(`${key}.filterQuery`, timeline), acc); @@ -270,7 +271,7 @@ const convertToString = (obj: unknown) => { } }; -type PossibleResponse = TimelineResponse | TimelineErrorResponse; +type PossibleResponse = PersistTimelineResponse | TimelineErrorResponse; function isTimelineErrorResponse(response: PossibleResponse): response is TimelineErrorResponse { return response && ('status_code' in response || 'statusCode' in response); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts index 6b88ec1923d0d..3713176e919c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts @@ -6,8 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { checkTimelineStatusRt } from '../../../../../../common/api/timeline'; +import { InstallPrepackedTimelinesRequestBody } from '../../../../../../common/api/timeline'; import { buildSiemResponse } from '../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; @@ -69,10 +68,8 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = (router: SecuritySolution const frameworkRequest = await buildFrameworkRequest(context, request); const prebuiltTimelineStatus = await checkTimelinesStatus(frameworkRequest); - const [validatedPrebuiltTimelineStatus] = validate( - prebuiltTimelineStatus, - checkTimelineStatusRt - ); + const validatedPrebuiltTimelineStatus = + InstallPrepackedTimelinesRequestBody.parse(prebuiltTimelineStatus); const responseBody: ReadPrebuiltRulesAndTimelinesStatusResponse = { rules_custom_installed: customRules.total, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts index 101883e8ccd9b..857c7a6b95814 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { importTimelineResultSchema } from '../../../../../common/api/timeline'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import { ImportTimelineResult } from '../../../../../common/api/timeline'; import type { SecuritySolutionApiRequestHandlerContext } from '../../../../types'; import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; @@ -18,10 +18,10 @@ export const performTimelinesInstallation = async ( securitySolutionContext.getFrameworkRequest(), true ); - const [result, error] = validate(timeline, importTimelineResultSchema); + const parsed = ImportTimelineResult.safeParse(timeline); return { - result, - error, + result: parsed.data, + error: parsed.error && stringifyZodError(parsed.error), }; }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts index eb4649f941d11..70187dc9bf40a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts @@ -6,7 +6,6 @@ */ import path, { join, resolve } from 'path'; -import type * as rt from 'io-ts'; import { TIMELINE_DRAFT_URL, @@ -17,9 +16,9 @@ import { } from '../../../../common/constants'; import type { SavedTimeline, - patchTimelineSchema, - createTimelineSchema, - GetTimelineQuery, + PatchTimelineRequestBody, + CreateTimelinesRequestBody, + GetTimelineRequestQuery, } from '../../../../common/api/timeline'; import { type TimelineType, @@ -135,14 +134,14 @@ export const updateTemplateTimelineWithTimelineId = { version: 'WzEyMjUsMV0=', }; -export const getCreateTimelinesRequest = (mockBody: rt.TypeOf) => +export const getCreateTimelinesRequest = (mockBody: CreateTimelinesRequestBody) => requestMock.create({ method: 'post', path: TIMELINE_URL, body: mockBody, }); -export const getUpdateTimelinesRequest = (mockBody: rt.TypeOf) => +export const getUpdateTimelinesRequest = (mockBody: PatchTimelineRequestBody) => requestMock.create({ method: 'patch', path: TIMELINE_URL, @@ -167,7 +166,7 @@ export const cleanDraftTimelinesRequest = (timelineType: TimelineType) => }, }); -export const getTimelineRequest = (query?: GetTimelineQuery) => +export const getTimelineRequest = (query?: GetTimelineRequestQuery) => requestMock.create({ method: 'get', path: TIMELINE_URL, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts index ea875a71416c1..932e6f3ce904d 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ResolvedTimelineWithOutcomeSavedObject } from '../../../../common/api/timeline'; +import type { ResolvedTimeline } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; export const mockResolvedSavedObject = { @@ -117,7 +117,7 @@ export const mockPopulatedTimeline = { pinnedEventsSaveObject: [], }; -export const mockResolveTimelineResponse: ResolvedTimelineWithOutcomeSavedObject = { +export const mockResolveTimelineResponse: ResolvedTimeline = { timeline: mockPopulatedTimeline, outcome: 'aliasMatch', alias_target_id: 'new-saved-object-id', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts index 387720b4a3b4f..6515817f28e11 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts @@ -6,10 +6,10 @@ */ import { v4 as uuidv4 } from 'uuid'; +import type { IKibanaResponse } from '@kbn/core-http-server'; 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'; @@ -21,12 +21,13 @@ import { persistTimeline, } from '../../../saved_object/timelines'; import { draftTimelineDefaults } from '../../../utils/default_timeline'; +import type { CleanDraftTimelinesResponse } from '../../../../../../common/api/timeline'; import { CleanDraftTimelinesRequestBody, TimelineTypeEnum, } from '../../../../../../common/api/timeline'; -export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_DRAFT_URL, @@ -42,7 +43,7 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _ }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const frameworkRequest = await buildFrameworkRequest(context, request); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts index 6619e4a0eb18b..1ba3167cdefae 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts @@ -5,19 +5,22 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; 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, persistTimeline } from '../../../saved_object/timelines'; import { draftTimelineDefaults } from '../../../utils/default_timeline'; -import { getDraftTimelineSchema } from '../../../../../../common/api/timeline'; +import { + GetDraftTimelinesRequestQuery, + type GetDraftTimelinesResponse, +} from '../../../../../../common/api/timeline'; -export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_DRAFT_URL, @@ -29,11 +32,11 @@ export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: .addVersion( { validate: { - request: { query: buildRouteValidationWithExcess(getDraftTimelineSchema) }, + request: { query: buildRouteValidationWithZod(GetDraftTimelinesRequestQuery) }, }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const frameworkRequest = await buildFrameworkRequest(context, request); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts index 6b44496b6c3c4..905e28872a5a4 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts @@ -28,25 +28,25 @@ import { persistNoteRoute, deleteNoteRoute, getNotesRoute } from './notes'; import { persistPinnedEventRoute } from './pinned_events'; export function registerTimelineRoutes(router: SecuritySolutionPluginRouter, config: ConfigType) { - createTimelinesRoute(router, config); - patchTimelinesRoute(router, config); + createTimelinesRoute(router); + patchTimelinesRoute(router); importTimelinesRoute(router, config); exportTimelinesRoute(router, config); - getDraftTimelinesRoute(router, config); - getTimelineRoute(router, config); - resolveTimelineRoute(router, config); - getTimelinesRoute(router, config); - cleanDraftTimelinesRoute(router, config); - deleteTimelinesRoute(router, config); - persistFavoriteRoute(router, config); - copyTimelineRoute(router, config); + getDraftTimelinesRoute(router); + getTimelineRoute(router); + resolveTimelineRoute(router); + getTimelinesRoute(router); + cleanDraftTimelinesRoute(router); + deleteTimelinesRoute(router); + persistFavoriteRoute(router); + copyTimelineRoute(router); installPrepackedTimelinesRoute(router, config); - persistNoteRoute(router, config); - deleteNoteRoute(router, config); - getNotesRoute(router, config); + persistNoteRoute(router); + deleteNoteRoute(router); + getNotesRoute(router); - persistPinnedEventRoute(router, config); + persistPinnedEventRoute(router); } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts index 92be926453403..9e6aeb5473fc2 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts @@ -5,21 +5,20 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; 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 type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; import { DeleteNoteRequestBody, type DeleteNoteResponse } from '../../../../../common/api/timeline'; import { deleteNote } from '../../saved_object/notes'; -export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { +export const deleteNoteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .delete({ path: NOTE_URL, @@ -35,7 +34,7 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: Co }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { @@ -55,9 +54,8 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: Co noteIds, }); - const body: DeleteNoteResponse = { data: {} }; return response.ok({ - body, + body: { data: {} }, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index 9cc8435d6aae0..920a7ef763dd5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -5,21 +5,20 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; 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'; -import type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; 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) => { +export const getNotesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: NOTE_URL, @@ -35,7 +34,7 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const queryParams = request.query; const frameworkRequest = await buildFrameworkRequest(context, request); @@ -60,8 +59,7 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp perPage: MAX_UNASSOCIATED_NOTES, }; const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); + return response.ok({ body: res ?? {} }); } } else { const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts index 7ee0dc886a787..2e825b4ff3a15 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts @@ -5,14 +5,13 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; 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 type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; @@ -22,7 +21,7 @@ import { } from '../../../../../common/api/timeline'; import { persistNote } from '../../saved_object/notes'; -export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const persistNoteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: NOTE_URL, @@ -38,7 +37,7 @@ export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: Config }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts index c1e245cda40fb..74db9e58d904b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; @@ -12,8 +13,6 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { PINNED_EVENT_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; @@ -23,10 +22,7 @@ import { } from '../../../../../common/api/timeline'; import { persistPinnedEventOnTimeline } from '../../saved_object/pinned_events'; -export const persistPinnedEventRoute = ( - router: SecuritySolutionPluginRouter, - config: ConfigType -) => { +export const persistPinnedEventRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: PINNED_EVENT_URL, @@ -42,7 +38,11 @@ export const persistPinnedEventRoute = ( }, version: '2023-10-31', }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { const siemResponse = buildSiemResponse(response); try { @@ -58,12 +58,10 @@ export const persistPinnedEventRoute = ( timelineId ); - const body: PersistPinnedEventRouteResponse = { - data: { persistPinnedEventOnTimeline: res }, - }; - return response.ok({ - body, + body: { + data: { persistPinnedEventOnTimeline: res }, + }, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts index 7a4e7a41a1ee7..6b8124f16aa91 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts @@ -20,7 +20,6 @@ import { import * as helpers from './helpers'; import { importTimelines } from '../../timelines/import_timelines/helpers'; import { buildFrameworkRequest } from '../../../utils/common'; -import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; jest.mock('../../timelines/import_timelines/helpers'); @@ -231,9 +230,8 @@ describe('installPrepackagedTimelines', () => { ); expect( - (result as ImportTimelineResultSchema).errors[0].error.message.includes( - 'read prepackaged timelines error:' - ) + 'errors' in result && + result.errors?.[0].error?.message?.includes('read prepackaged timelines error:') ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts index dee31d479e2da..f6f43435e5641 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts @@ -8,7 +8,7 @@ import path, { join, resolve } from 'path'; import { Readable } from 'stream'; -import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; +import type { ImportTimelineResult } from '../../../../../../common/api/timeline'; import type { FrameworkRequest } from '../../../../framework'; @@ -22,7 +22,7 @@ export const installPrepackagedTimelines = async ( isImmutable: boolean, filePath?: string, fileName?: string -): Promise => { +): Promise => { let readStream; const dir = resolve( join( @@ -47,7 +47,7 @@ export const installPrepackagedTimelines = async ( ], }; } - return loadData(readStream, (docs: T) => + return loadData(readStream, (docs: T) => docs instanceof Readable ? importTimelines(docs, maxTimelineImportExportSize, frameworkRequest, isImmutable) : Promise.reject(new Error(`read prepackaged timelines error`)) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts index 0b150b4e47f56..b1a6e2f781f45 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts @@ -6,14 +6,17 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { checkTimelineStatusRt } from '../../../../../../common/api/timeline'; +import type { IKibanaResponse } from '@kbn/core-http-server'; + import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_PREPACKAGED_URL } from '../../../../../../common/constants'; import type { ConfigType } from '../../../../../config'; - +import { + InstallPrepackedTimelinesRequestBody, + type InstallPrepackedTimelinesResponse, +} from '../../../../../../common/api/timeline'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { installPrepackagedTimelines } from './helpers'; @@ -45,23 +48,24 @@ export const installPrepackedTimelinesRoute = ( validate: {}, version: '2023-10-31', }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); const prepackagedTimelineStatus = await checkTimelinesStatus(frameworkRequest); - const [validatedprepackagedTimelineStatus, prepackagedTimelineStatusError] = validate( - prepackagedTimelineStatus, - checkTimelineStatusRt - ); - if (prepackagedTimelineStatusError != null) { - throw prepackagedTimelineStatusError; + const installResult = + InstallPrepackedTimelinesRequestBody.safeParse(prepackagedTimelineStatus); + + if (installResult.error) { + throw installResult.error; } - const timelinesToInstalled = - validatedprepackagedTimelineStatus?.timelinesToInstall.length ?? 0; - const timelinesNotUpdated = - validatedprepackagedTimelineStatus?.timelinesToUpdate.length ?? 0; + const timelinesToInstalled = installResult.data.timelinesToInstall.length ?? 0; + const timelinesNotUpdated = installResult.data.timelinesToUpdate.length ?? 0; let res = null; if (timelinesToInstalled > 0 || timelinesNotUpdated > 0) { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts index 5bdb4e0f93d67..e795ec89dd926 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts @@ -5,10 +5,13 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; -import type { ConfigType } from '../../../../..'; -import { copyTimelineSchema } from '../../../../../../common/api/timeline'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + CopyTimelineRequestBody, + type CopyTimelineResponse, +} from '../../../../../../common/api/timeline'; import { copyTimeline } from '../../../saved_object/timelines'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_COPY_URL } from '../../../../../../common/constants'; @@ -16,7 +19,7 @@ import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -export const copyTimelineRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const copyTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_COPY_URL, @@ -29,17 +32,16 @@ export const copyTimelineRoute = (router: SecuritySolutionPluginRouter, _: Confi { version: '1', validate: { - request: { body: buildRouteValidationWithExcess(copyTimelineSchema) }, + request: { body: buildRouteValidationWithZod(CopyTimelineRequestBody) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { const frameworkRequest = await buildFrameworkRequest(context, request); const { timeline, timelineIdToCopy } = request.body; const copiedTimeline = await copyTimeline(frameworkRequest, timeline, timelineIdToCopy); - return response.ok({ body: { data: { persistTimeline: copiedTimeline } }, }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts index 1428f09d39560..ac05fe3f880e9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts @@ -10,8 +10,9 @@ import { isEmpty } from 'lodash/fp'; import moment from 'moment'; import { timeline as timelineLib, pinnedEvent as pinnedEventLib } from '../../../saved_object'; import type { FrameworkRequest } from '../../../../framework'; -import type { ResponseTimeline, SavedTimeline, Note } from '../../../../../../common/api/timeline'; +import type { SavedTimeline, Note } from '../../../../../../common/api/timeline'; import { persistNotes } from '../../../saved_object/notes/persist_notes'; +import type { InternalTimelineResponse } from '../../../saved_object/timelines'; interface CreateTimelineProps { frameworkRequest: FrameworkRequest; @@ -21,7 +22,7 @@ interface CreateTimelineProps { overrideNotesOwner?: boolean; pinnedEventIds?: string[] | null; notes?: Note[]; - existingNoteIds?: string[]; + existingNoteIds?: string[] | null; isImmutable?: boolean; } @@ -40,7 +41,7 @@ export const createTimelines = async ({ existingNoteIds = [], isImmutable, overrideNotesOwner = true, -}: CreateTimelineProps): Promise => { +}: CreateTimelineProps): Promise => { const timerangeStart = isImmutable ? moment().subtract(24, 'hours').toISOString() : timeline.dateRange?.start; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts index 0fcf286661b96..95fb09fb28e56 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts @@ -7,16 +7,13 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { IKibanaResponse } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { createTimelineSchema } from '../../../../../../common/api/timeline'; import { buildFrameworkRequest, CompareTimelinesStatus, @@ -24,11 +21,14 @@ import { } from '../../../utils/common'; import { DEFAULT_ERROR } from '../../../utils/failure_cases'; import { createTimelines } from './helpers'; -import type { CreateTimelinesResponse } from '../../../../../../common/api/timeline'; +import { + CreateTimelinesRequestBody, + type CreateTimelinesResponse, +} from '../../../../../../common/api/timeline'; export * from './helpers'; -export const createTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const createTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_URL, @@ -42,7 +42,7 @@ export const createTimelinesRoute = (router: SecuritySolutionPluginRouter, _: Co version: '2023-10-31', validate: { request: { - body: buildRouteValidationWithExcess(createTimelineSchema), + body: buildRouteValidationWithZod(CreateTimelinesRequestBody), }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts index a11b16c96fb1f..8dd476c9f4e44 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts @@ -5,9 +5,9 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import type { ConfigType } from '../../../../..'; import { DeleteTimelinesRequestBody, type DeleteTimelinesResponse, @@ -19,7 +19,7 @@ import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; import { deleteTimeline } from '../../../saved_object/timelines'; -export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { +export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .delete({ path: TIMELINE_URL, @@ -35,7 +35,7 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter, confi request: { body: buildRouteValidationWithZod(DeleteTimelinesRequestBody) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts index 4974e6bd0edda..6e4f3d84da1ff 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts @@ -9,11 +9,11 @@ import { omit } from 'lodash/fp'; import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import type { - ExportedTimelines, ExportedNotes, ExportTimelineNotFoundError, Note, PinnedEvent, + TimelineResponse, } from '../../../../../../common/api/timeline'; import type { FrameworkRequest } from '../../../../framework'; @@ -45,7 +45,7 @@ const getPinnedEventsIdsByTimelineId = (currentPinnedEvents: PinnedEvent[]): str const getTimelinesFromObjects = async ( request: FrameworkRequest, ids?: string[] | null -): Promise> => { +): Promise> => { const { timelines, errors } = await getSelectedTimelines(request, ids); const exportedIds = timelines.map((t) => t.savedObjectId); @@ -65,7 +65,7 @@ const getTimelinesFromObjects = async ( [] ); - const myResponse = exportedIds.reduce((acc, timelineId) => { + const myResponse = exportedIds.reduce((acc, timelineId) => { const myTimeline = timelines.find((t) => t.savedObjectId === timelineId); if (myTimeline != null) { const timelineNotes = myNotes.filter((n) => n.timelineId === timelineId); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts index 78df97fa441be..781da93355442 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - serverMock, - requestContextMock, - createMockConfig, -} from '../../../../detection_engine/routes/__mocks__'; +import { serverMock, requestContextMock } from '../../../../detection_engine/routes/__mocks__'; import { getTimelineOrNull, getTimelineTemplateOrNull } from '../../../saved_object/timelines'; import { getTimelineRequest } from '../../../__mocks__/request_responses'; @@ -33,7 +29,7 @@ describe('get timeline', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - getTimelineRoute(server.router, createMockConfig()); + getTimelineRoute(server.router); }); test('should call getTimelineTemplateOrNull if templateTimelineId is given', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts index 321e6aafe4903..870955f7e8691 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts @@ -5,25 +5,24 @@ * 2.0. */ +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -import { getTimelineQuerySchema } from '../../../../../../common/api/timeline'; -import { getTimelineTemplateOrNull, getTimelineOrNull } from '../../../saved_object/timelines'; -import type { - TimelineSavedObject, - ResolvedTimelineWithOutcomeSavedObject, +import { + GetTimelineRequestQuery, + type GetTimelineResponse, } from '../../../../../../common/api/timeline'; +import { getTimelineTemplateOrNull, getTimelineOrNull } from '../../../saved_object/timelines'; +import type { ResolvedTimeline, TimelineResponse } from '../../../../../../common/api/timeline'; -export const getTimelineRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_URL, @@ -36,16 +35,16 @@ export const getTimelineRoute = (router: SecuritySolutionPluginRouter, _: Config { version: '2023-10-31', validate: { - request: { query: buildRouteValidationWithExcess(getTimelineQuerySchema) }, + request: { query: buildRouteValidationWithZod(GetTimelineRequestQuery) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); const query = request.query ?? {}; const { template_timeline_id: templateTimelineId, id } = query; - let res: TimelineSavedObject | ResolvedTimelineWithOutcomeSavedObject | null = null; + let res: TimelineResponse | ResolvedTimeline | null = null; if (templateTimelineId != null && id == null) { res = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts index b006b77c9a595..753ac1eb15aea 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - serverMock, - requestContextMock, - createMockConfig, -} from '../../../../detection_engine/routes/__mocks__'; +import { serverMock, requestContextMock } from '../../../../detection_engine/routes/__mocks__'; import { getAllTimeline } from '../../../saved_object/timelines'; import { getTimelineRequest } from '../../../__mocks__/request_responses'; import { getTimelinesRoute } from '.'; @@ -29,7 +25,7 @@ describe('get all timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - getTimelinesRoute(server.router, createMockConfig()); + getTimelinesRoute(server.router); }); test('should get the total count', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts index 97d9bf51a0876..52995efcf4be1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts @@ -5,24 +5,23 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINES_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { CustomHttpRequestError } from '../../../../../utils/custom_http_request_error'; -import { buildFrameworkRequest, escapeHatch, throwErrors } from '../../../utils/common'; +import { buildFrameworkRequest } from '../../../utils/common'; import { getAllTimeline } from '../../../saved_object/timelines'; -import { getTimelinesQuerySchema } from '../../../../../../common/api/timeline'; +import { + GetTimelinesRequestQuery, + type GetTimelinesResponse, +} from '../../../../../../common/api/timeline'; -export const getTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINES_URL, @@ -34,27 +33,23 @@ export const getTimelinesRoute = (router: SecuritySolutionPluginRouter, _: Confi .addVersion( { validate: { - request: { query: escapeHatch }, + request: { query: buildRouteValidationWithZod(GetTimelinesRequestQuery) }, }, version: '2023-10-31', }, - async (context, request, response) => { - const customHttpRequestError = (message: string) => - new CustomHttpRequestError(message, 400); + async (context, request, response): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); - const queryParams = pipe( - getTimelinesQuerySchema.decode(request.query), - fold(throwErrors(customHttpRequestError), identity) - ); - const onlyUserFavorite = queryParams?.only_user_favorite === 'true' ? true : false; - const pageSize = queryParams?.page_size ? parseInt(queryParams.page_size, 10) : null; - const pageIndex = queryParams?.page_index ? parseInt(queryParams.page_index, 10) : null; - const search = queryParams?.search ?? null; - const sortField = queryParams?.sort_field ?? null; - const sortOrder = queryParams?.sort_order ?? null; - const status = queryParams?.status ?? null; - const timelineType = queryParams?.timeline_type ?? null; + const onlyUserFavorite = request.query?.only_user_favorite === 'true'; + const pageSize = request.query?.page_size ? parseInt(request.query.page_size, 10) : null; + const pageIndex = request.query?.page_index + ? parseInt(request.query.page_index, 10) + : null; + const search = request.query?.search ?? null; + const sortField = request.query?.sort_field ?? null; + const sortOrder = request.query?.sort_order ?? null; + const status = request.query?.status ?? null; + const timelineType = request.query?.timeline_type ?? null; const sort = sortField && sortOrder ? { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts index 65f3510c75f8c..83a25ee38cd63 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts @@ -5,11 +5,7 @@ * 2.0. */ -import type * as rt from 'io-ts'; import type { Transform } from 'stream'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; import { createConcatStream, createSplitStream, createMapStream } from '@kbn/utils'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; import { @@ -19,23 +15,15 @@ import { } from '../../../../../utils/read_stream/create_stream_from_ndjson'; import type { ImportTimelineResponse } from './types'; -import { ImportTimelinesSchemaRt } from '../../../../../../common/api/timeline'; -import { throwErrors } from '../../../utils/common'; +import { ImportTimelines } from '../../../../../../common/api/timeline'; +import { parseOrThrowErrorFactory } from '../../../../../../common/timelines/zod_errors'; -type ErrorFactory = (message: string) => Error; +const createPlainError = (message: string) => new Error(message); +const parseOrThrow = parseOrThrowErrorFactory(createPlainError); -export const createPlainError = (message: string) => new Error(message); - -export const decodeOrThrow = - (runtimeType: rt.Type, createError: ErrorFactory = createPlainError) => - (inputValue: I) => - pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); - -export const validateTimelines = (): Transform => +const validateTimelines = (): Transform => createMapStream((obj: ImportTimelineResponse) => - obj instanceof Error - ? new BadRequestError(obj.message) - : decodeOrThrow(ImportTimelinesSchemaRt)(obj) + obj instanceof Error ? new BadRequestError(obj.message) : parseOrThrow(ImportTimelines)(obj) ); export const createTimelinesStreamFromNdJson = (ruleLimit: number) => { return [ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts index 6f7767247396d..5539631c66a67 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts @@ -10,12 +10,8 @@ import type { Readable } from 'stream'; import { v4 as uuidv4 } from 'uuid'; import { createPromiseFromStreams } from '@kbn/utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; -import { - importTimelineResultSchema, - TimelineStatusEnum, -} from '../../../../../../common/api/timeline'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import { ImportTimelineResult, TimelineStatusEnum } from '../../../../../../common/api/timeline'; import type { BulkError } from '../../../../detection_engine/routes/utils'; import { createBulkErrorObject } from '../../../../detection_engine/routes/utils'; @@ -88,7 +84,7 @@ export const importTimelines = async ( maxTimelineImportExportSize: number, frameworkRequest: FrameworkRequest, isImmutable?: boolean -): Promise => { +): Promise => { const readStream = createTimelinesStreamFromNdJson(maxTimelineImportExportSize); const parsedObjects = await createPromiseFromStreams([file, ...readStream]); @@ -262,17 +258,17 @@ export const importTimelines = async ( const timelinesUpdated = importTimelineResponse.filter( (resp) => isImportRegular(resp) && resp.action === 'updateViaImport' ); - const importTimelinesRes: ImportTimelineResultSchema = { + const importTimelinesRes: ImportTimelineResult = { success: errorsResp.length === 0, success_count: successes.length, errors: errorsResp, timelines_installed: timelinesInstalled.length ?? 0, timelines_updated: timelinesUpdated.length ?? 0, }; - const [validated, errors] = validate(importTimelinesRes, importTimelineResultSchema); - if (errors != null || validated == null) { - return new Error(errors || 'Import timeline error'); + const parseResult = ImportTimelineResult.safeParse(importTimelinesRes); + if (parseResult.success && parseResult.data) { + return parseResult.data; } else { - return validated; + return new Error(stringifyZodError(parseResult.error) || 'Import timeline error'); } }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts index 3415df46e9ac4..5528d92d34240 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts @@ -39,7 +39,6 @@ import { describe('import timelines', () => { let server: ReturnType; - let request: ReturnType; let securitySetup: SecurityPluginSetup; let { context } = requestContextMock.createTools(); let mockGetTimeline: jest.Mock; @@ -452,48 +451,6 @@ describe('import timelines', () => { }); }); }); - - describe('request validation', () => { - beforeEach(() => { - jest.doMock('../../../saved_object/timelines', () => { - return { - getTimelineOrNull: mockGetTimeline.mockReturnValue(null), - persistTimeline: mockPersistTimeline.mockReturnValue({ - timeline: { savedObjectId: '79deb4c0-6bc1-11ea-9999-f5341fb7a189' }, - }), - }; - }); - - jest.doMock('../../../saved_object/pinned_events', () => { - return { - savePinnedEvents: mockPersistPinnedEventOnTimeline.mockReturnValue( - new Error('Test error') - ), - }; - }); - - jest.doMock('../../../saved_object/notes/saved_object', () => { - return { - persistNote: mockPersistNote, - }; - }); - }); - test('disallows invalid query', async () => { - request = requestMock.create({ - method: 'post', - path: TIMELINE_EXPORT_URL, - body: { id: 'someId' }, - }); - const importTimelinesRoute = jest.requireActual('.').importTimelinesRoute; - - importTimelinesRoute(server.router, createMockConfig(), securitySetup); - const result = server.validate(request); - - expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value {"id":"someId"}, excess properties: ["id"]' - ); - }); - }); }); describe('import timeline templates', () => { @@ -904,7 +861,7 @@ describe('import timeline templates', () => { request = requestMock.create({ method: 'post', path: TIMELINE_EXPORT_URL, - body: { id: 'someId' }, + body: { isImmutable: 1 }, }); const importTimelinesRoute = jest.requireActual('.').importTimelinesRoute; @@ -912,7 +869,7 @@ describe('import timeline templates', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value {"id":"someId"}, excess properties: ["id"]' + "isImmutable: Expected 'true' | 'false', received number" ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts index be5ebc44e7faa..59a86238941ab 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts @@ -7,18 +7,23 @@ import { extname } from 'path'; import type { Readable } from 'stream'; +import { get } from 'lodash/fp'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_IMPORT_URL } from '../../../../../../common/constants'; import type { ConfigType } from '../../../../../config'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { importTimelines } from './helpers'; -import { ImportTimelinesPayloadSchemaRt } from '../../../../../../common/api/timeline'; +import { + ImportTimelinesRequestBody, + type ImportTimelinesResponse, +} from '../../../../../../common/api/timeline'; import { buildFrameworkRequest } from '../../../utils/common'; export { importTimelines } from './helpers'; @@ -39,11 +44,13 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi .addVersion( { validate: { - request: { body: buildRouteValidationWithExcess(ImportTimelinesPayloadSchemaRt) }, + request: { + body: buildRouteValidationWithZod(ImportTimelinesRequestBody), + }, }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const siemResponse = buildSiemResponse(response); const savedObjectsClient = (await context.core).savedObjects.client; @@ -52,7 +59,7 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi } const { file, isImmutable } = request.body; - const { filename } = file.hapi; + const filename = extractFilename(file); const fileExtension = extname(filename).toLowerCase(); if (fileExtension !== '.ndjson') { @@ -69,8 +76,11 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi frameworkRequest, isImmutable === 'true' ); - if (typeof res !== 'string') return response.ok({ body: res ?? {} }); - else throw res; + if (res instanceof Error || typeof res === 'string') { + throw res; + } else { + return response.ok({ body: res ?? {} }); + } } catch (err) { const error = transformError(err); const siemResponse = buildSiemResponse(response); @@ -82,3 +92,11 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi } ); }; + +function extractFilename(fileObj: unknown) { + const filename = get('hapi.filename', fileObj); + if (filename && typeof filename === 'string') { + return filename; + } + throw new Error('`filename` missing in file'); +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts index 1c3cba9fdcb91..1297f0cb1a829 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts @@ -7,22 +7,22 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { IKibanaResponse } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; -import type { ConfigType } from '../../../../..'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { patchTimelineSchema } from '../../../../../../common/api/timeline'; +import { + PatchTimelineRequestBody, + type PatchTimelineResponse, +} from '../../../../../../common/api/timeline'; import { buildFrameworkRequest, TimelineStatusActions } from '../../../utils/common'; import { createTimelines } from '../create_timelines'; import { CompareTimelinesStatus } from '../../../utils/compare_timelines_status'; -import type { PatchTimelinesResponse } from '../../../../../../common/api/timeline'; -export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: TIMELINE_URL, @@ -34,11 +34,11 @@ export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter, _: Con .addVersion( { validate: { - request: { body: buildRouteValidationWithExcess(patchTimelineSchema) }, + request: { body: buildRouteValidationWithZod(PatchTimelineRequestBody) }, }, version: '2023-10-31', }, - async (context, request, response): Promise> => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts index b8416b9ffe7bc..cf66c02cf9c97 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts @@ -5,14 +5,13 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; 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 type { ConfigType } from '../../../../..'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; @@ -23,7 +22,7 @@ import { TimelineTypeEnum, } from '../../../../../../common/api/timeline'; -export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: TIMELINE_FAVORITE_URL, @@ -39,7 +38,11 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: Co request: { body: buildRouteValidationWithZod(PersistFavoriteRouteRequestBody) }, }, }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts index 09549f9b9034f..0afc7d21ae296 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts @@ -6,24 +6,24 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; + import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_RESOLVE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -import { getTimelineQuerySchema } from '../../../../../../common/api/timeline'; -import { getTimelineTemplateOrNull, resolveTimelineOrNull } from '../../../saved_object/timelines'; -import type { - SavedTimeline, - ResolvedTimelineWithOutcomeSavedObject, +import { + ResolveTimelineRequestQuery, + type ResolveTimelineResponse, } from '../../../../../../common/api/timeline'; +import { getTimelineTemplateOrNull, resolveTimelineOrNull } from '../../../saved_object/timelines'; +import type { SavedTimeline, ResolvedTimeline } from '../../../../../../common/api/timeline'; -export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_RESOLVE_URL, @@ -36,16 +36,16 @@ export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter, _: Co { version: '2023-10-31', validate: { - request: { query: buildRouteValidationWithExcess(getTimelineQuerySchema) }, + request: { query: buildRouteValidationWithZod(ResolveTimelineRequestQuery) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); const query = request.query ?? {}; const { template_timeline_id: templateTimelineId, id } = query; - let res: SavedTimeline | ResolvedTimelineWithOutcomeSavedObject | null = null; + let res: SavedTimeline | ResolvedTimeline | null = null; if (templateTimelineId != null && id == null) { // Template timelineId is not a SO id, so it does not need to be updated to use resolve diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts index a12aaeb010234..571e0f4da8616 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts @@ -13,7 +13,7 @@ import type { Note } from '../../../../../common/api/timeline'; export const persistNotes = async ( frameworkRequest: FrameworkRequest, timelineSavedObjectId: string, - existingNoteIds?: string[], + existingNoteIds?: string[] | null, newNotes?: Note[], overrideOwner: boolean = true ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts index 2cde936f1c462..88054f0eb6953 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts @@ -16,7 +16,7 @@ import { SavedObjectTimelineType, SavedObjectTimelineStatus, } from '../../../../../common/types/timeline/saved_object'; -import type { TimelineSavedObject } from '../../../../../common/api/timeline'; +import type { TimelineResponse } from '../../../../../common/api/timeline'; import { type TimelineType, TimelineTypeEnum, @@ -49,7 +49,7 @@ const getTimelineTypeAndStatus = ( }; }; -export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineSavedObject => +export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineResponse => pipe( TimelineSavedObjectWithDraftRuntime.decode(savedObject), map((savedTimeline) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index 20049a63bddba..7e25a38363e2a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -23,8 +23,8 @@ import { getNotesByTimelineId, persistNote } from '../notes/saved_object'; import { getAllPinnedEventsByTimelineId, persistPinnedEventOnTimeline } from '../pinned_events'; import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { - AllTimelinesResponse, - ResolvedTimelineWithOutcomeSavedObject, + GetTimelinesResponse, + ResolvedTimeline, SavedTimeline, } from '../../../../../common/api/timeline'; import { @@ -141,7 +141,7 @@ describe('saved_object', () => { pageSize: 10, pageIndex: 1, }; - let result = null as unknown as AllTimelinesResponse; + let result = null as unknown as GetTimelinesResponse; beforeEach(async () => { (convertSavedObjectToSavedTimeline as jest.Mock).mockReturnValue(mockGetTimelineValue); mockFindSavedObject = jest @@ -275,7 +275,7 @@ describe('saved_object', () => { describe('resolveTimelineOrNull', () => { let mockResolveSavedObject: jest.Mock; let mockRequest: FrameworkRequest; - let result: ResolvedTimelineWithOutcomeSavedObject | null = null; + let result: ResolvedTimeline | null = null; beforeEach(async () => { (convertSavedObjectToSavedTimeline as jest.Mock).mockReturnValue(mockResolvedTimeline); mockResolveSavedObject = jest.fn().mockReturnValue(mockResolvedSavedObject); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index 08eeda3d8ab56..c3016164d4e7e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -19,20 +19,17 @@ import type { Note, BareNote, PinnedEvent, - AllTimelinesResponse, + GetTimelinesResponse, ExportTimelineNotFoundError, PageInfoTimeline, - ResponseTimelines, FavoriteTimelineResponse, - ResponseTimeline, SortTimeline, - TimelineResult, + TimelineResponse, TimelineType, TimelineStatus, - ResolvedTimelineWithOutcomeSavedObject, - TimelineSavedObject, + ResolvedTimeline, SavedTimeline, - TimelineWithoutExternalRefs, + SavedTimelineWithSavedObjectId, } from '../../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { SavedObjectTimelineWithoutExternalRefs } from '../../../../../common/types/timeline/saved_object'; @@ -49,11 +46,13 @@ import { timelineFieldsMigrator } from './field_migrator'; export { pickSavedTimeline } from './pick_saved_timeline'; export { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; +type TimelineWithoutExternalRefs = Omit; + export const getTimeline = async ( request: FrameworkRequest, timelineId: string, timelineType: TimelineType | null = TimelineTypeEnum.default -): Promise => { +): Promise => { let timelineIdToUse = timelineId; try { if (timelineType === TimelineTypeEnum.template) { @@ -77,7 +76,7 @@ export const getTimeline = async ( export const getTimelineOrNull = async ( frameworkRequest: FrameworkRequest, savedObjectId: string -): Promise => { +): Promise => { let timeline = null; try { timeline = await getTimeline(frameworkRequest, savedObjectId); @@ -89,23 +88,19 @@ export const getTimelineOrNull = async ( export const resolveTimelineOrNull = async ( frameworkRequest: FrameworkRequest, savedObjectId: string -): Promise => { - let resolvedTimeline = null; +): Promise => { try { - resolvedTimeline = await resolveSavedTimeline(frameworkRequest, savedObjectId); - // eslint-disable-next-line no-empty - } catch (e) {} - return resolvedTimeline; - // } + const resolvedTimeline = await resolveSavedTimeline(frameworkRequest, savedObjectId); + return resolvedTimeline; + } catch (e) { + return null; + } }; export const getTimelineByTemplateTimelineId = async ( request: FrameworkRequest, templateTimelineId: string -): Promise<{ - totalCount: number; - timeline: TimelineSavedObject[]; -}> => { +): Promise => { const options: SavedObjectsFindOptions = { type: timelineSavedObjectType, filter: `siem-ui-timeline.attributes.templateTimelineId: "${templateTimelineId}"`, @@ -117,7 +112,7 @@ export const getTimelineByTemplateTimelineId = async ( export const getTimelineTemplateOrNull = async ( frameworkRequest: FrameworkRequest, templateTimelineId: string -): Promise => { +): Promise => { let templateTimeline = null; try { templateTimeline = await getTimelineByTemplateTimelineId(frameworkRequest, templateTimelineId); @@ -190,10 +185,7 @@ export const getExistingPrepackagedTimelines = async ( request: FrameworkRequest, countsOnly?: boolean, pageInfo?: PageInfoTimeline -): Promise<{ - totalCount: number; - timeline: TimelineSavedObject[]; -}> => { +): Promise => { const queryPageInfo = countsOnly && pageInfo == null ? { @@ -218,7 +210,7 @@ export const getAllTimeline = async ( sort: SortTimeline | null, status: TimelineStatus | null, timelineType: TimelineType | null -): Promise => { +): Promise => { const searchTerm = search != null ? search : undefined; const searchFields = ['title', 'description']; const filter = combineFilters([ @@ -291,7 +283,7 @@ export const getAllTimeline = async ( export const getDraftTimeline = async ( request: FrameworkRequest, timelineType: TimelineType | null -): Promise => { +): Promise => { const filter = combineFilters([ getTimelineTypeFilter(timelineType ?? null, TimelineStatusEnum.draft), getTimelinesCreatedAndUpdatedByCurrentUser({ request }), @@ -385,13 +377,19 @@ export const persistFavorite = async ( } }; +export interface InternalTimelineResponse { + code: number; + message: string; + timeline: TimelineResponse; +} + export const persistTimeline = async ( request: FrameworkRequest, timelineId: string | null, version: string | null, timeline: SavedTimeline, isImmutable?: boolean -): Promise => { +): Promise => { const savedObjectsClient = (await request.context.core).savedObjects.client; const userInfo = isImmutable ? ({ username: 'Elastic' } as AuthenticatedUser) : request.user; try { @@ -414,7 +412,7 @@ export const persistTimeline = async ( timeline: await getSavedTimeline(request, timelineId), }; } else if (getOr(null, 'output.statusCode', err) === 403) { - const timelineToReturn: TimelineResult = { + const timelineToReturn: TimelineResponse = { ...timeline, savedObjectId: '', version: '', @@ -439,7 +437,7 @@ export const createTimeline = async ({ timeline: SavedTimeline; savedObjectsClient: SavedObjectsClientContract; userInfo: AuthenticatedUser | null; -}) => { +}): Promise => { const { transformedFields: migratedAttributes, references } = timelineFieldsMigrator.extractFieldsToReferences({ data: pickSavedTimeline(timelineId, timeline, userInfo), @@ -479,7 +477,7 @@ const updateTimeline = async ({ savedObjectsClient: SavedObjectsClientContract; userInfo: AuthenticatedUser | null; version: string | null; -}) => { +}): Promise => { const rawTimelineSavedObject = await savedObjectsClient.get( timelineSavedObjectType, @@ -516,11 +514,12 @@ export const updatePartialSavedTimeline = async ( timelineId ); - const { transformedFields, references } = - timelineFieldsMigrator.extractFieldsToReferences({ - data: timeline, - existingReferences: currentSavedTimeline.references, - }); + const { transformedFields, references } = timelineFieldsMigrator.extractFieldsToReferences< + Omit + >({ + data: timeline, + existingReferences: currentSavedTimeline.references, + }); const timelineUpdateAttributes = pickSavedTimeline( null, @@ -588,7 +587,7 @@ export const copyTimeline = async ( request: FrameworkRequest, timeline: SavedTimeline, timelineId: string -): Promise => { +): Promise => { const savedObjectsClient = (await request.context.core).savedObjects.client; // Fetch all objects that need to be copied @@ -658,7 +657,10 @@ const resolveBasicSavedTimeline = async (request: FrameworkRequest, timelineId: }; }; -const resolveSavedTimeline = async (request: FrameworkRequest, timelineId: string) => { +const resolveSavedTimeline = async ( + request: FrameworkRequest, + timelineId: string +): Promise => { const userName = request.user?.username ?? UNAUTHENTICATED_USER; const { resolvedTimelineSavedObject, ...resolveAttributes } = await resolveBasicSavedTimeline( @@ -673,7 +675,6 @@ const resolveSavedTimeline = async (request: FrameworkRequest, timelineId: strin ]); const [notes, pinnedEvents, timeline] = timelineWithNotesAndPinnedEvents; - return { timeline: timelineWithReduxProperties(notes, pinnedEvents, timeline, userName), ...resolveAttributes, @@ -742,9 +743,9 @@ export const convertStringToBase64 = (text: string): string => Buffer.from(text) export const timelineWithReduxProperties = ( notes: Note[], pinnedEvents: PinnedEvent[], - timeline: TimelineSavedObject, + timeline: TimelineResponse, userName: string -): TimelineSavedObject => ({ +): TimelineResponse => ({ ...timeline, favorite: timeline.favorite != null && userName != null @@ -789,7 +790,7 @@ export const getSelectedTimelines = async ( ); const timelineObjects: { - timelines: TimelineSavedObject[]; + timelines: TimelineResponse[]; errors: ExportTimelineNotFoundError[]; } = savedObjects.saved_objects.reduce( (acc, savedObject) => { @@ -805,7 +806,7 @@ export const getSelectedTimelines = async ( return { errors: [...acc.errors, savedObject.error], timelines: acc.timelines }; }, { - timelines: [] as TimelineSavedObject[], + timelines: [] as TimelineResponse[], errors: [] as ExportTimelineNotFoundError[], } ); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts index 50b57bf96bd5b..221e088bdab02 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts @@ -9,14 +9,14 @@ import { isEmpty } from 'lodash/fp'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { getUserDisplayName } from '@kbn/user-profile-components'; import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; -import type { SavedTimelineWithSavedObjectId } from '../../../../../common/api/timeline'; +import type { SavedTimeline } from '../../../../../common/api/timeline'; import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../../common/api/timeline'; export const pickSavedTimeline = ( timelineId: string | null, - savedTimeline: SavedTimelineWithSavedObjectId, + savedTimeline: SavedTimeline, userInfo: AuthenticatedUser | null -): SavedTimelineWithSavedObjectId => { +): SavedTimeline => { const dateNow = new Date().valueOf(); if (timelineId == null) { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts index a5e88a3ea04d5..caf4bee68e70f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts @@ -7,9 +7,9 @@ import path, { join, resolve } from 'path'; import type { - CheckTimelineStatusRt, - TimelineSavedObject, - ImportTimelinesSchema, + TimelineResponse, + ImportTimelines, + InstallPrepackedTimelinesRequestBody, } from '../../../../common/api/timeline'; import type { FrameworkRequest } from '../../framework'; @@ -19,9 +19,9 @@ import { getExistingPrepackagedTimelines } from '../saved_object/timelines'; import { loadData, getReadables } from './common'; export const getTimelinesToUpdate = ( - timelinesFromFileSystem: ImportTimelinesSchema[], - installedTimelines: TimelineSavedObject[] -): ImportTimelinesSchema[] => { + timelinesFromFileSystem: ImportTimelines[], + installedTimelines: TimelineResponse[] +): ImportTimelines[] => { return timelinesFromFileSystem.filter((timeline) => installedTimelines.some((installedTimeline) => { return ( @@ -34,9 +34,9 @@ export const getTimelinesToUpdate = ( }; export const getTimelinesToInstall = ( - timelinesFromFileSystem: ImportTimelinesSchema[], - installedTimelines: TimelineSavedObject[] -): ImportTimelinesSchema[] => { + timelinesFromFileSystem: ImportTimelines[], + installedTimelines: TimelineResponse[] +): ImportTimelines[] => { return timelinesFromFileSystem.filter( (timeline) => !installedTimelines.some( @@ -49,11 +49,11 @@ export const checkTimelinesStatus = async ( frameworkRequest: FrameworkRequest, filePath?: string, fileName?: string -): Promise => { +): Promise => { let readStream; let timeline: { totalCount: number; - timeline: TimelineSavedObject[]; + timeline: TimelineResponse[]; }; const dir = resolve( join( @@ -75,7 +75,7 @@ export const checkTimelinesStatus = async ( }; } - return loadData<'utf-8', CheckTimelineStatusRt>( + return loadData<'utf-8', InstallPrepackedTimelinesRequestBody>( readStream, (timelinesFromFileSystem: T) => { if (Array.isArray(timelinesFromFileSystem)) { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts index edee5ee4cd912..259719a18bdf0 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type * as rt from 'io-ts'; + import { set } from '@kbn/safer-lodash-set/fp'; import readline from 'readline'; import fs from 'fs'; @@ -13,7 +13,6 @@ import { createListStream } from '@kbn/utils'; import { schema } from '@kbn/config-schema'; import type { KibanaRequest, RequestHandlerContext } from '@kbn/core/server'; -import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import type { FrameworkRequest } from '../../framework'; @@ -38,12 +37,6 @@ export const buildFrameworkRequest = async ( export const escapeHatch = schema.object({}, { unknowns: 'allow' }); -type ErrorFactory = (message: string) => Error; - -export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => { - throw createError(formatErrors(errors).join('\n')); -}; - export const getReadables = (dataPath: string): Promise => new Promise((resolved, reject) => { const contents: string[] = []; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts index e5e247c1209fe..716bacaacf5ac 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts @@ -26,7 +26,7 @@ import { NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, } from './failure_cases'; -import type { TimelineSavedObject } from '../../../../common/api/timeline'; +import type { TimelineResponse } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import { mockGetTimelineValue, mockGetTemplateTimelineValue } from '../__mocks__/import_timelines'; @@ -69,7 +69,7 @@ describe('failure cases', () => { const version = null; const templateTimelineVersion = null; const templateTimelineId = null; - const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTimeline = mockGetTimelineValue as TimelineResponse; const existTemplateTimeline = null; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, @@ -94,7 +94,7 @@ describe('failure cases', () => { const templateTimelineVersion = 1; const templateTimelineId = 'template-timeline-id-one'; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -144,7 +144,7 @@ describe('failure cases', () => { const templateTimelineVersion = null; const templateTimelineId = null; const existTimeline = { - ...(mockGetTimelineValue as TimelineSavedObject), + ...(mockGetTimelineValue as TimelineResponse), status: TimelineStatusEnum.immutable, }; const existTemplateTimeline = null; @@ -172,7 +172,7 @@ describe('failure cases', () => { const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; const existTemplateTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), status: TimelineStatusEnum.immutable, }; const result = checkIsUpdateFailureCases( @@ -198,7 +198,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -246,10 +246,10 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), savedObjectId: 'someOtherId', }; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -273,7 +273,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -297,7 +297,7 @@ describe('failure cases', () => { const templateTimelineVersion = null; const templateTimelineId = null; const existTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), savedObjectId: 'someOtherId', }; const existTemplateTimeline = null; @@ -326,7 +326,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.draft, @@ -350,7 +350,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -373,7 +373,7 @@ describe('failure cases', () => { const version = mockGetTimelineValue.version; const templateTimelineVersion = null; const templateTimelineId = null; - const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTimeline = mockGetTimelineValue as TimelineResponse; const existTemplateTimeline = null; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, @@ -399,7 +399,7 @@ describe('failure cases', () => { const version = mockGetTimelineValue.version; const templateTimelineVersion = null; const templateTimelineId = null; - const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTimeline = mockGetTimelineValue as TimelineResponse; const existTemplateTimeline = null; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, @@ -424,7 +424,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -448,7 +448,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.immutable, @@ -496,10 +496,10 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), savedObjectId: 'someOtherId', }; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -523,7 +523,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -547,7 +547,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts index 045793e2251ba..7dace57181239 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash/fp'; -import type { TimelineType, TimelineSavedObject } from '../../../../common/api/timeline'; +import type { TimelineType, TimelineResponse } from '../../../../common/api/timeline'; import { type TimelineStatus, TimelineStatusEnum } from '../../../../common/api/timeline'; export const UPDATE_TIMELINE_ERROR_MESSAGE = @@ -42,8 +42,8 @@ export const DEFAULT_ERROR = `Something has gone wrong. We didn't handle somethi const isUpdatingStatus = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { const obj = isHandlingTemplateTimeline ? existTemplateTimeline : existTimeline; return obj?.status === TimelineStatusEnum.immutable ? UPDATE_STATUS_ERROR_MESSAGE : null; @@ -76,8 +76,8 @@ const commonUpdateTemplateTimelineCheck = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (isHandlingTemplateTimeline) { if ( @@ -129,8 +129,8 @@ const commonUpdateTimelineCheck = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (existTimeline == null) { // timeline !exists @@ -158,8 +158,8 @@ const commonUpdateCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (isHandlingTemplateTimeline) { return commonUpdateTemplateTimelineCheck( @@ -193,8 +193,8 @@ const createTemplateTimelineCheck = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (isHandlingTemplateTimeline && existTemplateTimeline != null) { // Throw error to create timeline template in patch @@ -219,8 +219,8 @@ export const checkIsUpdateViaImportFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (!isHandlingTemplateTimeline) { if (existTimeline == null) { @@ -281,8 +281,8 @@ export const checkIsUpdateFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { const error = isUpdatingStatus( isHandlingTemplateTimeline, @@ -315,8 +315,8 @@ export const checkIsCreateFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (!isHandlingTemplateTimeline && existTimeline != null) { return { @@ -346,8 +346,8 @@ export const checkIsCreateViaImportFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (status === TimelineStatusEnum.draft) { return { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts index 360125e450764..44afe078f9104 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { TimelineSavedObject } from '../../../../common/api/timeline'; +import type { TimelineResponse } from '../../../../common/api/timeline'; import { type TimelineType, TimelineTypeEnum, @@ -27,7 +27,7 @@ export class TimelineObject { public readonly version: string | number | null; private frameworkRequest: FrameworkRequest; - public data: TimelineSavedObject | null; + public data: TimelineResponse | null; constructor({ id = null, diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 95644a6d8db2e..aba6550ea9c3b 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -28,6 +28,7 @@ import { BulkPatchRulesRequestBodyInput } from '@kbn/security-solution-plugin/co import { BulkUpdateRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.gen'; import { BulkUpsertAssetCriticalityRecordsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.gen'; import { CleanDraftTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen'; +import { CopyTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/copy_timeline/copy_timeline_route.gen'; import { CreateAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen'; import { CreateAssetCriticalityRecordRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/create_asset_criticality.gen'; import { CreateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.gen'; @@ -272,6 +273,18 @@ If asset criticality records already exist for the specified entities, those rec .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Copies and returns a timeline or timeline template. + + */ + copyTimeline(props: CopyTimelineProps, kibanaSpace: string = 'default') { + return supertest + .get(routeWithNamespace('/api/timeline/_copy', kibanaSpace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, createAlertsIndex(kibanaSpace: string = 'default') { return supertest .post(routeWithNamespace('/api/detection_engine/index', kibanaSpace)) @@ -1350,6 +1363,9 @@ export interface BulkUpsertAssetCriticalityRecordsProps { export interface CleanDraftTimelinesProps { body: CleanDraftTimelinesRequestBodyInput; } +export interface CopyTimelineProps { + body: CopyTimelineRequestBodyInput; +} export interface CreateAlertsMigrationProps { body: CreateAlertsMigrationRequestBodyInput; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts index 6895d79c5aa8e..3206c14eee04d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { - TimelineResult, + TimelineResponse, TimelineTypeEnum, } from '@kbn/security-solution-plugin/common/api/timeline'; import { FtrProviderContext } from '../../../../../api_integration/ftr_provider_context'; @@ -419,8 +419,8 @@ export default function ({ getService }: FtrProviderContext) { }); } -const omitTypename = (key: string, value: keyof TimelineResult) => +const omitTypename = (key: string, value: keyof TimelineResponse) => key === '__typename' ? undefined : value; -const omitTypenameInTimeline = (timeline: TimelineResult) => +const omitTypenameInTimeline = (timeline: TimelineResponse) => JSON.parse(JSON.stringify(timeline), omitTypename); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts index a5e732c86b65f..1dcaae65a3392 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; +import type { PersistTimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; export interface Timeline { title: string; @@ -69,7 +69,7 @@ export const getTimelineNonValidQuery = (): CompleteTimeline => ({ }); export const expectedExportedTimelineTemplate = ( - templateResponse: Cypress.Response + templateResponse: Cypress.Response ) => { const timelineTemplateBody = templateResponse.body.data.persistTimeline.timeline; @@ -113,7 +113,9 @@ export const expectedExportedTimelineTemplate = ( }; }; -export const expectedExportedTimeline = (timelineResponse: Cypress.Response) => { +export const expectedExportedTimeline = ( + timelineResponse: Cypress.Response +) => { const timelineBody = timelineResponse.body.data.persistTimeline.timeline; return { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts index 864767d0504f9..055d98a2efcfd 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts @@ -6,8 +6,9 @@ */ import type { - AllTimelinesResponse, - TimelineResponse, + DeleteTimelinesResponse, + GetTimelinesResponse, + PatchTimelineResponse, } from '@kbn/security-solution-plugin/common/api/timeline'; import type { CompleteTimeline } from '../../objects/timeline'; import { getTimeline } from '../../objects/timeline'; @@ -21,7 +22,7 @@ const mockTimeline = getTimeline(); * @returns undefined */ export const createTimeline = (timeline: CompleteTimeline = mockTimeline) => - rootRequest({ + rootRequest({ method: 'POST', url: 'api/timeline', body: { @@ -75,7 +76,7 @@ export const createTimeline = (timeline: CompleteTimeline = mockTimeline) => * @returns undefined */ export const createTimelineTemplate = (timeline: CompleteTimeline = mockTimeline) => - rootRequest({ + rootRequest({ method: 'POST', url: 'api/timeline', body: { @@ -159,7 +160,7 @@ export const favoriteTimeline = ({ }); export const getAllTimelines = () => - rootRequest({ + rootRequest({ method: 'GET', url: 'api/timelines?page_size=100&page_index=1&sort_field=updated&sort_order=desc&timeline_type=default', }); @@ -167,7 +168,7 @@ export const getAllTimelines = () => export const deleteTimelines = () => { getAllTimelines().then(($timelines) => { const savedObjectIds = $timelines.body.timeline.map((timeline) => timeline.savedObjectId); - rootRequest({ + rootRequest({ method: 'DELETE', url: 'api/timeline', body: { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts index 16fb1bb48808a..580b96bfd2340 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts @@ -6,7 +6,7 @@ */ import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; -import { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; +import { PatchTimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; // @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { kibanaPackageJson } from '@kbn/repo-info'; import { type IndexedEndpointRuleAlerts } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/index_endpoint_rule_alerts'; @@ -64,7 +64,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // failing tests: https://github.com/elastic/kibana/issues/170705 describe.skip('from Timeline', () => { - let timeline: TimelineResponse; + let timeline: PatchTimelineResponse; before(async () => { log.info( diff --git a/x-pack/test/security_solution_ftr/services/timeline/index.ts b/x-pack/test/security_solution_ftr/services/timeline/index.ts index ebf6497567d81..8195f63ffc246 100644 --- a/x-pack/test/security_solution_ftr/services/timeline/index.ts +++ b/x-pack/test/security_solution_ftr/services/timeline/index.ts @@ -8,8 +8,12 @@ import { Response } from 'superagent'; import { EndpointError } from '@kbn/security-solution-plugin/common/endpoint/errors'; import { TIMELINE_DRAFT_URL, TIMELINE_URL } from '@kbn/security-solution-plugin/common/constants'; -import { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; -import { TimelineInput } from '@kbn/security-solution-plugin/common/search_strategy'; +import { + DeleteTimelinesResponse, + GetDraftTimelinesResponse, + PatchTimelineResponse, + SavedTimeline, +} from '@kbn/security-solution-plugin/common/api/timeline'; import moment from 'moment'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { FtrService } from '../../../functional/ftr_provider_context'; @@ -52,7 +56,7 @@ export class TimelineTestService extends FtrService { * for display (not sure why). TO get around this, just select a date range from the user date * picker and that seems to trigger the events to be fetched. */ - async createTimeline(title: string): Promise { + async createTimeline(title: string): Promise { // Create a new timeline draft const createdTimeline = ( await this.supertest @@ -61,7 +65,7 @@ export class TimelineTestService extends FtrService { .set('elastic-api-version', '2023-10-31') .send({ timelineType: 'default' }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as TimelineResponse) + .then((response) => response.body as GetDraftTimelinesResponse) ).data.persistTimeline.timeline; this.log.info('Draft timeline:'); @@ -71,7 +75,7 @@ export class TimelineTestService extends FtrService { const { savedObjectId: timelineId, version } = createdTimeline; - const timelineUpdate: TimelineInput = { + const timelineUpdate: SavedTimeline = { title, // Set date range to the last 1 year dateRange: { @@ -79,7 +83,7 @@ export class TimelineTestService extends FtrService { end: moment().toISOString(), // Not sure why `start`/`end` are defined as numbers in the type, but looking at the // UI's use of it, I can see they are being set to strings, so I'm forcing a cast here - } as unknown as TimelineInput['dateRange'], + } as unknown as SavedTimeline['dateRange'], // Not sure why, but the following fields are not in the created timeline, which causes // the timeline to not be able to pull in the event for display @@ -107,9 +111,9 @@ export class TimelineTestService extends FtrService { async updateTimeline( timelineId: string, - updates: TimelineInput, + updates: SavedTimeline, version: string - ): Promise { + ): Promise { return await this.supertest .patch(TIMELINE_URL) .set('kbn-xsrf', 'true') @@ -120,11 +124,11 @@ export class TimelineTestService extends FtrService { timeline: updates, }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as TimelineResponse); + .then((response) => response.body as PatchTimelineResponse); } /** Deletes a timeline using it timeline id */ - async deleteTimeline(id: string | string[]): Promise { + async deleteTimeline(id: string | string[]) { await this.supertest .delete(TIMELINE_URL) .set('kbn-xsrf', 'true') @@ -133,7 +137,7 @@ export class TimelineTestService extends FtrService { savedObjectIds: Array.isArray(id) ? id : [id], }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as TimelineResponse); + .then((response) => response.body as DeleteTimelinesResponse); } /** @@ -173,7 +177,7 @@ export class TimelineTestService extends FtrService { /** If defined, then only alerts from the specific `agent.id` will be displayed */ endpointAgentId: string; }> - ): Promise { + ): Promise { const newTimeline = await this.createTimeline(title); const { expression, esQuery } = this.getEndpointAlertsKqlQuery(endpointAgentId);